REST API-ファイル(画像)処理-ベストプラクティス


194

JSONを受け入れて応答するREST APIを備えたサーバーを開発しています。問題は、クライアントからサーバーに画像をアップロードする必要がある場合です。

注:また、エンティティ(ユーザー)が複数のファイル(carPhoto、licensePhoto)を持ち、他のプロパティ(name、email ...)を持つことができるユースケースについても話しますが、新しいユーザーを作成すると、これらの画像は送信されません。登録プロセスの後に追加されます。


私が知っている解決策ですが、それぞれにいくつかの欠陥があります

1. JSONの代わりにmultipart / form-dataを使用する

good:POSTおよびPUTリクエストは可能な限りRESTfulであり、ファイルとともにテキスト入力を含めることができます。

短所:JSONではなく、テストやデバッグなどがmultipart / form-dataに比べてはるかに簡単になりました。

2.個別のファイルの更新を許可する

新しいユーザーを作成するためのPOSTリクエストは画像を追加することを許可していません(これは、私が最初に言ったユースケースでは問題ありません)。

good:すべて(ファイルのアップロード自体を除く)はJSONのままなので、テストとデバッグが簡単です(長さを気にせずに完全なJSONリクエストをログに記録できます)

短所:直感的ではありません。エンティティのすべての変数を一度にPOSTまたはPUTすることはできません。また、このアドレス/users/4/carPhotoはコレクションと見なすことができます(REST APIの標準的な使用例は次のようになります/users/4/shipments)。通常、エンティティの各変数、たとえばusers / 4 / nameをGET / PUTすることはできません(したくありません)。users / 4でGETを使用して名前を取得し、PUTを使用して名前を変更できます。IDの後に何かがある場合、それは通常、users / 4 / reviewsのような別のコレクションです。

3. Base64を使用する

JSONとして送信しますが、Base64でファイルをエンコードします。

good:最初のソリューションと同じで、できるだけRESTfulなサービスです。

短所:繰り返しになりますが、テストとデバッグははるかに悪く(ボディにはメガバイトのデータが含まれる可能性があります)、サイズが増加し、クライアントとサーバーの両方で処理時間が長くなります。


私は本当にソリューション番号を使用したいと思います。2、しかし、それはその短所を持っています...誰でも私に「何が最善」の解決策のより良い洞察を与えることができますか?

私の目標は、できるだけ多くの標準を組み込んだRESTfulサービスを提供することですが、できるだけシンプルにしたいと考えています。


:また、この便利かもしれませんstackoverflow.com/questions/4083702/...
Markon

5
このトピックは古いことを知っていますが、最近この問題に直面しています。私たちが得た最善のアプローチは、あなたの2番に似ています。ファイルをAPIに直接アップロードし、これらのファイルをモデルに添付します。このシナリオでは、フォームの前、後、または同じページにアップロード画像を作成できますが、実際には重要ではありません。良い議論!
Tiago Matos 2017年

2
@TiagoMatos-はい、正確に、私は最近受け入れた1つの回答でそれを説明しました
libik

6
この質問をしていただきありがとうございます。
Zuhayer Tahir、2017

1
「また、このアドレス/ users / 4 / carPhotoは、よりコレクションと見なすことができます」–それは、コレクションのように見えず、必ずしも1つと見なされるわけではありません。コレクションではなく単一のリソースであるリソースとの関係を持つことは、まったく問題ありません。
B12Toaster

回答:


152

ここでOP(私は2年後にこの質問に答えています。DanielCerecedoによる投稿は一度に悪くはありませんでしたが、Webサービスは非常に速く開発されています)

3年間のフルタイムのソフトウェア開発(ソフトウェアアーキテクチャ、プロジェクト管理、マイクロサービスアーキテクチャにも焦点を当てている)の後、私は間違いなく2番目の方法(ただし、1つの一般的なエンドポイントを使用)を最良の方法として選択します。

画像に特別なエンドポイントがある場合、それらの画像を処理するよりもはるかに強力です。

モバイルアプリ(iOS / android)とフロントエンド(Reactを使用)の両方に同じREST API(Node.js)があります。これは2017年なので、画像をローカルに保存するのではなく、いくつかのクラウドストレージ(Googleクラウド、s3、cloudinaryなど)にアップロードしたいので、それらに対する一般的な処理が必要です。

通常のフローでは、画像を選択するとすぐにバックグラウンドでのアップロードが開始され(通常は/ imagesエンドポイントでPOST)、アップロード後にIDが返されます。これは本当にユーザーフレンドリーです。ユーザーが画像を選択し、通常は他のいくつかのフィールド(住所、名前など)に進むため、「送信」ボタンを押すと、画像は通常すでにアップロードされています。彼は「アップロード中...」と言って画面を見るのを待ちません。

画像を取得する場合も同様です。特に携帯電話と限られたモバイルデータのおかげで、元の画像を送信したくない、サイズを変更した画像を送信したいので、それほど多くの帯域幅を使用しません(そして、モバイルアプリを高速化するために、多くの場合、不要です)まったくサイズを変更するには、ビューにぴったり収まる画像が必要です)。このため、優れたアプリはcloudinaryのようなものを使用しています(または、サイズを変更するための独自の画像サーバーがあります)。

また、データが非公開でない場合は、URLのみをapp / frontendに送り返し、それをクラウドストレージから直接ダウンロードします。これにより、サーバーの帯域幅と処理時間を大幅に節約できます。私たちのより大きなアプリでは、毎月多くのテラバイトがダウンロードされます。CRUD操作に焦点を当てている各REST APIサーバーで直接処理する必要はありません。それを1つの場所(キャッシュなどを備えたImageserver)で処理するか、クラウドサービスにすべてを処理させたいとします。


短所:考えなければならない唯一の「短所」は「割り当てられていない画像」です。ユーザーが画像を選択して他のフィールドへの入力を続行しますが、「いや」と言ってアプリまたはタブをオフにしますが、その間に画像を正常にアップロードしました。これは、どこにも割り当てられていない画像をアップロードしたことを意味します。

これを処理する方法はいくつかあります。最も簡単なのは、「私は気にしない」です。これはあまり頻繁に発生しないか、ユーザーが送信したすべての画像を保存したい場合でも(何らかの理由で)何もしたくない場合に使用します。削除。

もう1つも簡単です。CRONがあり、毎週、1週間より古い未割り当ての画像をすべて削除します。


[画像を選択するとすぐにバックグラウンドでアップロードが開始され(通常は/ imagesエンドポイントでPOST)、アップロード後にIDが返される]インターネット接続が原因でリクエストが失敗した場合はどうなりますか?他のフィールド(アドレス、名前など)を続行するときに、ユーザーにプロンプ​​トを表示しますか?ユーザーが「送信」ボタンを押すまで待機し、リクエストを再試行して、「uploadiing ...」と言っている画面を見ながら待機させます。
Adromil Balais 2017

5
@AdromilBalais-RESTful APIはステートレスであるため、何もしません(サーバーはコンシューマーの状態を追跡しません)。サービスのコンシューマー(つまり、Webページまたはモバイルデバイス)は失敗した要求を処理する責任があるため、コンシューマーは、この要求が失敗した直後に同じ要求を呼び出すか、または何をすべきかを決定する必要があります(つまり、「画像のアップロードに失敗しました-再試行します")
libik 2017

2
非常に有益で啓発的な答え。回答ありがとうございます。
Zuhayer Tahir、2017

これは最初の問題を実際に解決するものではありません。これは単に「クラウドサービスを使用する」と言う
Martin Muzatk​​o

3
@MartinMuzatk​​o-2番目のオプションを選択し、それをどのように使用する必要があるか、およびその理由を通知します。「しかし、これは1つの要求ですべてを送信できる完全なオプションではなく、意味を持たない」-はい、残念ながらそのような解決策はありません。
libik 2018年

103

いくつかの決定を行う必要があります

  1. 最初のリソースパスについて:

    • 画像を独自のリソースとしてモデル化します。

      • ユーザーにネスト(/ user /:id / image):ユーザーと画像の関係が暗黙的に作成されます

      • ルートパス(/ image):

        • クライアントは、画像とユーザー間の関係を確立する責任があります。

        • イメージの作成に使用されるPOSTリクエストでセキュリティコンテキストが提供されている場合、サーバーは認証されたユーザーとイメージ間の関係を暗黙的に確立できます。

    • ユーザーの一部として画像を埋め込む

  2. 2番目の決定は、画像リソースの表現方法に関するものです

    • Base 64でエンコードされたJSONペイロードとして
    • マルチパートペイロードとして

これは私の決定トラックになります:

  • 強い理由がない限り、私は通常、パフォーマンスよりもデザインを優先します。これにより、システムの保守が容易になり、インテグレーターが理解しやすくなります。
  • したがって、私が最初に考えたのは、画像リソースのBase64表現を使用することです。これにより、すべてのJSONを保持できます。このオプションを選択した場合は、リソースパスを好きなようにモデル化できます。
    • ユーザーと画像の関係が1対1の場合、両方のデータセットが同時に更新される場合は、特に画像を属性としてモデル化することをお勧めします。それ以外の場合は、画像を属性としてモデル化するか、PUTまたはPATCHを介して更新するか、または個別のリソースとして自由にモデル化できます。
  • マルチパートペイロードを選択した場合は、画像を独自のリソースとしてモデル化する必要があるので、他のリソース、この場合はユーザーリソースは、画像のバイナリ表現を使用するかどうかの決定の影響を受けません。

次に質問が来ます:base64とマルチパートの選択についてパフォーマンスに影響はありますか?。マルチパート形式でデータを交換する方が効率的であると考えることができます。しかし、この記事では、両方の表現のサイズの違いがどれほど少ないかを示しています。

私の選択Base64:

  • 一貫した設計決定
  • パフォーマンスへの影響はごくわずか
  • ブラウザーはデータURI(base64エンコードされた画像)を理解するため、クライアントがブラウザーの場合はこれらを変換する必要はありません。
  • それを属性またはスタンドアロンリソースのどちらにするかについては投票しません。問題のドメイン(わからない)と個人の好みによって異なります。

3
protobufなどの他のシリアル化プロトコルを使用してデータをエンコードできませんか?基本的に、base64エンコーディングに伴うサイズと処理時間の増加に対処する他のより簡単な方法があるかどうかを理解しようとしています。
Andy Dufresne

1
非常に魅力的な答え。段階的なアプローチに感謝します。それはあなたのポイントをよりよく理解させてくれました。
Zuhayer Tahir、2017

13

あなたの2番目の解決策はおそらく最も正しいでしょう。HTTP仕様とMIMEタイプを意図したとおりに使用し、を介してファイルをアップロードする必要がありますmultipart/form-data。関係を処理する限り、私はこのプロセスを使用します(あなたの仮定またはシステム設計についてはゼロを知っていることを覚えておいてください)。

  1. POST/usersユーザエンティティを作成します。
  2. POST画像を/imagesLocation変換します。HTTP仕様に従って画像を取得できる場所にヘッダーを必ず返してください。
  3. PATCH/users/carPhotoで与えられた写真のIDと割り当てLocationステップ2のヘッダ。

1
私は「クライアントがAPIをどのように使用するか」を直接制御していません...この問題は、一部のリソースにパッチが適用されていない「デッド」画像です...
libik

4
通常、2番目のオプションを選択した場合、最初にメディア要素をアップロードしてメディア識別子をクライアントに返すことをお勧めします。その後、クライアントはメディア識別子を含むエンティティデータを送信できます。これらのアプローチにより、エンティティの不一致や情報の不一致が回避されます。
Kellerman Rivero

2

簡単な解決策はありません。それぞれの方法には長所と短所があります。しかし、標準的な方法は最初のオプションを使用することです:multipart/form-data。以下のようW3勧告ガイドは言います

コンテンツタイプ「multipart / form-data」は、ファイル、非ASCIIデータ、およびバイナリデータを含むフォームの送信に使用する必要があります。

実際にはフォームを送信していませんが、暗黙の原則が適用されます。目的を達成するために誤ったツールを使用しているため、base64をバイナリ表現として使用することは正しくありません。一方、2番目のオプションは、APIサービスを消費するためにAPIクライアントにさらに多くの仕事を強いることになります。使いやすいAPIを提供するには、サーバー側で大変な作業を行う必要があります。最初のオプションはデバッグが容易ではありませんが、それを実行しても、おそらく変更されることはありません。

multipart/form-dataあなたの使用は、REST / httpの哲学に固執しています。ここで同様の質問に対する回答を表示できます。

代替案を混合する場合の別のオプションとして、multipart / form-dataを使用できますが、すべての値を個別に送信する代わりに、payloadという名前の値をその中にjsonペイロードとともに送信できます。(私はASP.NET WebAPI 2を使用してこの方法を試し、問題なく動作しました)。


2
このW3推奨ガイドは、HTML 4仕様に関連しているため、ここでは関係ありません。
ヨハン

1
非常に本当です... "非ASCIIデータ"にはマルチパートが必要ですか?21世紀に?UTF-8の世界では?もちろん、それは今日のとんでもない推奨事項です。HTMLの4日間に存在していたことにも驚きましたが、インターネットインフラストラクチャの世界の動きが非常に遅い場合があります。
Ray Toal 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.