zoukankan      html  css  js  c++  java
  • C#:只支持GET和POST方法的浏览器,如何发送PUT/DELETE请求?RESTful WebAPI如何响应?

    理想的RESTful WebAPI采用面向资源的架构,并使用请求的HTTP方法表示针对目标资源的操作类型。但是理想和现实是有距离的,虽然HTTP协议提供了一系列原生的HTTP方法,但是在具体的网络环境中,很多是不支持的。比如有的浏览器只能发送GET和POST请求,客户端发送的PUT请求也不一定能够被服务器理解。除了客户端和服务器对请求采用的HTTP方法的制约外,像代理(Proxy)、网关(Gateway)等这些中间部件都具有针对HTTP方法的限制。

    我们一般采用HTTP方法重写的方式来解决这个问题。具体来说,WebAPI依然针对标准HTTP方法具有的资源操作语义来定义。客户端发送的请求只能采用网络允许的HTTP方法(一般来说,GET和POST总是被支持的),但是与资源操作方式相匹配的HTTP方法名称会通过一个请求报头发送给服务器。服务器在根据请求实施操作选择之前,它会提取该请求报头携带的HTTP方法,请求自身的HTTP方法会被它重写或者覆盖。按照约定,我们将这个携带覆盖当前请求HTTP方法的报头命名为X-HTTP-Method-Override

    一、ASP.NET WebAPI

    ASP.NET WebAPI采用管道式的设计,这个旨在解决部分HTTP方法在网络环境中不被支持的HTTP方法重写机制可以很容易地通过自定义HttpMessageHandler来实现。具体来说,由于消息处理管道根据表示请求的HttpRequestMessage对象的Method属性确定请求采用的HTTP方法,并且这是一个可读写的属性,如果我们利用注册的HttpMessageHandler根据X-HTTP-Method-Override报头值来设置当前HttpRequestMessage的Method属性,那么管道后续部分将会针对这个覆盖的HTTP方法进行处理。

    为此我们定义了如下一个HttpMethodOverrideHandler类型,它继承自DelegatingHandler。我们在重写的SendAsync方法中实现了对X-HTTP-Method-Override报头的提取和对HTTP方法的重写,最后调用基类的同名方法将处理后的请求传递给后续的HttpMessageHandler。

    public class HttpMethodOverrideHandler: DelegatingHandler
    {
    	protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    	{
    		IEnumerable<string> methodOverrideHeader;
    		if (request.Headers.TryGetValues("X-HTTP-Method-Override", out methodOverrideHeader))
    		{
    			request.Method = new HttpMethod(methodOverrideHeader.First());
    		}
    		return base.SendAsync(request, cancellationToken);
    	}
    }
    

    我们在一个空ASP.NET WebAPI应用中定义了如下一个继承自ApiController的DemoController,并在其中定义了4个用于返回自身方法名称的Action方法(Get、Post、Put和Delete)。按照ASP.NET WebAPI默认提供的HTTP方法与Action方法名称之间的映射机制,这4个Action方法支持HTTP方法与自身的方法名称一致。

    public class DemoController : ApiController
    {
    	public string Get()
    	{
    		return "Get";
    	}
    
    	public string Post()
    	{
    		return "Post";
    	}
    
    	public string Put()
    	{
    		return "Put";
    	}
    
    	public string Delete()
    	{
    		return "Delete";
    	}
    }
    

    在Global.asax文件中,我们采用如下的代码将一个HttpMethodOverrideHandler对象注册到ASP.NET WebAPI的消息处理管道中。我们采用IIS Express作为宿主,并将采用的端口固定为“3721”。

    public class WebApiApplication : System.Web.HttpApplication
    {
    	protected void Application_Start()
    	{
    		GlobalConfiguration.Configuration.MessageHandlers.Add(new HttpMethodOverrideHandler());
    		//其他操作
    	}
    }
    

    我们创建一个控制台应用作为调用WebAPI的客户端程序。如下面的代码片断所示,我们定义了一个辅助方法InvokeWebApi根据提供的HttpClient对象和请求采用的HTTP方法进行WebAPI的调用。在该方法中,我们根据指定的HTTP方法创建了一个指向目标WebAPI的HttpRequestMessage对象,并将其作为参数调用HttpClient对象的SendAsync方法对目标WebAPI发起调用。WebAPI成功调用后会得到最终被执行的目标Action方法的名称,我们将它连同当前请求采用的HTTP方法和X-HTTP-Method-Override报头值打印在控制台上。

    class Program
    {
    	static void Main(string[] args)
    	{
    		HttpClient httpClient1 = new HttpClient();
    		HttpClient httpClient2 = new HttpClient();
    		HttpClient httpClient3 = new HttpClient();
    		HttpClient httpClient4 = new HttpClient();
    
    		httpClient3.DefaultRequestHeaders.Add("X-HTTP-Method-Override", "PUT");
    		httpClient4.DefaultRequestHeaders.Add("X-HTTP-Method-Override", "DELETE");
    
    		Console.WriteLine("{0,-7}{1,-24}{2,-6}", "Method", "X-HTTP-Method-Override", "Action");
    		InvokeWebApi(httpClient1, HttpMethod.Get);
    		InvokeWebApi(httpClient2, HttpMethod.Post);
    		InvokeWebApi(httpClient3, HttpMethod.Post);
    		InvokeWebApi(httpClient4, HttpMethod.Post);
    
    		Console.Read();
    	}
    
    	async static void InvokeWebApi(HttpClient httpClient, HttpMethod method)
    	{
    		string requestUri = "http://localhost:3721/api/demo";
    		HttpRequestMessage request = new HttpRequestMessage(method, requestUri);
    		HttpResponseMessage response = await httpClient.SendAsync(request);
    		IEnumerable<string> methodsOverride;
    		httpClient.DefaultRequestHeaders.TryGetValues("X-HTTP-Method-Override", out methodsOverride);
    		string actionName = response.Content.ReadAsStringAsync().Result;
    		string methodOverride = methodsOverride == null ? "N/A" : methodsOverride.First();
    		Console.WriteLine("{0,-7}{1,-24}{2,-6}", method, methodOverride, actionName.Trim('"'));
    	}
    }
    

    在Main方法中,我们创建了4个HttpClient对象(httpClient1、httpClient2、httpClient3和httpClient4),并将X-HTTP-Method-Override报头添加到httpClient3和httpClient4的默认报头集合中,指定的HTTP方法分别是“PUT”和“DELETE”。我们将这4个HttpClient对象作为参数调用辅助方法InvokeWebApi对目标WebAPI发起4次调用,除了第1次(由于InvokeWebApi是一个异步方法,代码中的第一次调用并不意味着它首先被执行,更不能确保针对它的WebAPI调用率先完成)采用GET请求之外,其余请求均采用POST方法。

    在启动WebAPI宿主程序后运行客户端控制台应用,我们会得到如下所示的输出结果。我们可以清楚地看到在请求不具有X-HTTP-Method-Override报头的情况下,执行的Action方法取决于请求采用的HTTP方法。反之,如果请求通过X-HTTP-Method-Override报头携带了相应的HTTP方法,它将用于目标Action方法的选择。这一切均是HttpMethodOverrideHandler这个自定义HttpMessageHandler的功劳。

    Method X-HTTP-Method-Override  Action
    POST   PUT                     Put
    GET    N/A                     Get
    POST   N/A                     Post
    POST   DELETE                  Delete
    

    二、ASP.NET Core WebAPI

    通过注册中间件来解决这个问题

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
    	app.UseHttpMethodOverride();
    }
    
    public static class HttpMethodOverrideExtensions
    {
    	/// <summary>
    	/// Allows incoming POST request to override method type with type specified in header.
    	/// </summary>
    	/// <param name="builder">The <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" /> instance this method extends.</param>
    	public static IApplicationBuilder UseHttpMethodOverride(this IApplicationBuilder builder)
    	{
    		if (builder == null)
    			throw new ArgumentNullException(nameof (builder));
    		return builder.UseMiddleware<HttpMethodOverrideMiddleware>(Array.Empty<object>());
    	}
    }
    
    
    public class HttpMethodOverrideMiddleware
    {
    	private const string xHttpMethodOverride = "X-Http-Method-Override";
    	private readonly RequestDelegate _next;
    	private readonly HttpMethodOverrideOptions _options;
    
    	public HttpMethodOverrideMiddleware(RequestDelegate next, IOptions<HttpMethodOverrideOptions> options)
    	{
    		if (next == null)
    			throw new ArgumentNullException(nameof (next));
    		if (options == null)
    			throw new ArgumentNullException(nameof (options));
    		this._next = next;
    		this._options = options.Value;
    	}
    
    	public async Task Invoke(HttpContext context)
    	{
    		if (string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
    		{
    			if (this._options.FormFieldName != null)
    			{
    				if (context.Request.HasFormContentType)
    				{
    					StringValues stringValues = (await context.Request.ReadFormAsync(new CancellationToken()))[this._options.FormFieldName];
    					if (!string.IsNullOrEmpty((string) stringValues))
    						context.Request.Method = (string) stringValues;
    				}
    			}
    			else
    			{
    				StringValues header = context.Request.Headers["X-Http-Method-Override"];
    				if (!string.IsNullOrEmpty((string) header))
    					context.Request.Method = (string) header;
    			}
    		}
    		await this._next(context);
    	}
    }
    
  • 相关阅读:
    Akka(33): Http:Marshalling,to Json
    Akka(32): Http:High-Level-Api,Route exception handling
    Akka(31): Http:High-Level-Api,Route rejection handling
    Akka(30): Http:High-Level-Api,Routing DSL
    Akka(29): Http:Server-Side-Api,Low-Level-Api
    Akka(28): Http:About Akka-Http
    Akka(27): Stream:Use case-Connecting Slick-dbStream & Scalaz-stream-fs2
    Akka(26): Stream:异常处理-Exception handling
    Akka(25): Stream:对接外部系统-Integration
    Akka(24): Stream:从外部系统控制数据流-control live stream from external system
  • 原文地址:https://www.cnblogs.com/zhaoshujie/p/14189684.html
Copyright © 2011-2022 走看看