现在流行webapp或者前端和后端分离,那么后端服务就会从重的程序处理,转变成数据驱动的数据抽取即可。那么webapi就变成了最佳选择。
然而,处理http请求还是仍旧的核心内容。 先看下跨域请求的定义:
跨域资源共享-Cross Origin Resource Sharing(CORS)是一项W3C标准,允许服务端释放同源策略,使得服务端在接受一些跨域请求的同时拒绝其他的跨域请求( 浏览器的安全策略会阻止网页向另一个站点发送ajax请求,同时也会阻止恶意站点从另一个站点读取数据。这种限制被称作“同源策略”。)
如果两个URL的协议、域名、端口相同,那么这两个URL就是同源。不是同源的就是跨域的,也就是说凡是发送请求URL的协议、域名、端口三者中任意一个与当前页面的URL不同即为跨域。
看下CORS如何工作的
CORS特性提供了多个Http请求头以供跨域请求使用。如果一个浏览器支持CORS,它会自动的为跨域请求加上相应的http请求头,在Http请求头的“Origin”项中是发起请求的站点域名。
如果服务端允许了这个跨域请求,它会在http的Response的头部设置项Access-Control-Allow-Origin。只有当Response中Access-Control-Allow-Origin的值与Request中Origin的值相匹配时,这个请求才会成功。如果Access-Control-Allow-Origin的值是”*”,则意味着任意跨域访问都是被允许的。
如果在HttpResponse的头部中没有包含Access-Control-Allow-Origin项,跨域请求会失败。即使服务端已经返回了正确的反馈,浏览器也不会将这个返回传递给客户端应用。
对于一些CORS请求,浏览器会在发送实际需要的请求之前发送一个额外的请求,我们称之为“前置请求”。前置请求使用了Http Options方法,它包含了两个特殊的请求头Access-Control-Request-Method和Access-Control-Request-Headers。
也就是说浏览器发送了两次请求,首先发送前置请求,验证当前发送请求的站点是否在允许访问的域的范围内,若验证通过就发送真正的请求获取数据,否则请求就会被拒绝。在浏览器的调试界面(F12-Network)我们也可以看到请求是有两次。
在下列情况下,浏览器会跳过前置请求:
1.Http请求方法是GET、HEAD或POST
2.Http请求的Content-Type是application/x-www-form-urlencoded、multipart/form-data、text/plain
3.Http请求头包含了Accept, Accept-Language, Content-Language, Content-Type
理解了这个过程,和原理。下面写下如何实现:
1.如果指不限定谁能请求,谁都可以访问的话。
web.config文件中加入配置
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET,POST,PUT,DELETE,OPTIONS" />
</customHeaders>
</httpProtocol>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
注意:当收不了Options请求时, <remove name="OPTIONSVerbHandler" /> 这句是个坑,去掉试试。
同时在 Global.asax里面 加入
protected void Application_BeginRequest()
{
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
Response.End();
}
}
这两处修改意思很明确,第一个增加配置,服务器会在response的头部加入Access-Control-Allow3项内容,浏览器看到他们就完成交互了。第二处意思是看见options的前置请求并且就直接结束,接着处理下面正式的请求。
2 如果要限定哪些域名可以访问的话,如果是vs2013以上的可以直接通过NuGet安装Microsoft.AspNet.WebApi.Cors为WebApi添加CORS。使用[EnableCros]属性可以全局设置CORS,也可以为Controller或Action单独设置CORS。
但是,我的开发vs是2013版本的,引入该插件最新版本,项目即可出现dll版本不适配的情况,导致不能正常启动运行。
mvc4 不能使用,mvc5 可使用。
3 最后的方法只能根据原理,重写相关方法,来实现了。
由于跨域是2次请求。先处理前置请求,然后处理正式请求。浏览器发来的请求如下
{Method: OPTIONS, RequestUri: 'http://192.168.10.112/TempsenCloudApi/api/CompanyAccount/CreatTenant',
Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent,
Headers:
{
Connection: keep-alive
Accept: */*
Accept-Encoding: gzip
Accept-Encoding: deflate
Accept-Encoding: sdch
Accept-Language: zh-CN
Accept-Language: zh; q=0.8
Host: 192.168.10.112
Referer: http://192.168.10.104:8080/
User-Agent: Mozilla/5.0
User-Agent: (Windows NT 6.1; Win64; x64)
User-Agent: AppleWebKit/537.36
User-Agent: (KHTML, like Gecko)
User-Agent: Chrome/58.0.3029.110
User-Agent: Safari/537.36
Access-Control-Request-Method: POST
Origin: http://192.168.10.104:8080
Access-Control-Request-Headers: content-type
}}
首先重写CorsPreflightActionSelector : ApiControllerActionSelector类的SelectAction方法。
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
var originalRequest = controllerContext.Request;
var isCorsRequest = originalRequest.Headers.Contains(Headers.Origin);
if (originalRequest.Method == HttpMethod.Options && isCorsRequest)
{
var accessControlRequestMethod = originalRequest.Headers.GetValues(Headers.AccessControlRequestMethod).FirstOrDefault();
if (!string.IsNullOrEmpty(accessControlRequestMethod))
{
var modifiedRequest = new HttpRequestMessage(
new HttpMethod(accessControlRequestMethod),
originalRequest.RequestUri
);
controllerContext.Request = modifiedRequest;
HttpActionDescriptor actualDescriptor = base.SelectAction(controllerContext);
controllerContext.Request = originalRequest;
if (actualDescriptor != null && actualDescriptor.GetFilters().OfType<CorsEnabledAttribute>().Any())
{
return new CorsPreflightActionDescriptor(actualDescriptor, accessControlRequestMethod);
}
}
}
return base.SelectAction(controllerContext);
}
然后再继承CorsEnabledAttribute : ActionFilterAttribute ,控制访问的controller和action
注意几种方法选其一,不能混合使用。也就是web.config里的配置要去掉,选择后两种方式时。