RESTサービスではOPTIONSメソッドが必要ですか?

HTTP / 1.1標準によれば、クライアントはOPTIONSメソッドを使用して、リソースに関連付けられたパラメーターまたは要件を決定できます。 サーバーは、ドキュメントを読み取り可能な形式で送信することもできます。 OPTIONS要求への応答には、Allowヘッダーにこのリソースの有効なメソッドのリストが含まれる場合があります。



つまり、このメソッドは、一方でRESTサービスを文書化するための優れたツールであり、他方でHATEOASのアーキテクチャ上の制限に大きく追加される可能性があります。



さて、「HATEOAS」のような怖い言葉から休憩して、自問してみましょう。WebアプリケーションでOPTIONSメソッドを使用することの実用的な利点はありますか?



そのため、少なくとも、OPTIONS要求への応答には、特定のエンドポイント、または単にUriに対して有効なメソッドのリストがAllowヘッダーに含まれている必要があります。



HTTP/1.1 200 OK

Allow: GET,POST,DELETE,OPTIONS

Content-Length: 0







それは何を与えますか?



リソースを投稿して操作できるWebアプリケーションがあるとします。 たとえば、Googleドキュメントのようなものです。 各ユーザーには、ドキュメントに対する特定の権限があります。誰かがそれを読むことができ、誰かがそれを編集でき、誰かがそれを削除できます。



ユーザーインターフェイスを開発するという課題に直面しています。 大まかに言えば、ある時点で、現在のユーザーの権限に応じて、[削除]ボタンを非表示にするか表示するかを決定する必要があります。



サービスから資格情報に関する知識を取得し、クライアント側でロジックを実装できます。 しかし、これは悪いアプローチです。



RESTアーキテクチャでは、クライアントとサーバーはできるだけ独立している必要があります。 ドキュメントを削除するためにユーザーに必要な権限を見つけることは、クライアントの仕事ではありません。 クライアントがそのロジックを実装している場合、サーバーで権限メカニズムを変更すると、クライアントで変更が必要になる可能性が高くなります。



UriによってドキュメントのOPTIONSをリクエストし、有効なメソッドのリストを取得できる場合、タスクは単純に解決されます。Allowヘッダーに「Delete」が含まれている場合、ボタンを表示するか、非表示にする必要があります。



OPTIONSメソッドの実装は簡単です。



 [HttpOptions] [ResponseType(typeof(void))] [Route("Books", Name = "Options")] public IHttpActionResult Options() { HttpContext.Current.Response.AppendHeader("Allow", "GET,OPTIONS"); return Ok(); }
      
      





しかし、各コントローラーと各ルートにメソッドを実装することは困難です。 問題は、この経済を維持することは非常に面倒であるということです。適切なメソッドをコントローラーに追加するのを忘れたり、ルーティングの変更を間違えたりするのは非常に簡単です。



プロセスを自動化することは可能ですか?



ASP.NET Web APIは、自動化のための良い機会を提供します: HTTPメッセージハンドラー



DelegatingHandlerクラスから新しいハンドラーを継承し、 SendAsyncメソッドをオーバーロードし、タスクの継続として新しい機能を追加します。 最初に基本的なルーティングメカニズムを開始するため、これは重要です。 この場合、 要求変数には必要なすべてのプロパティが含まれます。



 protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { return await base.SendAsync(request, cancellationToken).ContinueWith( task => { var response = task.Result; if (request.Method == HttpMethod.Options) { var methods = new ActionSelector(request).GetSupportedMethods(); if (methods != null) { response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; response.Content.Headers.Add("Allow", methods); response.Content.Headers.Add("Allow", "OPTIONS"); } } return response; }, cancellationToken); }
      
      





ActionSelectorクラス 、コンストラクター内の要求に適したコントローラーを見つけようとしています。 コントローラーが見つからない場合、 GetSupportedMethodsメソッドはnullを返します。 IsMethodSupported関数は、指定された名前でアクションメソッドを見つけるために、コンテキスト内の現在の要求を置き換えます。 _apiSelector.SelectActionを呼び出すとこのコンテキストプロパティを変更できるため、finallyブロックはRouteDataを復元します。



 private class ActionSelector { private readonly HttpRequestMessage _request; private readonly HttpControllerContext _context; private readonly ApiControllerActionSelector _apiSelector; private static readonly string[] Methods = { "GET", "PUT", "POST", "PATCH", "DELETE", "HEAD", "TRACE" }; public ActionSelector(HttpRequestMessage request) { try { var configuration = request.GetConfiguration(); var requestContext = request.GetRequestContext(); var controllerDescriptor = new DefaultHttpControllerSelector(configuration) .SelectController(request); _context = new HttpControllerContext { Request = request, RequestContext = requestContext, Configuration = configuration, ControllerDescriptor = controllerDescriptor }; } catch { return; } _request = _context.Request; _apiSelector = new ApiControllerActionSelector(); } public IEnumerable<string> GetSupportedMethods() { return _request == null ? null : Methods.Where(IsMethodSupported); } private bool IsMethodSupported(string method) { _context.Request = new HttpRequestMessage( new HttpMethod(method), _request.RequestUri); var routeData = _context.RouteData; try { return _apiSelector.SelectAction(_context) != null; } catch { return false; } finally { _context.RouteData = routeData; } } }
      
      





最後の手順は、スタートアップコードの構成にハンドラーを追加することです。



 configuration.MessageHandlers.Add(new OptionsHandler());
      
      





すべてが正しく機能するためには、パラメーターのタイプを明示的に指定する必要があります。 Route属性を使用する場合、タイプを直接指定する必要があります。



 [Route("Books/{id:long}", Name = "GetBook")]
      
      





タイプを明示的に指定しない場合、 Books / abcdパスは有効と見なされます。



したがって、サービスでサポートされているすべてのUriのOPTIONS実装ができました。 ただし、これはまだ理想的なソリューションではありません。 説明されているアプローチでは、承認は考慮されていません。 アクセストークンを使用し、サービスへの各呼び出しが現在のプリンシパルの存在を提供する場合、その権利は一切考慮されず、OPTIONSメソッドの値は大幅に減少します。 すべてのユーザーに対して、クライアントは、権限に関係なく、常に有効なHTTPメソッドの同じリストを受け取ります。



これを回避するには、必要に応じて、HttpOptions実装をコントローラーに手動で追加します。 これらのアクションメソッドは、有効なメソッドのリストを作成するときにユーザー権限を確認する必要があります。 この場合、OptionsHandlerはコントローラーからの応答があればそれを使用する必要があります。



ただし、CORSには問題があります。 クロスドメインリクエストの場合、ブラウザはリクエストされたアドレスにOPTIONSリクエストを個別に送信するため、Authorizationヘッダーをインストールする方法はありません。



したがって、許可されたユーザーと許可されていないユーザーの両方に対して、サービスにOPTIONSを実装する必要があります。 最初のケースでは、現在のユーザーの権利を考慮し、2番目のケースでは、すべての有効なメソッドを返す必要があります。



最終コード






著者の翻訳



All Articles