RESTful API 設計へのアプローチ。 HTTP リクエストの種類と REST 哲学 Restful API の例

05.01.2024 携帯電話会社

この記事では、RESTful API の設計における私の経験を共有します。具体的な例を使用して、少なくとも単純なサービスを美しく作成する方法を示します。 また、API とは何か、なぜそれが必要なのか、REST の基本についても説明します。REST の実装方法についても説明します。 このテクノロジーに依存する、または依存しない Web の基本的な実践について触れてみましょう。 また、最小限の労力で優れたドキュメントを作成する方法を学び、RESTful API にどのようなバージョン管理方法があるのか​​を確認します。

パート 1. 理論

ご存知のとおり、API はアプリケーション プログラミング インターフェイスであり、1 つのアプリケーションまたはコンポーネントが他のアプリケーションまたはコンポーネントと対話するための一連のルールとメカニズムです。

優れた API が重要なのはなぜですか?

  • 使いやすさとサポート。 優れた API は使用と保守が簡単です。
  • 開発者間の良好な変換。 誰もがあなたの API を気に入ってくれれば、新しいクライアントやユーザーがあなたのところにやってくるでしょう。
  • サービスの人気を高める。 API ユーザーが多いほど、サービスの人気が高くなります。
  • コンポーネントの絶縁性の向上。 API 構造が優れているほど、コンポーネントの分離も向上します。
  • 商品の好印象。 API は開発者 UI のようなものです。 これは、開発者が製品に出会うときに最初に注目する点です。 API が曲がっている場合、技術専門家として、企業がサードパーティから何かを購入してそのような製品を使用することはお勧めできません。

では、どのような種類の API があるのか​​を見てみましょう。

実装方法ごとの API の種類:

  • WebサービスAPI
    • XML-RPC と JSON-RPC
  • WebSocket API
  • ライブラリベースのAPI
    • JavaScript
  • クラスベースの API
    • C# API
  • OSの機能とルーチン
    • ファイルシステムへのアクセス
    • ユーザーインターフェースへのアクセス
  • オブジェクトリモート API
    • コルバ
    • .Netリモート処理
  • ハードウェアAPI
    • ビデオアクセラレーション (OpenCL...)
    • ハード・ディスク・ドライブ
    • PCIバス


ご覧のとおり、Web API には XML-RPC、JSON-RPC、SOAP、REST が含まれます。

RPC (リモート プロシージャ コール) は非常に古い概念であり、古代、中期、最新のプロトコルを組み合わせて、別のアプリケーションのメソッドを呼び出すことができます。 XML-RPC は、XML の出現直後の 1998 年に登場したプロトコルです。 これは当初 Microsoft によってサポートされていましたが、すぐに Microsoft は SOAP に完全に切り替えたため、.Net Framework ではこのプロトコルをサポートするクラスが見つかりません。 それにもかかわらず、XML-RPC は今日に至るまでさまざまな言語 (特に PHP) で生き続けています。明らかに、そのシンプルさで開発者から愛されています。

SOAP も Microsoft の努力により 1998 年に登場しました。 ソフトウェア界の革命として発表されました。 すべてがマイクロソフトの計画通りに進んだというわけではありません。プロトコルの複雑さと重さのため、大量の批判がありました。 同時に、SOAP が真の進歩であると考える人もいました。 このプロトコルは進化を続け、数十の新しい仕様を生み出し、2003 年に W3C が SOAP 1.2 を推奨として承認しましたが、これは現在も最新です。 SOAP ファミリは、WS-Addressing、WS-Enumeration、WS-Eventing、WS-Transfer、WS-Trust、WS-Federation、Web Single Sign-On など、優れたものであることが判明しました。

その後、当然のことながら、非常にシンプルなアプローチ、REST が登場しました。 REST という略語は、Representational State Transfer (表現状態の転送) の略で、「表現状態の転送」、またはクライアントにとって便利な形式でのデータのプレゼンテーションをより適切に表現します。 「REST」という用語は、2000 年に Roy Fielding によって造られました。REST の基本的な考え方は、サービスを呼び出すたびにクライアント アプリケーションが新しい状態になるということです。 本質的に、REST はプロトコルや標準ではなく、API 設計のアプローチ、アーキテクチャ スタイルです。

RESTの原則は何ですか?

  • クライアントサーバーアーキテクチャ- これなしでは REST は考えられません。
  • あらゆるデータはリソースです.
  • どのリソースにも ID がありますからデータを取得できます。
  • リソースは相互にリンクできます- この場合、ID または、より多くの場合推奨されているように、リンクが応答の一部として送信されます。 しかし、リンクを簡単に使用できるほどすべてがうまくいくまでには至っていません。
  • 標準のHTTPメソッドが使用されます(GET、POST、PUT、DELETE) - これらはすでにプロトコルに含まれているため、それらを使用してサーバーと対話するためのフレームワークを構築できます。
  • サーバーは状態を保存しません- これは、サーバーが呼び出しを別の呼び出しから分離せず、すべてのセッションをメモリに保存しないことを意味します。 サービスを実装する何らかのスケーラブルなクラウドやサーバー ファームがある場合、すべてのノード間でこれらのサービスの状態の一貫性を確保する必要はありません。 これにより、スケーリングが大幅に簡素化され、別のノードを追加するときに、すべてが正常に動作します。

なぜRESTが良いのでしょうか?

  • とても簡単です!
  • 既存の標準を再利用します、非常に長い間使用されており、多くのデバイスで使用されています。
  • RESTはHTTPに基づいています=> すべてのグッズが入手可能です:
    • キャッシング。
    • スケーリング。
    • 間接費を最小限に抑えます。
    • 標準エラーコード。
  • 非常に良好な普及率(IoT デバイスでさえ、HTTP の実行方法をすでに知っています)。
ベスト ソリューション (テクノロジーに依存しない)
特定の実装に関係しない、現代世界における最良のソリューションは何ですか? これらのソリューションを使用することを強くお勧めします。
  • どこでもSSL- サービスで最も重要なこと。SSL がなければ認可と認証は無意味だからです。
  • ドキュメントとバージョン管理サービス - 仕事の初日から。
  • POST メソッドと PUT メソッドは返さなければなりません変更または作成したオブジェクトを返します。これにより、サービスへのアクセスにかかる時間が半分に短縮されます。
  • フィルタリング、並べ替え、ページングをサポート- これが標準化され、すぐに機能することが非常に望ましいです。
  • メディアタイプのサポート。 MediaType は、コンテンツの形式をサーバーに伝える方法です。 標準的な Web API 実装を使用してブラウザからアクセスすると、API は XML を提供し、Postman 経由でアクセスすると JSON を返します。
  • Prettyprint と gzip。 リクエストを最小化したり、JSON (サーバーからの応答) をコンパクトにしたりしないでください。 prettyprint のオーバーヘッドはパーセント単位で表示されます。これは、メッセージの合計サイズに対してタブがどれだけの量を占めているかを見るとわかります。 タブを削除してすべてを 1 行で送信すると、デバッグが疲れてしまいます。 gzip に関しては、何倍ものメリットがあります。 特に、prettyprint と gzip の両方を使用することを強くお勧めします。
  • 標準のキャッシュ メカニズム (ETag) と Last-Modified (最終変更日) のみを使用します。- これら 2 つのパラメータは、サーバーがコンテンツを更新する必要がないことをクライアントに理解させるのに十分です。 ここで独自の何かを発明することに意味はありません。
  • 常に標準の HTTP エラー コードを使用する。 そうしないと、いつか、クライアントがプロジェクトのエラー 419 を何らかの理由で思いついたとおりに解釈する必要があると判断した理由を誰かに説明しなければならないことになります。 これは不便で醜いものです。クライアントはこれに感謝することはありません。
HTTPメソッドのプロパティ

今日は GET、POST、PUT、DELETE についてのみ説明します。

表に示されているその他の機能について簡単に説明すると、OPTIONS - セキュリティ設定の取得、HEAD - メッセージ本文のないヘッダーの取得、PATCH - コンテンツの部分的な変更です。

ご覧のとおり、表に示されている POST を除くすべてのメソッドは冪等です。 冪等性とは、サービスへの同じ呼び出しを複数回実行できる機能であり、応答は毎回同じになります。 つまり、このアクションを実行した理由や回数は関係ありません。 オブジェクトの変更 (PUT) アクションを実行していてエラーを受け取ったとします。 何が原因で、いつ、その物体が変化したかどうかもわかりません。 ただし、冪等性のおかげで、このアクションを再度実行することが保証されるため、クライアントはデータの整合性について安心できます。

「安全」とは、サーバーにアクセスしてもコンテンツが変更されないことを意味します。 したがって、GET は何度も呼び出すことができますが、内容は変更されません。 GET はキャッシュできるため、コンテンツを変更する場合は、キャッシュと格闘し、いくつかのトリッキーなパラメーターを考案する必要があります。

パート 2. 実践
テクノロジーの選択

REST がどのように機能するかを理解したので、RESTful API (REST 原則に従うサービス) の作成を開始できます。 まずはテクノロジーの選択から始めましょう。

最初のオプション - WCF サービス。 このテクノロジを使用したことがある人は、通常、そのテクノロジに戻りたくありません。これには重大な欠点があり、利点はほとんどありません。
– webHttpBinding のみ (残りはなぜですか?..)。
– HTTP Get と POST のみがサポートされます (それだけです)。
+ さまざまな形式の XML、JSON、ATOM。

2 番目のオプション - ウェブAPI。 この場合、利点は明らかです。
+ 非常にシンプルです。
+ オープンソース。
+ すべての HTTP 機能。
+ すべての MVC 機能。
+軽量。
+ 多数のフォーマットもサポートしています。

当然、Web APIを選択します。 次に、Web API に適したホスティングを選択しましょう。

Web API のホスティングの選択

ここにはかなりの数のオプションがあります:

  • ASP.NET MVC (古き良きもの)。
  • Azure(クラウド構造)。
  • OWIN - .NET 用のオープン Web インターフェイス (Microsoft による新規開発)。
  • 自己ホスト型
OWI
オーウィンプラットフォームやライブラリではなく、サーバー実装から Web アプリケーションの強い結合を取り除く仕様です。 これにより、OWIN をサポートする任意のプラットフォーム上でアプリケーションを変更せずに実行できるようになります。 実際、仕様は非常に単純です。これはパラメータとその値の単なる「辞書」です。 基本的なパラメータは仕様で定義されています。

OWIN は非常に単純な構造に要約されます。

この図から、「パラメータ - 値」リストで構成される非常に単純な「辞書」をサポートするサーバーを備えたホストがあることがわかります。 このサーバーに接続するすべてのモジュールはこのように構成されます。 このコントラクトをサポートするサーバーは、特定のプラットフォームに関連付けられており、これらすべてのパラメーターを認識し、それに応じてインフラストラクチャを初期化できます。 OWIN で動作するサービスを作成すると、コードを変更せずに自由にプラットフォーム間で転送し、同じものを他の OS で使用できることがわかりました。

カタナ- Microsoft の OWIN の実装。 これにより、IIS で OWIN アセンブリをホストできるようになります。 非常にシンプルですが、次のようになります。

名前空間 RestApiDemo ( public class Startup ( public void Configuration(IAppBuilder app) ( var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); app.UseWebApi(config); ) ) )

スタートアップがどのクラスであるかを指定します。 これは、IIS によって選択される単純な DLL です。 コンフィギュレーターが呼び出されます。 このコードはすべてを機能させるのに十分です。

インターフェースの設計
次に、インターフェースを設計して、すべてがどのように見えるか、どのようなルールに従うべきかを見てみましょう。 REST のすべてのリソースは名詞であり、触れたり触れたりできるものです。

例として、駅での電車の時刻表を含む単純なモデルを考えてみましょう。 単純な REST リクエストの例を次に示します。

  • ルート (独立した) API エンティティ:
    • GET /stations - すべてのステーションを取得します。
    • GET /stations/123 - ID = 123 のステーションに関する情報を取得します。
    • GET /trains - すべての列車のスケジュール。
  • 依存する (ルートからの) エンティティ:
    • GET /stations/555/payments - 駅 555 から出発する列車。
コントローラ

ステーションができたので、次は簡単なコントローラーを作成する必要があります。

パブリック クラス RailwayStationsController: ApiController ( public IEnumerable GetAll() ( return testData; ) RailwayStationModel testData = /*ここで初期化*/ )

これは属性ベースのルーティングです。 ここでは、コントローラーの名前を指定し、リスト (この場合はランダムなテスト データ) を要求します。

OData (www.odata.org)

ここで、クライアント上に必要以上のデータがあると想像してください (100 を超えるデータを保持することはほとんど意味がありません)。 同時に、もちろん、自分でページネーションを書きたくないでしょう。 代わりに、Web API でサポートされている軽量バージョンの OData を使用する簡単な方法があります。

パブリック クラス RailwayStationsController: ApiController ( public IQueryable GetAll() ( return testData.AsQueryable(); ) RailwayStationModel testData = /*ここで初期化*/ )

IQueryable を使用すると、クライアント側でいくつかのシンプルだが効果的なフィルタリングおよびデータ操作メカニズムを使用できます。 必要な作業は、NuGet から OData アセンブリに接続し、EnableQuery を指定して iQueryable インターフェイスを返すことだけです。

この軽量バージョンと完全バージョンの主な違いは、メタデータを返すコントローラーがないことです。 本格的な OData は、答えを少し変更し (返そうとしているモデルを特別なラッパーでラップします)、それに与えたいオブジェクトの関連ツリーを返すことができます。 また、OData の軽量バージョンでは、結合やカウントなどのことはできません。

クエリパラメータ

できることは次のとおりです。

  • $filter - 名前などでフィルターします。 すべての関数は OData Web サイトで参照できます。これらは非常に便利で、サンプルを大幅に制限できます。
  • $select は非常に重要です。 大規模なコレクションがあり、すべてのオブジェクトが厚いが、同時に、表示する ID と名前以外には何もない、ある種のドロップダウンを作成する必要がある場合、この関数が役に立ちます。サーバーとの対話が簡素化され、高速化されます。
  • $orderby - 並べ替え。
  • $top および $skip - サンプルの制限。

これだけあれば、自分で車輪を再発明する必要がなくなります。 Breeze のような標準の JS ライブラリでこれらすべてを行うことができます。

EnableQuery 属性
実際、OData は非常に簡単に自分自身を攻撃してしまう種類のものです。 テーブルに何百万ものレコードがあり、それらをサーバーからクライアントにプルする必要がある場合、それは困難であり、そのようなリクエストが多数ある場合、それは完全に致命的になります。

このような場合のために、EnableQuery 属性 (上記のコードを参照) には、必要以上の行を与えない、結合や算術演算を許可しないなど、多くのことを制限できるパラメーターのセットがあります。同時に、自分で書く必要はありません。

  • 許可された算術演算子
  • 許可された機能
  • 許可された論理演算子
  • 許可されたオーダーバイプロパティ
  • 許可されたクエリオプション
  • EnableConstantParameterization
  • 安定した注文を確保する
  • ハンドルNull伝播
  • MaxAnyAllExpressionDepth
  • 最大拡張深度
  • 最大ノード数
  • MaxOrderByNodeCount
  • マックススキップ
  • マックストップ
  • ページサイズ

依存コントローラ
したがって、最も単純な REST リクエストの例を次に示します。

  • GET /stations – すべてのステーションを取得します
  • GET /trains – すべての列車の時刻表
  • GET /駅/555/到着
  • GET /駅/555/出発

ステーション 555 があり、そのすべての出発と到着を取得したいとします。 明らかに、ここではステーション エンティティに依存するエンティティを使用する必要があります。 しかし、コントローラーでこれを行うにはどうすればよいでしょうか? これらすべてをルーティング属性で実行し、それらを 1 つのクラスにまとめれば、このような例では問題がないことは明らかです。 しかし、ネストされたエンティティが 12 個もあり、その深さがさらに深くなると、すべてがサポートされない形式になってしまいます。

そして、簡単な解決策があります。コントローラーのルーティング属性に変数を作成できます。

パブリック クラス TrainsFromController: TrainsController ( public IQueryable GetAll(int station) ( return GetAllTrips().Where(x =>

したがって、すべての依存エンティティを別のコントローラーに配置します。 彼らは別々に住んでいるので、何人いるかはまったく重要ではありません。 Web API の観点からは、これらは別のコントローラーによって認識されます。URL ではそのように見えても、システム自体はそれらが依存していることを認識していないようです。

唯一の問題は、ここには「駅」があり、その前にも「駅」があったということです。 ある場所で何かを変更し、別の場所では何も変更しない場合、何も機能しません。 ただし、簡単な解決策があります。 ルーティング定数:

パブリック静的クラス TrainsFromControllerRoutes ( public const string BasePrefix = RailwayStationsControllerRoutes.BasePrefix + "/(station:int)/payments"; public const string GetById = "(id:int)"; )

依存するコントローラーは次のようになります。

パブリック クラス TrainsFromController: TrainsController ( public IQueryable GetAll(int station) ( return GetAll().Where(x => x.OriginRailwayStationId == station); ) )

依存するコントローラーに対して簡単な操作を行うことができます。自分でルートを選択して計算するだけなので、間違うことはありません。 さらに、これらはテストにも役立ちます。 テストを作成してそれを管理し、毎回何百万ものテストをすべて実行して、相対 URL が指定されているすべての行を修正するのではなく、これらの定数を使用することもできます。 これらを変更すると、データはあらゆる場所で変更されます。 とても快適です。

クラッド
以上、最も単純な GET 操作がどのようなものであるかを説明しました。 単一の GET を実行する方法は誰もが理解しています。 しかし、これに加えて、さらに 3 つの操作について説明する必要があります。
  • POST – 新しいエンティティを作成します
    • POST /Stations – エンティティ全体の JSON 説明。 このアクションにより、新しいエンティティがコレクションに追加されます。
    • 作成されたエンティティを返します (第 1 に、サーバーへの二重送信を避けるため、第 2 に、必要に応じて、このオブジェクトで計算され、クライアントで必要なパラメーターをサーバーから返すため)。
  • PUT - エンティティを変更する
    • PUT /Stations/12 - ID = 12 のエンティティを変更します。パラメーターに含まれる JSON は上書きされます。
    • 変更されたエンティティを返します。 何度も適用されたパスは、システムを同じ状態に導く必要があります。
  • 消去
    • DELETE /Stations/12 - ID = 12 のエンティティを削除します。

その他の CRUD の例:

  • POST /ステーション - ステーションを追加します。
  • POST /駅/1/出発 - 駅 1 からの出発に関する情報を追加します。
  • DELETE /Stations/1/deliverys/14 - ステーション 1 からの出発記録を削除します。
  • GET /駅/33/出発/10/チケット - 駅 33 からの出発 10 に販売されるチケットのリスト。

ノードは必ず何らかの実体、つまり「触れる」ことができるもの (切符、電車、電車の出発の事実など) であることを理解することが重要です。

アンチパターン
してはいけないことの例を次に示します。
  • GET /駅/?op=出発&電車=11
    ここで、クエリ文字列はデータの送信だけでなく、アクションにも使用されます。
  • GET /ステーション/すべて削除
    これは実際の例です。 ここでは、このアドレスに対して GET を実行します。理論的には、コレクションからすべてのエンティティが削除されるはずです。その結果、キャッシュが原因で非常に予測不可能な動作をします。
  • POST /GetUserActivity
    これは実際には GET であり、POST として記述されます。 本体にリクエスト パラメーターがあるため POST が必要でしたが、GET は本体に何も渡すことができません。GET はクエリ文字列にのみ渡すことができます。 GET は標準では body さえサポートしていません。
  • POST /ステーション/作成
    ここではアクションが URL の一部として指定されていますが、これは冗長です。
APIの設計
人々に提供したい API があり、ドメイン モデルがあるとします。 API エンティティはドメイン モデルにどのように関連していますか? はい、実際には、それらは何のつながりもありません。 これは必要ありません。API で行うことは内部ドメイン モデルとは何の関係もありません。

CRUD ではない場合、API をどのように設計すればよいのかという疑問が生じるかもしれません。 これを行うために、すべてのアクションを変更コマンドとして記録します。 コマンドの保存、読み取り、削除、GET、そのコマンドのステータスの確認を行います。 コマンド コレクションから GET - 特定のエンティティに対して送信したすべてのコマンドのリストを取得します。

ドメインモデル
ドメイン モデルとオブジェクトの関係について説明します。 この例では、ホテル (ホテル) があり、予約 (予約)、部屋 (ルーム)、およびそれらに関連付けられたデバイス (デバイス) があります。 私たちのプロジェクトでは、これにより、これらのデバイスを通じて部屋を制御できるようになりました。

しかし、ここに問題があります。デバイスは独自の生活を営む実体であり、ホテルからどのように分離するかが明確ではありません。 しかし、ホテルとデバイスを分離するのは実際には非常に簡単です。DDD が役に立ちます。 まず最初に、ドメイン領域の境界がどこにあるのか、またシステムの一貫性を担うエンティティの境界がどこにあるのかを把握する必要があります。

境界付きコンテキスト (BC)
境界付けられたコンテキスト (分離されたサブドメイン) - 実際には、互いに独立しており、完全に独立したモデル (異なる) を持つオブジェクトのセット。 この例では、ホテルとデバイスを 2 つの異なる BC に分離できます。これらは相互接続されていませんが、重複は存在します。 追加のエンティティ (AttachedDevice) が表示されます。

ここでは同じデバイスを異なる表現で表現していますが、それは何の問題もありません。

DDD では、集約ルートはすべての子を所有するエンティティです。 これが私たちの木の頂上(ホテル)です。 他のすべてのものを引っ張ることができる何か。 しかし、AttachedDevice をそのように受け取ることはできません。AttachedDevice は存在せず、意味がありません。 同様に、ルーム クラスと予約クラスはホテルから分離されているため、意味がありません。 したがって、これらすべてのクラスへのアクセスは、ルート エンティティ経由 (この場合はホテル経由) のみになります。 デバイスは最初から異なるルートであり、異なるフィールドのセットを持つ異なるツリーです。

したがって、2 つの異なるドメインで 1 つのエンティティがプレイしていることを理解している場合は、それを切り取るだけで、マスター エンティティの単なる投影になります。 AttachedDevice には、たとえば部屋番号のフィールドがありますが、Device ではそのようなフィールドは必要ありません。

そしてここ クエリの例、そのようなドメイン モデルではど​​のように見えるか:

  • PUT /hotels/555/rooms/105/attachedDevices - 接続されているデバイスのコレクション全体を新しいものに置き換えます。
  • POST /hotels/555/rooms/105/attachedDevices - 別のデバイスを接続します。
  • DELETE /hotels/12 - ID=12 のホテルの説明を削除します。
  • POST /hotels/123/reservations - ホテル ID=123 で新しい予約を作成します。
CQRS - コマンド クエリの責任の分離

このアーキテクチャについては今は説明しませんが、その動作原理について簡単に説明したいと思います。 CQRS アーキテクチャは、データ ストリームの分離に基づいています。

ユーザーがドメインを変更するコマンドをサーバーに送信するスレッドが 1 つあります。 ただし、変更が実際に発生するという事実はありません。ユーザーはデータを直接操作しません。 したがって、ユーザーがエンティティを変更するコマンドを送信すると、サーバーはそれを処理し、読み取り用に最適化されたある種のモデルに入れます。つまり、UI がそれを読み取ります。

このアプローチにより、REST 原則に非常に簡単に従うことができます。 コマンドがある場合は、「コマンドのリスト」エンティティが存在します。

PUT なしの REST
単純な CRUD の世界では、PUT はオブジェクトを変更するものです。 しかし、CQRS の原則に厳密に従い、すべてをコマンドで実行すると、オブジェクトを変更できないため、PUT が消えてしまいます。 代わりに、オブジェクトに変更コマンドを送信することしかできません。 同時に、実行状況の追跡、コマンドのキャンセル (DELETE)、変更履歴の保存が簡単に行え、ユーザーは何も変更せず、単に意図を報告するだけです。

PUT を使用しない REST パラダイムはまだ議論の余地があり、十分にテストされていませんが、場合によっては非常に適切に適用できます。

きめの細かい VS きめの粗い
大規模なサービス、大きなオブジェクトを作成していると想像してください。 ここでは、粒度の細かい API と粒度の粗い API (「粒度の細かい」API と「粒度の粗い」API) の 2 つのアプローチがあります。

きめ細かい API:

  • 小物がたくさん。
  • ビジネス ロジックはクライアント側に送られます。
  • オブジェクトがどのように接続されているかを知る必要があります。

粗粒度の API:

  • さらにエンティティを作成します。
  • ローカルな変更を行うのは困難です。
    • POST /blogs/(id)/likes.
  • クライアント上の状態を監視する必要があります。
  • 大きなオブジェクトは部分的に保存できません。

まず、きめ細かい API を設計することをお勧めします。オブジェクトを作成するたびに、それをサーバーに送信します。 クライアント側のアクションごとに、サーバーへの呼び出しが行われます。 ただし、大きなエンティティを扱うよりも小さなエンティティを扱う方が簡単です。大きなエンティティを作成すると、後でそれを切り取るのが難しくなり、小さな変更を加えてそこから独立した部分を取り出すのが難しくなります。 したがって、小さなエンティティから始めて、徐々に拡大することをお勧めします。

バージョン番号付け
たまたまですが、私たちの業界は契約に対して非常にリラックスした態度をとっています。 何らかの理由で、人々は API を作成すれば、それを使ってやりたいことが何でもできると信じています。 しかし、そうではありません。 API を作成して少なくとも 1 つの取引相手に提供したことがある場合、すべてがバージョン 1.0 になります。 変更を加えるとバージョンが変更されるはずです。 結局のところ、人々は自分のコードをあなたが提供したバージョンにリンクするでしょう。

最後のプロジェクトでは、API がクライアントに提供されていたため、API を数回ロールバックする必要がありました。エラー コードを変更しましたが、クライアントはすでに古いコードに慣れていました。

Web API バージョンに番号を付けるための現在知られているオプションは何ですか?

最も簡単な方法は、URL でバージョンを指定することです。

自分で何もする必要がない場合の既製のオプションは次のとおりです。

ここに、既製の興味深いオプションが 1 つあります。

これは単なる制約付きの属性ルーティングです。本格的なオブジェクトを実行したことがあれば、おそらく制約も実行したことがあるでしょう。 この属性のバージョン番号に基づいて、単純に制約を実装しました。 したがって、バージョンが異なる同じ属性、ただしコントローラ名は同じを 2 つの異なるクラスに割り当て、異なるバージョンを指定します。 すべてが箱から出してすぐに機能します...

VersionedRoute("v2/値", バージョン = 2)]
config.ConfigureVersioning(
versioningHeaderName: "バージョン"、vesioningMediaTypes: null);
config.ConfigureVersioning(
versioningHeaderName: null、
vesioningMediaTypes: new("application/vnd.model"));

ドキュメンテーション
Swagger という、さまざまな用途に使用できる素晴らしいオープンソースがあります。 特別なアダプター、スワッシュバックルを使用して使用します。
  • http://swagger.io/
  • https://github.com/domaindrivendev/Swashbuckle
スワッシュバックル: httpConfiguration .EnableSwagger(c => c.SingleApiVersion("v1", ”デモ API")) .EnableSwaggerUi(); public static void RegisterSwagger(this HttpConfiguration config) ( config.EnableSwagger(c => ( c.SingleApiVersion("v1) ", "DotNextRZD.PublicAPI") .Description("DotNextRZD パブリック API") .TermsOfService("利用規約") .Contact(cc => cc .Name("Vyacheslav Mikhaylov") .Url("http://www .dotnextrzd.com") .Email(" [メールで保護されています]")).License(lc => lc.Name("License").Url("http://tempuri.org/license")); c.IncludeXmlComments(GetXmlCommentFile()); c.GroupActionsBy(GetControllerGroupingKey) ; c.OrderActionGroupsBy(new CustomActionNameComparer()); c.CustomProvider(p => new CustomSwaggerProvider(config, p)); EnableSwaggerUi(c => ( c.InjectStylesheet(Assembly.GetExecutingAssembly(), "DotNextRZD.PublicApi.Swagger. Styles.SwaggerCustom.css"); )); ) )

タグ: タグを追加する

インターネットのロシア語部分では、SOAP および XML-RPC に基づく Web サービスに特化した記事が多数ありますが、何らかの理由で、非常に注目に値する (しかしあまり一般的ではない) REST アーキテクチャについてはほとんど記事がありません。

この記事では、このアーキテクチャの基本、機能、使用例について説明します。

レストとは何ですか

REST (Representational state transfer) は、World Wide Web などの分散システム用のソフトウェア アーキテクチャのスタイルであり、通常は Web サービスの構築に使用されます。 REST という用語は、HTTP プロトコルの作成者の 1 人である Roy Fielding によって 2000 年に造られました。 REST をサポートするシステムは RESTful システムと呼ばれます。

一般に、REST は、追加の内部レイヤーを使用しない、非常にシンプルな情報管理インターフェイスです。 各情報は、URL などのグローバル識別子によって一意に識別されます。 各 URL には厳密に定義された形式があります。

そして今、同じことがより明確になっています。

追加の内部層がないということは、データがデータ自体と同じ形式で転送されることを意味します。 それらの。 SOAP や XML-RPC のようにデータを XML でラップしたり、Flash のように AMF を使用したりしません。 データ自体を提供するだけです。

各情報は URL によって一意に識別されます。つまり、URL が基本的にデータの主キーとなります。 それらの。 たとえば、本棚の 3 番目の本は /book/3 のようになり、この本の 35 ページは /book/3/page/35 のようになります。 ここから、厳密に定義された形式が得られます。 さらに、/book/3/page/35 にデータがどのような形式で配置されているかはまったく問題ではありません。HTML、jpeg ファイル形式のスキャン コピー、または Microsoft Word ドキュメントのいずれでもかまいません。

サービス情報の管理方法は完全にデータ転送プロトコルに基づいています。 最も一般的なプロトコルは、もちろん HTTP です。 したがって、HTTP の場合、データに対するアクションは GET (受信)、PUT (追加、置換)、POST (追加、変更、削除)、DELETE (削除) のメソッドを使用して指定されます。 したがって、CRUD (作成、読み取り、更新、削除) アクションは、4 つのメソッドすべてを使用して実行することも、GET と POST のみを使用して実行することもできます。

例では次のようになります。

GET /book/ - すべての書籍のリストを取得します
GET /book/3/ - 本番号 3 を取得します
PUT /book/ - 本を追加します(リクエスト本文のデータ)

DELETE /book/3 – 本を削除します

重要な付録:いわゆる REST パターンがあり、HTTP メソッドとその動作の関連付けが異なります。 特に、パターンが異なれば POST と PUT の扱いも異なります。 ただし、PUT は作成、再生、または更新を目的としており、POST に対しては定義されていません。 (POST 操作は非常に一般的なものであり、特定の意味を付加することはできません)。 したがって、私の例は、このフォームでも、POST と PUT が入れ替わった場合のフォームでも正しくなります。

一般に、POST はすべての変更アクションに同時に使用できます。
POST /book/ – 本を追加します(リクエスト本文のデータ)
POST /book/3 – 本を変更します(リクエスト本文のデータ)
POST /book/3 – 本を削除します(リクエスト本文は空です)

これにより、PUT と DELETE の拒否に関連する不快な問題を回避できる場合があります。

REST を使用して Web サービスを構築します。

ご存知のとおり、Web サービスは World Wide Web 上で実行されるアプリケーションであり、HTTP プロトコルを介してアクセスが提供され、XML 形式を使用して情報が交換されます。 したがって、リクエストボディで送信されるデータ形式は常に XML になります。

情報の単位 (info) ごとに、5 つのアクションが定義されます。 つまり:

情報を取得/ (索引)– すべてのオブジェクトのリストを取得します。 原則として、これは簡略化されたリストです。 オブジェクト識別子と名前フィールドのみが含まれ、他のデータは含まれません。

GET /info/(id) (ビュー)– オブジェクトに関する完全な情報を受け取ります。

PUT /情報/または 投稿/情報/ (作成する)– 新しいオブジェクトを作成します。 データは、urlencode も含めたエンコードを使用せずに、リクエストの本文で送信されます。 PHP では、リクエスト本文は次の方法で取得できます。

関数 getBody() (
if (!isset($HTTP_RAW_POST_DATA))
$HTTP_RAW_POST_DATA = file_get_contents("php://input");
$HTTP_RAW_POST_DATA を返します。
}

POST /info/(id)または PUT /info/(id) (編集)– 識別子 (id) を使用してデータを変更し、場合によってはそれらを置き換えます。 データはリクエストの本文でも送信されますが、PUT とは異なり、ここにはいくつかのニュアンスがあります。 実際のところ、POST リクエストは urldecoded-post-data の存在を意味します。 それらの。 コーディングを使用しない場合、これは標準違反となります。 ここでは、誰が望むにせよ、標準に注意を払わない人もいれば、ある種の事後変数を使用する人もいます。

/情報/(id)を削除してください (消去)– 識別子 (id) を持つデータを削除します。

この例では、/info/ - は他の情報に基づいている可能性があり、それが URL に反映される可能性があります (また、反映されるべきです)。

/data/4/otherdata/6/info/3/ …など。

これからどのような結論が導き出せるか:

ご覧のとおり、REST アーキテクチャは非常に使いやすいです。 (SOAP や XML-RPC とは異なり) 形式を理解していなくても、受信したリクエストの種類によって、そのリクエストが何を行うかをすぐに判断できます。 データは追加のレイヤーを使用せずに転送されるため、要求が何をすべきかを理解するためにリクエストを解析する必要がなく、データをある形式から別の形式に変換する必要がないため、REST はリソースの消費量が少ないと考えられます。

実用。

このサービスの最も重要な利点は、XML を解析して HTTP リクエストを実行するメソッドがほぼどこにでも存在するため、Web サイト、フラッシュ、プログラムなど、あらゆるシステムでサービスを使用できることです。

REST アーキテクチャにより、このタスクが大幅に簡素化されます。 もちろん、実際には、記載されている内容だけでは十分ではありません。誰にも情報を変更する機会を与えることはできないためです。つまり、承認と認証も必要です。 ただし、これは、さまざまなタイプのセッションまたは単に HTTP 認証を使用することで非常に簡単に解決できます。

親愛なる読者の皆さん、こんにちは! この記事を読み始める前に、この記事の作成目的と、それを書くきっかけを説明したいと思います。

当社のプロジェクトの 1 つでは、REST スタイルでサーバー アプリケーションを設計する必要がありました。 当初、私たちには、これはかなり単純な作業であり、私たち自身の経験だけで解決できるだろうと思われました。

しかし、アーキテクチャを開発し、REST サービスを単一のスタイルにまとめるプロセスが始まると、同僚と私は、さまざまな側面の実装に関して物議を醸す問題や異なる視点を持ち始めました。 そこで私たちは、Google を開いて集合知の助けを求め、RESTful アプリケーションを設計する際に使用すべき提案されたベスト プラクティスを研究する必要があることに気付きました。

この記事は、Web アプリケーション (場合によっては REST サービス) の経験がすでにあるものの、得た知識を統合して標準化する必要がある人にとって役立ちます。

意味

まず、REST とは何かを決める必要があります。 Wikipedia はこの質問に対して次のように答えています。 休む (代表的な状態の転送- 「プレゼンテーション状態の転送」) は、ネットワーク上の分散アプリケーションのコンポーネント間の対話のアーキテクチャ スタイルです。 REST は、分散ハイパーメディア システムを設計するときに考慮される一貫した制約のセットです。

私自身の言葉で言えば、REST の概念を「クライアント アプリケーションとサーバー アプリケーションの相互作用を統合できるようにする一連の推奨事項」として説明します。
この記事では、一般に受け入れられている慣行に従って REST サービスを設計および作成するのに役立つこれらの「推奨事項」について説明しようと思います。

REST サービスとは何かを理解する必要もあります。 私は REST サービスを「クライアント アプリケーションとサーバー間の対話ポイント」と定義します。 Java 用語では、これはクライアントがリクエストを送信するサーブレットです。

問題

ただし、ルールの説明を始める前に、REST は標準ではないため、従うべき単一の厳密なルールはないという考えを伝えたいと思います。 これは、特定の状況にどのソリューションを適用するのが最適であるかについて、まだ完全な合意が得られていないことを意味します。 特定の状況ごとに、どの HTTP メソッドを使用するか、どの HTTP コードを返すかについて議論が行われることがよくあります。

サービス名

まず、REST サービスの名前を選択する必要があります。 サービス名とは、リクエスト URI 内のパスを意味します。 例えば、 http://my-site.by/api/rest/service/name。 名前を選択するには、REST アーキテクチャにおける「リソース」が何であるかを理解する必要があります。

リソースビュー

REST 用語では、HTML ドキュメント、画像、特定のユーザーに関する情報など、あらゆるものをリソースにすることができます。 リソースが何らかのオブジェクトである場合、XML や JSON などの標準形式を使用してそれを表すのは簡単です。 その後、サーバーは選択した形式を使用してこのリソースを送信でき、クライアントは同じ形式を使用してサーバーから受信したリソースを操作できます。

JSON 形式での「プロファイル」リソース表現の例:

    "id" :1 、

    "名前" :"マヘシュ" ,

    "ログイン":"マネシュ"

REST では、リソースを表すために使用する必要がある形式に明示的な制限はありませんが、リソースを表すために使用される形式を設計するときに従う必要があるルールがいくつかあります。

  • クライアントとサーバーは、選択した形式を「理解」し、使用できる必要があります。
  • リソースは、リソースの複雑さに関係なく、選択した形式を使用して完全に記述することができます。
  • この形式は、リソース間の関係を表現できる機能を提供する必要があります。

「order」リソースの表現と「profile」リソースとの接続の例:

    ID: 11254、

    通貨: "ユーロ" 、

    金額: 100

    プロフィール: (

    ID:11、

    ウリ: 「http://MyService/Profiles/11」

ご覧のとおり、別のリソースによって参照されるリソースの構造全体を完全に複製する必要はありません。 代わりに、別のリソースへの明確なリンクを使用できます。

リソースへのアクセス

各リソースは永続的な識別子によって一意に識別される必要があります。 「永続的」とは、通信中、およびリソースの状態が変化した場合でも識別子が変更されないことを意味します。 リソースに別の識別子が割り当てられている場合、サーバーはクライアントにリクエストが失敗したことを通知し、新しいアドレスへのリンクを提供する必要があります。 各リソースは URL によって一意に識別されます。 これは、URL が基本的にデータ項目の主キーであることを意味します。 つまり、たとえば、本棚の 2 番目の本は次のようになります。 /本/2 、そしてこの本の41ページ - /本/2/ページ/41 。 ここから、厳密に定義された形式が得られます。 さらに、そのアドレスにデータがどのような形式で配置されているかはまったく問題ではありません。 /本/2/ページ/41 – これは、HTML、jpeg ファイル形式のスキャンされたコピー、または Word 文書の場合があります。

RESTサービス名を定義する場合は、複数のリソース名を使用することを推奨します。 このアプローチでは、既存のサービスの名前を拡張するだけで新しい REST サービスを追加できます。 たとえば、サービス /本 すべての書籍のリストを返します。 /本/3 3冊目の書籍とサービスに関する情報を返します /本/3/ページ 3冊目の本は全ページ返却します。

リソースに対して特定のアクションを実行するサービスの場合、アクションを指定するには、サービス名またはそのパラメーターの 2 つの方法があります。 例えば、 /books/3/clean または /books/3?clean 。 私は最初のオプションを好みます。通常、このようなサービスでは、URL へのパラメーターの受け渡しをサポートしていない POST メソッドが使用されることが多く、そのためサービスがあまり読みにくくなると私は考えています。 サービス名でアクション タイプを定義することで、HTTP メソッド タイプに依存しないため、サービスの拡張性が高まります。

また、複数の単語を含み、サービスのビジネス コンポーネントを説明する名前を使用することも強くお勧めしません (Java メソッドに名前を付ける場合に推奨されます)。 たとえば、代わりに /getAllCars メソッドを作った方が良い /車 。 メソッドを一言で説明できない場合は、単一の区切り文字スタイルを使用する必要があります。私は通常「-」を使用します。これが最も一般的な方法です。 例えば、 /車/3/販売可能。

REST サービス名の設計について詳しくは、以下を参照してください。

HTTPメソッド

REST は、GET、POST、PUT、DELETE という 4 つの主要な HTTP メソッドを使用します。 ほとんどの場合、各メソッドは CRUD から事前定義されたアクションを実行するために使用されます ( c食べる、 r読んで、 あなた更新、 d elete - 「作成、読み取り、更新、削除」).
POST - 作成、GET - 読み取り、PUT - 更新、DELETE - 削除。

重要な付録: いわゆる REST パターンがあり、HTTP メソッドとその動作の関連付けが異なります。 特に、パターンが異なれば POST と PUT の扱いも異なります。 ただし、PUT は作成、置換、または更新を目的としていますが、POST ではこれは定義されていません (POST 操作は非常に汎用的であり、それに特定の意味を付けることはできません)。 したがって、POST と PUT が入れ替わることもあります。 ただし、ほとんどの場合、作成には POST が使用され、編集には PUT が使用されます。その理由については後ほど説明します。

さまざまなメソッドを使用してリソースと対話する例をいくつか示します。

  • /本/を入手– すべての書籍のリストを取得します。 原則として、これは簡略化されたリストです。 オブジェクト識別子と名前フィールドのみが含まれ、他のデータは含まれません。
  • /books/(id) を取得する– 本に関する完全な情報を受け取ります。
  • 投稿 /書籍/– 新しい本を作成します。 データはリクエストの本文に含めて送信されます。
    PUT /books/(id)– 書籍に関するデータを識別子 (ID) で変更し、場合によってはそれらを置き換えます。 データはリクエストの本文でも送信されます。
  • オプション/本– 指定されたリソースでサポートされている操作のリストを取得します (実際には使用されません)
  • /books/(id) を削除します– 識別子 (id) を持つデータを削除します。

セキュリティと冪等性

これらのメソッドのセキュリティと冪等性に関する知識は、HTTP メソッドを選択する際に非常に役立ちます。

安全なリクエストとは、アプリケーションの状態を変更しないリクエストです。

冪等クエリとは、複数回実行した場合の効果が 1 回実行した場合の効果と等しいクエリです。

この表から判断すると、GET リクエストは、それが適用されるリソースの状態を変更するべきではありません。 PUT リクエストと DELETE リクエストはリソースの状態を変更する可能性がありますが、前のリクエストが完了したかどうかわからない場合は、安全に繰り返すことができます。 原則として、これは論理的です。特定のリソースを削除または置換するリクエストを繰り返し繰り返すと、その結果、リソースが削除または置換されます。 しかし、表からわかるように、POST リクエストは安全ではなく、冪等ではありません。 つまり、リソースの状態を変更するだけでなく、繰り返しを行うと、繰り返しの回数に応じた効果が生じます。 その意味は、データベースに新しい要素を追加する操作に対応します。つまり、クエリが X 回実行され、X 個の要素がデータベースに追加されました。

また、GET リクエストでリソースの状態を変更すべきではない理由の例も示します。 GET リクエストは、たとえばプロキシ サーバー レベルでキャッシュできます。 この場合、リクエストはアプリケーション サーバーにさえ到達しない可能性があり、プロキシ サーバーはキャッシュから情報を応答として返します。

HTTPコード

HTTP 標準では、70 を超えるステータス コードが説明されています。 少なくとも基本的なものを使用することをお勧めします。

  • 200 – OK – リクエストは成功しました。 クライアントがデータを要求した場合、そのデータはメッセージのヘッダーおよび/または本文に含まれます。
  • 201 – OK – リクエストが正常に実行された結果、新しいリソースが作成されました。
  • 204 – OK – リソースは正常に削除されました。
  • 304 – 未変更 – クライアントはキャッシュのデータを使用できます。
  • 400 – 不正なリクエスト – リクエストが無効であるか、処理できません。
  • 401 – 無許可 – リクエストにはユーザー認証が必要です。
  • 403 – 禁止 – サーバーはリクエストを理解しましたが、その処理を拒否したか、アクセスが拒否されました。
  • 404 – 見つかりません – リソースが見つかりません。
  • 500 – 内部サーバー エラー – API 開発者は、このようなエラーを回避するように努める必要があります。

これらのエラーはグローバル catch ブロックで捕捉され、ログに記録される必要がありますが、応答で返されるべきではありません。

使用するコードのセットが広範であればあるほど、作成する API はより理解しやすくなります。 ただし、ブラウザによって一部のコードが異なる方法で処理されることを考慮する必要があります。 たとえば、一部のブラウザは 307 応答コードを受信するとすぐにリダイレクトを実行しますが、一部のブラウザではこの状況を処理してアクションをキャンセルできます。 コードを使用する前に、それがクライアント側でどのように処理されるかを完全に理解する必要があります。

ヘッダー

  • コンテンツタイプ- リクエストの形式。
  • 受け入れる- 応答フォーマットのリスト。

リソース検索オプション

情報を返すサービスの使用を簡素化し、サービスの生産性を高めるためには、並べ替え、フィルタリング、フィールド選択、およびページネーションのパラメータをクエリ パラメータとして使用する必要があります。

濾過

フィルタリングを実装するには、フィールドごとに一意のクエリ パラメータを使用します。 これにより、表示される情報量が制限され、リクエストの処理時間が最適化されます。

たとえば、すべてのレッドブックを表示するには、次のクエリを実行する必要があります。

GET /books?color=red

仕分け

ソートはフィルタリングと同様に実装されます。 たとえば、すべての書籍を出版年ごとに降順で、タイトルごとに昇順で並べて表示するには、次のクエリを実行する必要があります。

GET /books?sort=-年,+名前

ページネーション

特定のアプリケーション ページに表示されるリソースのリストを読み込む機能をサポートするには、REST API がページネーション機能を提供する必要があります。 これは、SQL でおなじみのlimit パラメーターと offset パラメーターを使用して実装されます。 例えば:

GET /books?offset=10&limit=5

さらに、リンク ヘッダーに前、次、最初、最後のページへのリンクを表示することをお勧めします。 例えば:

リンク: ; rel="次"、
; rel="最後"、
; rel="最初",
; rel="前へ"

リソースフィールドの選択

サービスをより便利に使用してトラフィックを節約するために、データ出力形式を制御する機能を提供できます。 これは、REST サービスが返すリソース フィールドを選択する機能を提供することによって実装されます。 たとえば、書籍の ID とその色のみを取得する必要がある場合は、次のクエリを実行する必要があります。

GET /books?fields=id,color

状態ストレージ

RESTful サービスの制限の 1 つは、リクエストを受信したクライアントの状態を保存する必要がないことです。

ステートレス サービスの例:
リクエスト1:
リクエスト2: GET http://MyService/persons/2 HTTP/1.1

これらの各リクエストは、他のリクエストとは独立して処理できます。

状態を保存するサービスの例:
リクエスト1: GET http://MyService/persons/1 HTTP/1.1
リクエスト2: http://MyService/Nextperson HTTP/1.1 を取得します。

2 番目のリクエストを処理するには、サーバーはクライアントによって最後にリクエストされた人の ID を「記憶」する必要があります。 それらの。 サーバーは現在の状態を「記憶」する必要があります。そうしないと、2 番目のリクエストを処理できません。 サービスを設計するときは、状態を保存する必要性を避ける必要があります。これには多くの利点があります。

ステートレス サービスの利点:

  • サービスはリクエストを互いに独立して処理します。
  • サービスアーキテクチャが簡素化されます。
  • ステートレスでもある HTTP プロトコルを使用してサービスを実装するために追加の作業は必要ありません。

ステートレス サービスの欠点:

  • クライアント自体が、必要なコンテキストをサービスに渡す責任を負う必要があります。

バージョン管理

REST API のバージョン管理をサポートすることをお勧めします。 これにより、既に API を使用しているクライアントに変更を加えることなく、将来的に API を簡単に拡張できるようになります。
バージョン管理を実装するには、いくつかのアプローチがあります。

  • Accept ヘッダーを使用します。 この場合、API バージョンは Accept - に表示されます。 受け入れる:テキスト/v2+json
  • URIを使用します。 このアプローチでは、API バージョンは URI で直接指定されます - http://localhost/api/v2/books
  • カスタムヘッダーの使用。 API バージョンを渡すことのみを担う独自のヘッダーを使用できます。 - API バージョン:v2
  • クエリパラメータを使用する。 クエリパラメータを使用して API バージョンを渡すことができます - /books?v=2

提示された各方法には存在する権利があり、それぞれに独自の長所と短所があります。 ただし、バージョン管理を実装するどの方法がプロジェクトに適しているかを判断するのはあなた次第です。

ドキュメンテーション

REST サービスを便利に使用するには、適切でわかりやすいドキュメントを作成する必要があります。 これらの目的には、Mashape や Apiary などのさまざまなツールを使用できますが、Swagger を使用することをお勧めします。

Swagger は、REST サービスを文書化できるテクノロジーです。 Swagger は、多くのプログラミング言語とフレームワークをサポートしています。 さらに、Swagger はドキュメントを表示するための UI を提供します。

Swagger の詳細については、こちらをご覧ください。

アーカイブ

キャッシング

また、データベースへのクエリを減らし、REST サービスのパフォーマンスを向上させるために、キャッシュ メカニズムを使用することをお勧めします。 状況に応じて、キャッシュはサーバー レベルとアプリケーション自体の両方で構成できます。

キャッシュは、次の HTTP ヘッダーを使用して制御できます。

  • 日付 - リソースの作成日時。
  • 最終変更日 - サーバー上のリソースが最後に変更された日時。
  • Cache-Control - キャッシュの制御に使用される HTTP 1.1 ヘッダー。
  • 経過時間 - リソースが最後に受信されてからの経過時間。ヘッダーは中間 (クライアントとサーバーの間) コンポーネント (プロキシ サーバーなど) によって追加できます。

この投稿は、私の記事へのコメントで寄せられた質問に対する回答です。

この記事では、HTTP メソッド GET/POST/PUT/DELETE などが何であるか、それらが発明された理由、および REST に従ってそれらを使用する方法について説明したいと思います。

HTTP

では、インターネットの主要なプロトコルの 1 つは何でしょうか? 衒学的な部分は RFC2616 に送り、残りについては人間的に伝えます :)

このプロトコルは、リクエストとレスポンスと呼ばれるメッセージに基づいて構築された 2 台のコンピュータ (クライアントとサーバー) 間の対話を記述します。 各メッセージは、開始行、ヘッダー、本文の 3 つの部分で構成されます。 この場合、必要なのはスタートラインのみです。

リクエストとレスポンスの開始行の形式は異なります。ここで注目するのは、次のようなリクエストの開始行だけです。

メソッド URI HTTP/ バージョン ,

ここで、METHOD は HTTP リクエスト メソッド、URI はリソース識別子、VERSION はプロトコルのバージョンです (現在はバージョン 1.1 です)。

ヘッダーは、コロンで区切られた名前と値のペアのコレクションです。 ヘッダーは、メッセージ エンコーディング、ブラウザーの名前とバージョン、クライアントの送信元アドレス (参照元) など、さまざまなサービス情報を伝えます。

メッセージの本文は、送信される実際のデータです。 応答では、送信されるデータは原則としてブラウザが要求した HTML ページであり、要求では、たとえばメッセージの本文で、サーバーにアップロードされたファイルのコンテンツが送信されます。 ただし、原則として、リクエストにはメッセージ本文がまったくありません。

HTTP インタラクションの例

例を見てみましょう。

リクエスト:
GET /index.php HTTP/1.1 ホスト: example.com ユーザーエージェント: Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9b5) Gecko/2008050509 Firefox/3.0b5 Accept: text/html 接続: close
最初の行はクエリ行で、残りはヘッダーです。 メッセージ本文がありません

答え:
HTTP/1.0 200 OK サーバー: nginx/0.6.31 コンテンツ言語: ru コンテンツタイプ: text/html; charset=utf-8 Content-Length: 1234 接続: close ... HTML ページ自体...

リソースと方法

リクエストの開始行に戻り、リクエストには URI などのパラメータが含まれていることを思い出してください。 これは、Uniform Resource Identifier (統一リソース識別子) の略です。 リソースは原則としてサーバー上のファイル (この場合の URI の例は「/styles.css」) ですが、一般にリソースは抽象オブジェクト (「/blogs/webdev/」 - ポイント) であることもあります。特定のファイルではなく「Web」ブロック開発」にアクセスします)。

HTTP リクエスト タイプ (HTTP メソッドとも呼ばれます) は、リソースに対してどのようなアクションを実行するかをサーバーに伝えます。 当初 (90 年代初頭)、クライアントはリソースから 1 つのこと、つまりリソー​​スを受信することだけを望むと考えられていましたが、現在では HTTP プロトコルを使用して、投稿の作成、プロフィールの編集、メッセージの削除などを行うことができます。 そして、これらの行為を「受け取り」という言葉と組み合わせるのは困難です。

HTTP メソッドのレベルでアクションとリソースを区別するために、次のオプションが考案されました。

  • GET - リソースの取得
  • POST - リソースの作成
  • PUT - リソースの更新
  • DELETE - リソースの削除
HTTP 仕様では、サーバーがすべてのメソッド (実際には 4 つ以上のメソッドがあります) を理解する必要はないことに注意してください。必要なのは GET のみであり、特定のリクエストを受信したときにサーバーが何をすべきかについてもサーバーに指示しません。方法。 これは、サーバーが DELETE /index.php HTTP/1.1 リクエストに応答することを意味します。 する義務はない GET /index.php HTTP/1.1 リクエストの場合と同様に、サーバー上のindex.phpページを削除します。 する義務はない Index.php ページを返します。たとえば、削除できます:)

RESTの登場

REST (REpresentational State Transfer) は、HTTP プロトコルの開発者の 1 人である Roy Fielding によって、Web アプリケーションを構築するための一連の原則の名前として 2000 年に導入された用語です。 一般に、REST は HTTP より広い範囲をカバーしており、他のプロトコルを使用する他のネットワークでも使用できます。 REST は、「リソース」と「動詞」(主語と述語として理解できます)の概念に基づいて、クライアントとサーバー間の対話の原則を記述します。 HTTP の場合、リソースはその URI によって識別され、動詞は HTTP メソッドです。

REST は、異なるリソース (つまり、/index.php?article_id=10 と /index.php?article_id=20 のような 2 つの異なる記事のアドレス - これは REST の方法ではありません) に対して同じ URI の使用を放棄することを提案しています。異なるアクションに対して異なる HTTP メソッドを使用します。 つまり、REST アプローチを使用して作成された Web アプリケーションは、HTTP DELETE メソッドでアクセスするとリソースを削除します (もちろん、これはすべてのユーザーを削除する機会を与える必要があるという意味ではありませんが、 どれでもアプリケーションの削除リクエストでは HTTP DELETE メソッドを使用する必要があります)。

REST を使用すると、プログラマーは、標準化され、以前よりも少しきれいになった Web アプリケーションを作成できるようになります。 REST を使用すると、新しいユーザーを追加する URI は /user.php?action=create (GET/POST メソッド) ではなく、単に /user.php (厳密には POST メソッド) になります。

その結果、既存の HTTP 仕様と REST アプローチを組み合わせることで、さまざまな HTTP メソッドが最終的に意味のあるものになります。 GET - リソースを返します。POST - 新しいリソースを作成します。PUT - 既存のリソースを更新します。DELETE - 削除します。

問題がありますか?

はい、実際に REST を使用する場合には小さな問題があります。 この問題は HTML と呼ばれます。

PUT/DELETE リクエストは、XMLHttpRequest を使用してサーバーに手動で接続することで (たとえば、curl 経由、または Telnet 経由でも) 送信できますが、本格的な PUT/DELETE リクエストを送信する HTML フォームを作成することはできません。

問題は、HTML 仕様では、GET または POST 経由以外でデ​​ータを送信するフォームを作成することを許可していないということです。 したがって、他のメソッドを正常に動作させるには、それらを人為的に模倣する必要があります。 たとえば、Rack (Ruby が Web サーバーと対話するための基礎となるメカニズム。Rails、Merb、およびその他の Ruby フレームワークは Rack を使用して作成されます) では、フォームに「_method」という名前の隠しフィールドを追加できます。メソッドの名前を値として指定します (例: "PUT")。この場合、POST リクエストが送信されますが、Rack は POST ではなく PUT を受信したかのように振る舞うことができます。

RESTful API はサードパーティのサービスだけでなく作成できます。 バックエンド作業用の単一ページ アプリケーションで使用できます。 インターフェイスを設計する際に知っておくべき基本的な事項をいくつか紹介します。

URLとアクション

REST の重要な原則は、API を論理リソースに分割することです。 これらのリソースは、HTTP リクエストと適切なメソッド (GET、POST、PUT、PATCH、DELETE) を使用して管理されます。

リソースは複数名詞で記述する必要があります。 リソースに対するアクションは通常、CRUD 戦略によって定義され、次のように HTTP メソッドに対応します。

  • GET /api/users - ユーザーのリストを取得します。
  • GET /api/users/123 - 指定されたユーザーを取得します。
  • POST /api/users - 新しいユーザーを作成します。
  • PUT /api/users/123 - 指定されたユーザーのすべてのデータを更新します。
  • PATCH /api/users/123 - ユーザー データを部分的に更新します。
  • DELETE /api/users/123 - ユーザーを削除します。

リソースが別のリソースのコンテキスト内にのみ存在する場合、URL は複雑になる可能性があります。

  • GET /api/posts/9/comments - 投稿番号 9 のコメントのリストを取得します。
  • GET /api/posts/9/comments/3 - コメント番号 3 から投稿番号 9 を取得します。

オブジェクトに対するアクションが CRUD 操作に対応しない場合、そのアクションは複合リソースとみなされます。

  • POST /api/posts/9/like - 投稿番号 9 を「いいね!」としてマークします。
  • DELETE /api/posts/9/like - 投稿番号 9 から「いいね」マークを削除します。

リソースを作成および更新するアクションはリソースを返す必要があります

POST、PUT、または PATCH メソッドは、リクエストに含まれていないリソース フィールド (ID、作成日、更新日など) を変更できます。 API ユーザーが更新データを取得するために別のリクエストを強制されることを避けるために、このようなメソッドは応答でデータを返す必要があります。

フィルター、並べ替え、検索

HTTP リクエスト内のパラメータは、リクエストを絞り込んだり、データを並べ替えたりするために使用できます。

  • GET /api/users?state=active - アクティブなユーザーのリスト。
  • GET /api/tasks?state=open&sort=priority,-created_at - 優先度と作成日で並べ替えられた未処理のタスクのリスト (新しいタスクが最初)。

ページナビゲーション

オブジェクトのリストのリクエストに応じてページネーションに関する情報を追加する必要がある場合は、データ ラッパーを追加するのではなく、Link HTTP ヘッダーを使用する必要があります。

ヘッダーの例:

リンク: ; rel="次"、 ; rel="前", ; rel="最初", ; rel="最後"

rel の可能な値:

  • next - 結果の次のページ。
  • prev - 結果の前のページ。
  • 最初 - 結果の最初のページ。
  • 最後 - 結果の最後のページ。

ページネーション ナビゲーションは、単純にページを反復するのではなく、複雑なルールに基づいている場合があるため、独自の URL を構築するのではなく、これらの値に従うことが重要です。

HTTPメソッドのオーバーライド

GET と POST 以外の HTTP メソッドをサポートしていない一部のサーバーまたはクライアントとの互換性のために、それらをエミュレートすると便利な場合があります。 メソッドの値は X-HTTP-Method-Override ヘッダーで渡され、メソッド自体は POST メソッドとして実行されます。 GET リクエストによってサーバーの状態が変更されるべきではありません。

HTTPステータスコード

  • 200 OK - 成功した GET、PUT、PATCH、または DELETE リクエストに対する応答。
  • 201 Created - POST リクエストに対する応答。新しいオブジェクトが作成されました。 応答には、リソース URL を指す Location ヘッダーも伴う必要があります。
  • 204 No Content - 何も返さない、正常に実行されたリクエストに対する応答 (DELETE など)。
  • 404 Not Found - 要求されたオブジェクトが見つかりませんでした。
  • 500 内部サーバー エラー - サーバー上のエラー。

エラーが発生した場合、可能であれば応答に開発者向けのデバッグ情報が含まれる場合があります。