一、问题
跨域请求无法处理的问题,由于为了阻止恶意的网站通过JS脚本来窃取正常网站受保护的资源。所由所有的浏览器的默认策略是阻止XmlHttpRequest的跨域的异步请求。 但是对于一个 复合型的应用集合来说,可能需要使用不同的域来部署我们的应用。对于这种正常的需求,我们的服务与应用就需要能够支持指定信认域的跨域的异步请法。
通常来说,我们有三种替代方案
1, 使用JSONP,
JSONP(JSON with Padding), 但由于JSONP使用的是在HTML DOM中加入<script>标签来包裹来对提供GET的服务请求数据,所有JSONP只对HTTP GET方式的请求取作用。我们的SOA框架,大多是使用POST的请求。
2, 使用分离的“Proxy”代理服务,
代理服务是指,在同一个请求的域下创建一个代理的服务, Ajax请求这个代理服务,由代理服务来路由这个请求到目标域的服务上。 如果我们的所有的运用都必须使用HTML页面来实现。 那这种方式,需要为每一个不同域的应用创建代理服务。 但请注意,如果我们的应用是门户网,请求可能来自外面的人, 对于性能需求高,那么使用HTML是不可行的。我们需要使用ASP.NET MVC结构, 那么Controller层就如同我们的代理服务层。
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的话,只会显示一条。
二、解决步骤。
1, 应用, 对于应用层来说, 该怎么样写,还是怎么样写,不需要变化。但对于底版本的浏览器的支持需要改代码,不能使用XmlHttpRequest。需要使用XDomainRequest。
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个,如下
建议这个设置在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;
}
结束。