㈠ 如何改写WebApi部分默认规则
如何改写WebApi部分默认规则
如何改写WebApi部分默认规则为什么要改最近公司在推广SOA框架,第一次正经接触这种技术(之前也有但还是忽略掉吧),感觉挺好,就想自己也折腾一下,实现一个简单的SOA框架
用过mvc进行开发,印象之中WebApi和Mvc好像是一样的,带着这样的预设开始玩WebApi,然后被虐得找不到着北。
被虐的原因,是Mvc和WebApi在细节上差别还是有点大,例如:
在Mvc中,一个Controller中的所有公共方法一般情况下可以响应POST方法,而WebApi中不行在Mvc中,一个Action方法中的参数即可来自Url,也可以来自Form,而WebApi中不是这样,具体的规则好像是除非你在参数中加了[FromBody],否则这个参数永远也无法从Form中获取这是这两种技术我知道的最大的差别,其他的没发现或者说是没注意,也有可能这些差别是因为我不会用,毕竟接触WebApi时间不长。如果我有些地方说错了,请指正。
就这两个不同点,我查了很多资料,也没有办法解决,第一个还好,加个特性就行了,第二个的话好像就算加了[FromBody]也还是不行,感觉就是一堆限制。接着,既然这么多让我不爽的地方,那我就来改造它吧。
改造的目标,有以下几个:
不再限制控制器必须以Controller结尾,其实这个并不是必须,只是被限制着确实不太舒服所有方法可以响应所有的请求方法,如果存在方法名相同的方法,那么才需要特性来区分Action中的参数优先从Url中获取,再从Body中获取,从Body中获取的时候,优先假设Body中的数据是表单参数,若不是则将Body中的数据当作json或xml数据进行获取定下了目标之后,感觉微软为什么要这样设计WebApi呢,或许它有它的道理。
目标好定,做起来真是头大,一开始想参考公司的SOA框架的实现,但因为我用了OWIN技术来进行宿主,而看了公司的框架好像不是用的这个,总之就是看了半天没看懂应该从哪个地方开始,反而是越看越糊,毕竟不是完全一样的技术,所以还是自己弄吧。
OK,废话了这么多,进入正题吧。首先来一个链接,没了这个文章我就不可能改造成功:http://www.cnblogs.com/beginor/archive/2012/03/22/2411496.html
OWIN宿主其实这个网上很多,我主要是为了贴代码,不然的话下面几小节写不下去
[assembly: OwinStartup(typeof(Startup))]//这句是在IIS宿主的时候使用的,作用是.Net会查找Startup类来启动整个服务 namespace Xinchen.SOA.Server { public class Startup { public void Configuration(IAppBuilder appBuilder) { HttpConfiguration config = new HttpConfiguration(); config.Routes.MapHttPRoute( name: "DefaultApi", routeTemplate: "{controller}/{action}" ); config.Services.Add(typeof(ValueProviderFactory), new MyValueProviderFactory());//自定义参数查找,实现第三个目标 config.Services.Replace(typeof(IHttpControllerSelector), new ControllerSelector(config));//自定义控制器查找,实现第一个目标 config.Services.Replace(typeof(IHttpActionSelector), new HttpActionSelector());//自定义Action查找,实现第二个目标 appBuilder.UseWebApi(config); } } } 省略了部分不太重要的代码,Services.Add和Replace从字面就能明白是什么意思,但我没有试过是否必须要像上面那样写才行
对控制器的限制public class ControllerSelector : IHttpControllerSelector { HttpConfiguration _config; IDictionary<string, HttpControllerDescriptor> _desriptors = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase); public ControllerSelector(HttpConfiguration config) { _config = config; } void InitControllers() { if (_desriptors.Count <= 0) { lock (_desriptors) { if (_desriptors.Count <= 0) { var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.GlobalAssemblyCache && !x.IsDynamic); var controllerTypes = new List<Type>(); foreach (var ass in assemblies) { controllerTypes.AddRange(ass.GetExportedTypes().Where(x => typeof(ApiController).IsAssignableFrom(x))); } var descriptors = new Dictionary<string, HttpControllerDescriptor>(); foreach (var controllerType in controllerTypes) { var descriptor = new HttpControllerDescriptor(_config, controllerType.Name, controllerType); _desriptors.Add(descriptor.ControllerName, descriptor); } } } } } public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { InitControllers(); return _desriptors; } public System.Web.Http.Controllers.HttpControllerDescriptor SelectController(System.Net.Http.HttpRequestMessage request) { InitControllers(); var routeData = request.GetRouteData(); var controllerName = Convert.ToString(routeData.Values.Get("controller")); if (string.IsNullOrWhiteSpace(controllerName)) { throw new ArgumentException(string.Format("没有在路由信息中找到controller")); } return _desriptors.Get(controllerName); } } 这个其实比较简单,测试中WebApi好像没调用GetControllerMapping方法,直接调用了SelectController方法,最后一个方法中有两个Get方法调用,Get只是把从字典获取值的TryGetValue功能给封装了一下,InitControllers方法是从当前所有的程序集中找继承了ApiController的类,找到之后缓存起来。这段代码整体比较简单。
对Action的限制public class HttpActionSelector : IHttpActionSelector { public ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor) { var methods = controllerDescriptor.ControllerType.GetMethods(); var result = new List<HttpActionDescriptor>(); foreach (var method in methods) { var descriptor = new ReflectedHttpActionDescriptor(controllerDescriptor, method); result.Add(descriptor); } return result.ToLookup(x => x.ActionName); } public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) { var actionDescriptor = new ReflectedHttpActionDescriptor(); var routeData = controllerContext.RouteData; object action = string.Empty; if (!routeData.Values.TryGetValue("action", out action)) { throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在路由中未找到action")); } string actionName = action.ToString().ToLower(); var methods = controllerContext.ControllerDescriptor.ControllerType.GetMethods().Where(x => x.Name.ToLower() == actionName); var count = methods.Count(); MethodInfo method = null; switch (count) { case 0: throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中未找到名为" + actionName + "的方法")); case 1: method = methods.FirstOrDefault();
break; default: var httpMethod = controllerContext.Request.Method; var filterdMethods = methods.Where(x => { var verb = x.GetCustomAttribute<AcceptVerbsAttribute>(); if (verb == null) { throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中找到多个名为" + actionName + "的方法,请考虑为这些方法加上AcceptVerbsAttribute特性")); } return verb.HttpMethods.Contains(httpMethod); }); if (filterdMethods.Count() > 1) { throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中找到多个名为" + actionName + "的方法,并且这些方法的AcceptVerbsAttribute都含有" + httpMethod.ToString() + ",发生重复")); } else if (filterdMethods.Count() <= 0) { throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中找到多个名为" + actionName + "的方法,但没有方法被配置为可以响应" + httpMethod.ToString() + "请求")); } method = filterdMethods.FirstOrDefault();
break; } return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); } } GetActionMapping方法很简单,从控制器类型中找到所有的Action方法并返回
SelectAction方法相对复杂,其实就是第二个目标的逻辑,代码看起来比较多其实并有很难的地方。
对Action的参数的限制这一块比较难,我试了很久才成功,而且还有坑
public class ActionValueBinder : DefaultActionValueBinder { protected override HttpParameterBinding GetParameterBinding(HttpParameterDescriptor parameter) { ParameterBindingAttribute parameterBinderAttribute = parameter.ParameterBinderAttribute; if (parameterBinderAttribute == null) { parameterBindingRules = parameter.Configuratio
㈡ 昨天有老同学问我怎么在Windows Server2012 R2里面部署 MVC 的网
一直以来都是在win8或windows server2012中进行asp.net mvc或webapi程序的部署,没有发现任何问题。今天在win2008中进行asp.net webapi的部署,访问api的时候页面显示404错误。下面步骤将解决asp.net webapi在win2008中的部署问题。
添加映射
在iis中选中需要设置的webapi站点,双击“处理程序映射”
在弹出框中添加对webapi的请求映射:
请求路径:api /*
可执行文件:%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll,注意32位和64位路径的区别。
名称:webapi,自定义名称即可
修改应用程序池的管道模式
将webapi的应用程序池的管道模式修改为经典
更新dll
映射添加之后,访问站点,不会报404错误了,但会报一些dll文件未能加载,一共有4个,名称分别如下:
System.Web.WebPages.Deployment.dll
Microsoft.Web.Infrastructure.dll
System.Web.WebPages.Razor.dll
System.Web.WebPages.dll
将上面四个dll文件复制到webapi站点到bin目录中,问题解决。
㈢ net webapi 能不能使用mvc的过滤器
这个很有用的,13个扩展点中过滤器占重要的地位。自定义身份验证、自定义post表单时自动检测错误、自定义发生异常时的自动化处理、等等应用。一定要搞明白的。
㈣ 如何使 WebAPI 自动生成漂亮又实用在线API文档
1.1 SwaggerUI
SwaggerUI 是一个简单的Restful API 测试和文档工具。简单、漂亮、易用(官方)。通过读取JSON 配置显示API. 项目本身仅仅也只依赖一些 html,css.js静态文件. 你可以几乎放在任何Web容器上使用。
1.2 Swashbuckle
Swashbuckle 是.NET类库,可以将WebAPI所有开放的控制器方法生成对应SwaggerUI的JSON配置。再通过SwaggerUI 显示出来。类库中已经包含SwaggerUI 。所以不需要额外安装。
2.快速开始
创建项目 OnlineAPI来封装网络音乐服务(示例下载) ,通过API可以搜索、获取音乐的信息和播放连接。
我尽量删除一些我们demo中不会用到的一些文件,使其看上去比较简洁。
WebAPI 安装 Swashbuckle
Install-Package Swashbuckle
代码注释生成文档说明。
Swashbuckle 是通过生成的XML文件来读取注释的,生成 SwaggerUI,JSON 配置中的说明的。
安装时会在项目目录 App_Start 文件夹下生成一个 SwaggerConfig.cs 配置文件,用于配置 SwaggerUI 相关展示行为的。如图:
将配置文件大概99行注释去掉并修改为
c.IncludeXmlComments(GetXmlCommentsPath(thisAssembly.GetName().Name));
并在当前类中添加一个方法
/// <summary>
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
protected static string GetXmlCommentsPath(string name)
{
return string.Format(@"{0}\bin\{1}.XML", AppDomain.CurrentDomain.BaseDirectory, name);
}
紧接着你在此Web项目属性生成选卡中勾选 “XML 文档文件”,编译过程中生成类库的注释文件
添加网络音乐 3个API
访问 http://<youhost>/swagger/ui/index,最终显示效果
我们通过API 测试API 是否成功运行
3.添加自定义HTTP Header
在开发移动端 API时常常需要验证权限,验证参数放在Http请求头中是再好不过了。WebAPI配合过滤器验证权限即可
首先我们需要创建一个 IOperationFilter 接口的类。IOperationFilter
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.Http.Filters;
using Swashbuckle.Swagger;
namespace OnlineAPI.Utility
{
public class HttpHeaderFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry
schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null) operation.parameters = new
List<Parameter>();
var filterPipeline =
apiDescription.ActionDescriptor.GetFilterPipeline();
//判断是否添加权限过滤器
var isAuthorized = filterPipeline.Select(filterInfo =>
filterInfo.Instance).Any(filter => filter is IAuthorizationFilter);
//判断是否允许匿名方法
var allowAnonymous =
apiDescription.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
if (isAuthorized && !allowAnonymous)
{
operation.parameters.Add(new Parameter
{
name = "access-key",
@in = "header",
description = "用户访问Key",
required = false,
type = "string"
});
}
}
}
}
在 SwaggerConfig.cs 的 EnableSwagger 配置匿名方法类添加一行注册代码
c.OperationFilter<HttpHeaderFilter>();
添加Web权限过滤器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using Newtonsoft.Json;
namespace OnlineAPI.Utility
{
/// <summary>
///
/// </summary>
public class AccessKeyAttribute : AuthorizeAttribute
{
/// <summary>
/// 权限验证
/// </summary>
/// <param name="actionContext"></param>
/// <returns></returns>
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.Headers.Contains("access-key"))
{
var accessKey = request.Headers.GetValues("access-key").SingleOrDefault();
//TODO 验证Key
return accessKey == "123456789";
}
return false;
}
/// <summary>
/// 处理未授权的请求
/// </summary>
/// <param name="actionContext"></param>
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var content = JsonConvert.SerializeObject(new {State = HttpStatusCode.Unauthorized});
actionContext.Response = new HttpResponseMessage
{
Content = new StringContent(content, Encoding.UTF8, "application/json"),
StatusCode = HttpStatusCode.Unauthorized
};
}
}
}
在你想要的ApiController 或者是 Action 添加过滤器
[AccessKey]
最终显示效果
4.显示上传文件参数
SwaggerUI 有上传文件的功能和添加自定义HTTP Header 做法类似,只是我们通过特殊的设置来标示API具有上传文件的功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Description;
using Swashbuckle.Swagger;
namespace OnlineAPI.Utility
{
/// <summary>
///
/// </summary>
public class UploadFilter : IOperationFilter
{
/// <summary>
/// 文件上传
/// </summary>
/// <param name="operation"></param>
/// <param name="schemaRegistry"></param>
/// <param name="apiDescription"></param>
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (!string.IsNullOrWhiteSpace(operation.summary) && operation.summary.Contains("upload"))
{
operation.consumes.Add("application/form-data");
operation.parameters.Add(new Parameter
{
name = "file",
@in = "formData",
required = true,
type = "file"
});
}
}
}
}
在 SwaggerConfig.cs 的 EnableSwagger 配置匿名方法类添加一行注册代码
c.OperationFilter<UploadFilter>();
API 文档展示效果
㈤ 如何在Windows Server2008R2中部署WebAPI
一、安装说明:
1、安装基本信息
电脑软件
在WindowsServer2008(或WindowsServer2008R2)中,单击“开始”-“程序”-“管理工具”-“服务器管理”,或在“运行”中输入命令:servermanager.msc命令打“服务器管理”程序。
在“角色”选项中,单击“添加角色”:
选择服务器角色:Web服务器(IIS)
单击选中“web服务器(IIS))前面的单选框。
在弹出的对话框中,单击“添加必需的功能”:
“添加角色向导”对web服务器进行简单介绍,之后单击“下一步”:
选择角色需要的相关服务之后单击“下一步”:
确认安装选择,之后单击“下一步”:
系统开始安装所选的角色服务,可能需要几十秒可几钟时间:
安装完成,点击关闭即可:
名称:IIS7.0安装
大小:10MB|版本:7.0|类别:系统工具|语言:中文
应用平台:windows server 2008
2、安装介绍
windows server 2008系统中的iis安装,与windows server 2003中的安装明显不再相同了,windows server 2008安装iis,不再需要其他相关组件,不像windows server 2003需要有i386文件,下面来详细介绍一下,windows server 2008安装iis的步骤:
二、安装步骤:
开始--服务器管理--角色--添加角色
点击添加必需的功能
勾选在“Web服务器(IIS)”
点击下一步
下面是选择“角色服务”的,针对你需要的进行相应的选择
然后点击“下一步”开始确认安装
然后等待安装成功
㈥ 使用Web API问题,怎么解决
继承ExceptionFilterAttribute类,重写OnException方法
:ExceptionFilterAttribute{//重写基类的异常处理方法publicoverridevoidOnException(){//业务异常处理if(actionExecutedContext.ExceptionisBusinessException){varbusinessException=(BusinessException)actionExecutedContext.Exception;//自定义业务异常编码intbusinessExceptionCode=businessException.ErrorMessage.Code;//业务异常消息stringbusinessExceptionMsg=businessException.Message;//异常消息Json串varerr=new{errcode=businessExceptionCode,errmsg=businessExceptionMsg};stringerrMsg=JsonConvert.SerializeObject(err);//系统异常码varoResponse=newHttpResponseMessage(HttpStatusCode.InternalServerError);oResponse.Content=newStringContent("");oResponse.Headers.Add("errMsg",errMsg);actionExecutedContext.Response=oResponse;}else{actionExecutedContext.Response=newHttpResponseMessage(HttpStatusCode.InternalServerError);//异常消息Json串varerr=new{errcode=CommonErrorCode.ServiceError,errmsg=actionExecutedContext.Exception.Message};stringerrMsg=JsonConvert.SerializeObject(err);//系统异常码varoResponse=newHttpResponseMessage(HttpStatusCode.InternalServerError);oResponse.Content=newStringContent("");oResponse.Headers.Add("errMsg",errMsg);actionExecutedContext.Response=oResponse;}base.OnException(actionExecutedContext);}}
2.Global.asax中,Application_Start方法中添加过滤器
12345protectedvoidApplication_Start(){GlobalConfiguration.Configure(WebApiConfig.Register);GlobalConfiguration.Configuration.Filters.Add(());}
3.例子:
控制器:
:ApiController{[HttpGet]publicstringGetTest1(){return"value1";}[HttpGet]publicstringGetTest2(){thrownewBusinessException(123456,"业务异常");}[HttpGet]publicstringGetTest3(){thrownewException("系统异常");}}
㈦ 在WebApi中返回一个JSON格式的数据,如何到客户端就变了
转载 在使用Web Api的时候,有时候只想返回JSON;实现这一功能有多种方法,本文提供两种方式,一种传统的,一种作者认为是正确的方法。
JSON in Web API – the formatter based approach
只支持JSON最普遍的做法是:首先清除其他所有的formatters,然后只保留JsonMediaTypeFormatter。
有了HttpConfiguration的实例,你将会很简单的清除所有formatters,然后重新添加JsonMediaTypeFormatter。
实现代码如下:
configuration.Formatters.Clear();
configuration.Formatters.Add(new JsonMediaTypeFormatter());这种方式虽然可以实现功能,但是所有的conent negotiation还是会发生,这就会产生以下额外的开销了。因为,你已经知道要返回的结果了,也只想返回Json,其他的content negotiation都不需要了。
下面的方法可以很好的解决这个问题。
JSON in Web API – the conneg based approach
最好的方法是使用自定义的只返回Json Result的content negotiation代替Web Api中默认的content negotiation。
Conneg通过实现IContentNegotiator的Negotiator方法实现扩展。Negotiator方法返回ContentNegotiationResult(它包装了你选择的headers和formatter)。
下面的方法通过传递一个JsonMediaTypeFormatter给自定义的conneg negotiator,让它一直返回applicaton/json 的content-type以及JsonMediaTypeFormatter。这种方法避免了每次请求都要重新创建一次formatter。
代码如下:
public class JsonContentNegotiator : IContentNegotiator
{
private readonly JsonMediaTypeFormatter _jsonFormatter;
public JsonContentNegotiator(JsonMediaTypeFormatter formatter)
{
_jsonFormatter = formatter;
}
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
var result = new ContentNegotiationResult(_jsonFormatter, new MediaTypeHeaderValue("application/json"));
return result;
}
}接下来,你需要在HttpConfiguration实例上注册你的新的实现机制:
var jsonFormatter = new JsonMediaTypeFormatter();
//optional: set serializer settings here
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
通过替换默认的DefaultContentNegotiator,我们使用我们自定义的JsonContentNegotiator,它只支持Json,而且可以马上返回。
如果你想更深入的了解Content Negotiation的知识,你可以查看作者的这篇文章。
总结
通过使用自定义的JsonContentNegotiator替换系统默认的DefaultContentNegotiator,很好的实现Web Api只返回Json的功能,而且没有额外的开销。
㈧ .net core中如何在执行action前植入一个webapi过滤器
新建一个MyFilter.cs
public class MyFilter: Attribute,IAuthorizationFilter
实现 OnAuthorization 验证
public class MyFilter : Attribute,IActionFilter,IOrderedFilter
实现 OnActionExecuted(请求方法后)和专 OnActionExecuting(请求方法前)
Startup.cs中ConfigureServices方法中 注册属此MyFilter.cs
services.AddMvc(config => config.Filters.Add(new MyFilter()));
㈨ js调用跨域get请求调用webApi 多出个options请求是为什么
我尝试用我的语言描述一下吧:
先说跨域请求的原理,浏览器的安全机制是不允许出现跨域请求的,否则会有很严重的安全问题,解决跨域问题有几种不同的方法,你题目中提到的方法就是通过在Response header中添加Access-Control-Allow-Origin 来让浏览器知道服务器所在的域,对用于访问的域进行了授权。
但是因为这个Header要服务器提供,所以无论如何,请求是要先发出去才能指导是不是允许跨域请求,所以在报跨域错误的时候,虽然报错了,但是请求实际上依然发送到服务器了,只是浏览器看了一眼服务器的返回,然后发觉不行,这个请求返回的header里没有授权,所以浏览器不能用。
这样就带来一个问题,请求会对服务器造成影响,试想一下,要使用XHR跨域提交一个表单,无论返回头里面是否添加了跨域的header,都会提交一个请求到服务器,服务器要进行相应的操作。这种情况其实在一定条件下也是可以接受的,但是如果有更大的安全隐患,就不可以了,所以就需要OPTIONS请求了。
OPTIONS请求就是在符合一定条件下的跨域请求发送之前,浏览器会先发一个OPTIONS请求,问一下服务器,是不是能跨域,如果能,就发真正的请求,如果不能,就不发了。这个的作用就很好理解了。
如上面所说,不是所有的跨域请求都要先发OPTIONS请求的,规范里面规定,以下情况不需要先发一个 OPTIONS请求:
请求类型必须是GET、HEAD、POST中的一种。
请求的Header里面只能包涵一些规范重点Header,以及规范的值,包括:Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save、Data、Viewport-Width、Width
Content-Type的类型必须是以下几种:application/x-www-form-urlencoded、multipart/form-data、text/plain
所以,如果你不希望浏览器多余的发一个OPTIONS请求,只要遵循这个规范就可以了。
但是有时候因为需求原因,也难避免要自定义一些Header。比如,很多JS的AJAX库,都会自定义一个Header,让服务器可以识别出这是否是一个异步请求,这样OPTIONS请求就一定要被先发送了。
顺便说,服务器端也要判断OPTIONS类型的请求,进行一系列操作,不要让OPTIONS请求影响到数据。
更多内容,可以看相关资料和文档:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
https://developer.mozilla.org/en-US/docs/Web/HTTP/Server-Side_Access_Control
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html