WCF SOA --- AJAX 跨域请求处理 CORS for WCF

1、问题

       跨域请求没法处理的问题,因为为了阻止恶意的网站经过JS脚原本窃取正常网站受保护的资源。所由全部的浏览器的默认策略是阻止XmlHttpRequest的跨域的异步请求。 可是对于一个 复合型的应用集合来讲,可能须要使用不一样的域来部署咱们的应用。对于这种正常的需求,咱们的服务与应用就须要可以支持指定信认域的跨域的异步请法。web

      一般来讲,咱们有三种替代方案跨域

      1, 使用JSONP,浏览器

            JSONP(JSON with Padding), 但因为JSONP使用的是在HTML DOM中加入<script>标签来包裹来对提供GET的服务请求数据,全部JSONP只对HTTP GET方式的请求取做用。咱们的SOA框架,大可能是使用POST的请求。cors

      2, 使用分离的“Proxy”代理服务,框架

            代理服务是指,在同一个请求的域下建立一个代理的服务, Ajax请求这个代理服务,由代理服务来路由这个请求到目标域的服务上。 若是咱们的全部的运用都必须使用HTML页面来实现。 那这种方式,须要为每个不一样域的应用建立代理服务。 但请注意,若是咱们的应用是门户网,请求可能来自外面的人, 对于性能需求高,那么使用HTML是不可行的。咱们须要使用ASP.NET MVC结构, 那么Controller层就如同咱们的代理服务层。dom

      3, 使用CORS异步

            CORS,Cross-Origin Resource Sharing, 这是一种新的对跨域支持的方法, 它是经过定义一系列的HTTP Headers 在Service 与Client, 服务端能够去掉对于跨域的约束,不只可使用GET,还可使用POST, PUT,或是DELETE,  XmlHttpRequest对象已经实现了CORS, 将它做为正常的AJAX调用。可是目前只有最新版本的浏览器才支持, 对于Firefox 3.5如下,Safari 4如下,Chrome 3如下, IE 10 如下,可使用XDomainRequest对象来代替XmlHttpRequest对象来实现。性能

           浏览器对于使用CORS的请求,会分红次请求,第一次,预先受权请求,会将请求的方式变成OPTIONS, 请求的数据为空,这个过程其实是在看当前的Origin是否被服务容许,若是容许只服务返回200OK, 不然返回405 Method NOT Allowed, 第二次,正常请求,浏览器将请求的数据POST发送服务,服务端会正常响应。 注意下面是Chrome的,IE的话,只会显示一条。网站

image2015-1-28 16-56-35

2、解决步骤。

1, 应用, 对于应用层来讲, 该怎么样写,仍是怎么样写,不须要变化。但对于底版本的浏览器的支持须要改代码,不能使用XmlHttpRequest。须要使用XDomainRequest。spa

2, 服务, 这里咱们使用的是WCF, 对于使用ASP.NET Web API的话,使用参考:https://msdn.microsoft.com/en-us/magazine/dn532203.aspx, 或是Google, CORS in ASP.NET Web Api.

对于WCF,我参考了 CarlosFigueira写的 http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support-in-wcf.aspx ,文章写的很是不错, 可是我发现,其它它是一个半成品,没有实现玩,它的目的是建立一个新的Operation,只是支持OPTIONS方式的请求,Action的名称是当前操做的名称 +特定的后缀。 但这样问题来了,客户端浏览器的请求,每一次不会自动加上这个后缀,全部Invoke一直都不会被调用。 你们的兴趣下载了使用的话,若是想结合到本身的项目中, 可要记得把Web.config中的<modules runAllManagedModulesForAllRequests="true">去掉。 固然,前提是,你使用的Service都是本身经过ServiceHostFactory建立出来的, 若是使用默认的,是不会支持的。 其它 CarlosFigueira想法。 我作了一些改变。 实现步骤以下:

1,在IIS中,添加Http  Response Header 3个,以下

image2015-1-28 17-15-25

建议这个设置在WebSite上, 这样你的服务在这个Website下做为一个Web Application的话,将自动继承这些配置。 写在web.config,能够方便控制容许访问的域。当前我使用 *, 这样全部的域都能访问,若是想指定哪些域的话,能够改为如 Http://domain1.com,Http://domain2......。那么只有来自这些域的请求才能处理。固然这个设置,或以在WCF,添加本身的IDispatchMessageInspector实现,  在BeforeSendReply事件中,添加响应头。

2,在使用自定ServiceHostFactory,建立服务时, 咱们能够指定IServiceBehavior, 在它的ApplyDispatchBehavior事件中。 找到全部的Operation,对它注入WebInvokeAttribute, Method设置成*,  目的是动态将Operation对 Method=OPTIONS的请求处理, 以下代码

foreach (ServiceEndpoint endpoint in desc.Endpoints)
{
foreach (var operation in endpoint.Contract.Operations)
{
//Add WebInvoke Attribute to all operation except for WebGet only, so that our opeations are able to handle OPTIONS Http Request Method for CORS issue.
if ( !operation.Behaviors.Any(d => d is WebGetAttribute)
&& !operation.Behaviors.Any(d => d is WebInvokeAttribute))
{
WebInvokeAttribute wia = new WebInvokeAttribute();
wia.UriTemplate = operation.Name;
wia.Method = "*";
operation.Behaviors.Add(wia);
}
}
}

3,当OPTIONS方式的请求来到时,在自定的 IOperationInvoker的invoke事件中, 判断当前的Http Method是否为OPTIONS, 若是是的话,不处理正常逻辑, 返回一个200 OK的响应。代码以下

public object Invoke(object instance, object[] inputs, out object[] outputs)
{

string operationName = "";
if (OperationContext.Current.IncomingMessageHeaders.Action != null)
{
operationName = OperationContext.Current.IncomingMessageHeaders.Action.ToString();
}
if (OperationContext.Current.IncomingMessageProperties.Keys.Contains("HttpOperationName"))
{
operationName = OperationContext.Current.IncomingMessageProperties["HttpOperationName"].ToString();
}

HttpRequestMessageProperty request = System.ServiceModel.OperationContext.Current.IncomingMessageProperties["httpRequest"] as HttpRequestMessageProperty;
//If enable CORS, then we need to handle OPTIONS Method to reply OK, so that the browser will seed the right POST request.
if (request != null
&& request.Method == "OPTIONS" )
{
System.ServiceModel.Channels.Message input = (System.ServiceModel.Channels.Message)inputs[0];
outputs = null;
return HandlePreflight(input, operationName);
}

else

{

正常处理。。。。

}

}

System.ServiceModel.Channels.Message HandlePreflight(System.ServiceModel.Channels.Message input, string operationName)
{
System.ServiceModel.Channels.Message reply = System.ServiceModel.Channels.Message.CreateMessage(MessageVersion.None, operationName);
HttpResponseMessageProperty httpResponse = new HttpResponseMessageProperty();
reply.Properties.Add(HttpResponseMessageProperty.Name, httpResponse);

httpResponse.SuppressEntityBody = true;
httpResponse.StatusCode = System.Net.HttpStatusCode.OK;
httpResponse.Headers.Add(CorsConstants.AccessControlAllowOrigin, "*");
httpResponse.Headers.Add(CorsConstants.AccessControlAllowMethods, string.Join(",", new List<string>() { "POST", "GET", "OPTIONS" }));

return reply;
}

结束。

相关文章
相关标签/搜索