REST Webサービスを使用してメタデータを含むファイルをアップロードするにはどうすればよいですか?


249

現在このURLを公開しているREST Webサービスがあります。

http:// server / data / media

ユーザーPOSTは次のJSONを実行できます。

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}

新しいメディアメタデータを作成するため。

次に、メディアメタデータと同時にファイルをアップロードする機能が必要です。これに対処する最良の方法は何ですか?という新しいプロパティを導入してfilebase64でファイルをエンコードすることもできましたが、もっと良い方法があるかどうか疑問に思っていました。

multipart/form-dataHTMLフォームが送信するようなものも使用していますが、私はREST Webサービスを使用しており、可能であればJSONの使用に固執したいと考えています。


35
RESTful Webサービスを使用するために、JSONのみを使用することにこだわる必要はありません。RESTは基本的に、HTTPメソッドの主要な原則と他のいくつかの(おそらく非標準化された)ルールに従うものにすぎません。
Erik Kaplun、2012年

回答:


192

2フェーズアプローチが妥当な解決策であるとグレッグに同意しますが、逆の方法でそれを行います。私はします:

POST http://server/data/media
body:
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}

メタデータエントリを作成して次のような応答を返すには:

201 Created
Location: http://server/data/media/21323
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentUrl": "http://server/data/media/21323/content"
}

クライアントはこのContentUrlを使用して、ファイルデータをPUTできます。

このアプローチの良い点は、サーバーが膨大な量のデータで圧迫され始めたときに、返されるURLがより多くのスペース/容量を持つ他のサーバーを指すことです。または、帯域幅が問題になる場合は、ある種のラウンドロビンアプローチを実装できます。


8
最初にコンテンツを送信する利点の1つは、メタデータが存在する時点で、コンテンツがすでに存在していることです。最終的に正しい答えは、システム内のデータの編成に依存します。
グレッグヒューギル

ありがとう、これが正解でした。これが私がやりたかったことです。残念ながら、奇妙なビジネスルールのため、アップロードは任意の順序で行う必要があります(最初にメタデータまたは最初にファイル)。両方の状況に対処する頭痛を軽減するために2つを組み合わせる方法があるかどうか疑問に思っていました。
ダニエルT.

@Daniel最初にデータファイルをPOSTする場合、Locationで返されたURLを取得して、メタデータのContentUrl属性に追加できます。このようにして、サーバーがメタデータを受信するときにContentUrlが存在する場合、サーバーはファイルの場所をすでに認識しています。ContentUrlがない場合は、作成する必要があることを認識しています。
Darrel Miller

最初にPOSTを実行する場合、同じURLに投稿しますか?(/ server / data / media)または、ファイルファーストアップロード用に別のエントリポイントを作成しますか?
Matt Brailsford、

1
@Farawayメタデータに画像の「いいね」の数が含まれている場合はどうなりますか?では、それを単一のリソースとして扱いますか?あるいは、画像の説明を編集したい場合は、画像を再アップロードする必要があるとお考えですか?マルチパートフォームが適切なソリューションである多くの場合があります。いつもそうであるとは限りません。
Darrel Miller、

103

リクエストの本文全体をJSONでラップしていないmultipart/form-dataからといって、JSONとファイルの両方を1つのリクエストに投稿するのにRESTfulではないという意味ではありません。

curl -F "metadata=<metadata.json" -F "file=@my-file.tar.gz" http://example.com/add-file

サーバー側(Pythonを擬似コードに使用):

class AddFileResource(Resource):
    def render_POST(self, request):
        metadata = json.loads(request.args['metadata'][0])
        file_body = request.args['file'][0]
        ...

複数のファイルをアップロードするには、それぞれに個別の「フォームフィールド」を使用することができます。

curl -F "metadata=<metadata.json" -F "file1=@some-file.tar.gz" -F "file2=@some-other-file.tar.gz" http://example.com/add-file

...その場合には、サーバーのコードがありますrequest.args['file1'][0]し、request.args['file2'][0]

または同じものを多くの人に再利用します:

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz" -F "files=@some-other-file.tar.gz" http://example.com/add-file

...この場合request.args['files']、長さ2のリストになります。

または、単一のフィールドを介して複数のファイルを渡します。

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz,some-other-file.tar.gz" http://example.com/add-file

...この場合request.args['files']、すべてのファイルを含む文字列となり、自分で解析する必要があります。その方法はわかりませんが、難しくはないか、以前の方法を使用する方がよいと思います。

違い@とは<つまり@一方、ファイルアップロードとして執着するファイルの原因となる<テキストフィールドとしてアタッチファイルの内容。

PS要求curlを生成する方法として使用しているからとPOSTいって、Pythonなどのプログラミング言語から、または十分に機能するツールを使用して、まったく同じHTTP要求を送信できなかったわけではありません。


4
私は自分自身でこのアプローチについて疑問に思っていましたが、なぜ他の人がまだそれを発表しているのを見たことがないのでしょうか。私には同意します。完全にRESTfulに思えます。
スープドッグ2013年

1
はい!これは非常に実用的なアプローチであり、リクエスト全体のコンテンツタイプとして「application / json」を使用するよりもRESTfulです。
sickill 2014年

..しかし、.jsonファイルにデータがあり、それをアップロードする場合にのみ可能です。そうではありません
itsjavi

5
@mjolnicコメントは関係ありません。cURLの例は単なるです。答えは、要求を送信するために何でも使用できることを明示的に述べています...また、何を書くことを妨げているのcurl -f 'metadata={"foo": "bar"}'ですか?
Erik Kaplun、2015

3
承認された回答が開発中のアプリケーションでは機能しないため、このアプローチを使用しています(データの前にファイルは存在できず、データが最初にアップロードされ、ファイルがアップロードされない場合の処理​​に不必要な複雑さが追加されます)。 。
BitsEvolved 2016年

33

この問題に取り組む1つの方法は、アップロードを2段階のプロセスにすることです。最初に、POSTを使用してファイル自体をアップロードします。サーバーはクライアントに識別子を返します(識別子はファイルコンテンツのSHA1の場合があります)。次に、2番目のリクエストでメタデータをファイルデータに関連付けます。

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentID": "7a788f56fa49ae0ba5ebde780efe4d6a89b5db47"
}

JSONリクエスト自体にbase64エンコードされたファイルデータを含めると、転送されるデータのサイズが33%増加します。これは、ファイルの全体的なサイズに応じて、重要な場合とそうでない場合があります。

別のアプローチは、生のファイルデータのPOSTを使用することですが、HTTPリクエストヘッダーにメタデータを含めます。ただし、これは基本的なREST操作の範囲外であり、一部のHTTPクライアントライブラリではより扱いにくい場合があります。


Ascii85を1/4だけ使用できます。
Singagirl 2016

base64がサイズをそれほど大きくする理由についての参照はありますか?
jam01

1
@ jam01:偶然にも、昨日、スペースの質問によく答える何かを見たことがあります。Base64 エンコーディングのスペースオーバーヘッドは何ですか?
グレッグヒューギル

10

私はこれが非常に古い質問であることを理解していますが、うまくいけば、同じことを探しているこの投稿に出会ったとき、これが他の誰かを助けるでしょう。私のメタデータがGuidとintであるというだけの同様の問題がありました。解決策は同じです。必要なメタデータをURLの一部にするだけです。

「Controller」クラスのPOST受け入れメソッド:

public Task<HttpResponseMessage> PostFile(string name, float latitude, float longitude)
{
    //See http://stackoverflow.com/a/10327789/431906 for how to accept a file
    return null;
}

次に、ルートを登録しているすべてのもので、この場合はWebApiConfig.Register(HttpConfiguration config)を使用します。

config.Routes.MapHttpRoute(
    name: "FooController",
    routeTemplate: "api/{controller}/{name}/{latitude}/{longitude}",
    defaults: new { }
);

5

ファイルとそのメタデータが1つのリソースを作成している場合、1つのリクエストで両方をアップロードするのはまったく問題ありません。サンプルリクエストは次のようになります。

POST https://target.com/myresources/resourcename HTTP/1.1

Accept: application/json

Content-Type: multipart/form-data; 

boundary=-----------------------------28947758029299

Host: target.com

-------------------------------28947758029299

Content-Disposition: form-data; name="application/json"

{"markers": [
        {
            "point":new GLatLng(40.266044,-74.718479), 
            "homeTeam":"Lawrence Library",
            "awayTeam":"LUGip",
            "markerImage":"images/red.png",
            "information": "Linux users group meets second Wednesday of each month.",
            "fixture":"Wednesday 7pm",
            "capacity":"",
            "previousScore":""
        },
        {
            "point":new GLatLng(40.211600,-74.695702),
            "homeTeam":"Hamilton Library",
            "awayTeam":"LUGip HW SIG",
            "markerImage":"images/white.png",
            "information": "Linux users can meet the first Tuesday of the month to work out harward and configuration issues.",
            "fixture":"Tuesday 7pm",
            "capacity":"",
            "tv":""
        },
        {
            "point":new GLatLng(40.294535,-74.682012),
            "homeTeam":"Applebees",
            "awayTeam":"After LUPip Mtg Spot",
            "markerImage":"images/newcastle.png",
            "information": "Some of us go there after the main LUGip meeting, drink brews, and talk.",
            "fixture":"Wednesday whenever",
            "capacity":"2 to 4 pints",
            "tv":""
        },
] }

-------------------------------28947758029299

Content-Disposition: form-data; name="name"; filename="myfilename.pdf"

Content-Type: application/octet-stream

%PDF-1.4
%
2 0 obj
<</Length 57/Filter/FlateDecode>>stream
x+r
26S00SI2P0Qn
F
!i\
)%!Y0i@.k
[
endstream
endobj
4 0 obj
<</Type/Page/MediaBox[0 0 595 842]/Resources<</Font<</F1 1 0 R>>>>/Contents 2 0 R/Parent 3 0 R>>
endobj
1 0 obj
<</Type/Font/Subtype/Type1/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>
endobj
3 0 obj
<</Type/Pages/Count 1/Kids[4 0 R]>>
endobj
5 0 obj
<</Type/Catalog/Pages 3 0 R>>
endobj
6 0 obj
<</Producer(iTextSharp 5.5.11 2000-2017 iText Group NV \(AGPL-version\))/CreationDate(D:20170630120636+02'00')/ModDate(D:20170630120636+02'00')>>
endobj
xref
0 7
0000000000 65535 f 
0000000250 00000 n 
0000000015 00000 n 
0000000338 00000 n 
0000000138 00000 n 
0000000389 00000 n 
0000000434 00000 n 
trailer
<</Size 7/Root 5 0 R/Info 6 0 R/ID [<c7c34272c2e618698de73f4e1a65a1b5><c7c34272c2e618698de73f4e1a65a1b5>]>>
%iText-5.5.11
startxref
597
%%EOF

-------------------------------28947758029299--

3

8年の間に誰も簡単な答えを投稿しなかった理由がわかりません。ファイルをbase64としてエンコードするのではなく、jsonを文字列としてエンコードします。次に、サーバー側でjsonをデコードします。

JavaScriptでは:

let formData = new FormData();
formData.append("file", myfile);
formData.append("myjson", JSON.stringify(myJsonObject));

Content-Typeを使用してPOSTします:multipart / form-data

サーバー側では、通常どおりファイルを取得し、jsonを文字列として取得します。文字列をオブジェクトに変換します。オブジェクトは、使用するプログラミング言語に関係なく、通常は1行のコードです。

(はい、それはうまくいきます。私のアプリの1つでそれを行います。)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.