web-dev-qa-db-ja.com

REST Webサービスを使用してメタデータを含むファイルをアップロードする方法

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

http://サーバー/データ/メディア

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

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

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

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

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

222
Daniel T.

私はGregに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は、より多くのスペース/容量を持つ他のサーバーを指すことができます。帯域幅が問題になる場合は、ある種のラウンドロビンアプローチを実装することもできます。

176
Darrel Miller

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

curl -F "metadata=<metadata.json" -F "[email protected]" 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 "[email protected]" -F "[email protected]" http://example.com/add-file

...その場合、サーバーコードはrequest.args['file1'][0]request.args['file2'][0]を持ちます。

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

curl -F "metadata=<metadata.json" -F "[email protected]" -F "[email protected]" http://example.com/add-file

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

または実際には一度に複数のファイルを1つのフィールドに渡す:

curl -F "metadata=<metadata.json" -F "[email protected],some-other-file.tar.gz" http://example.com/add-file

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

@<の違いは、@がファイルのアップロードとして添付されるのに対し、<はファイルの内容をテキストフィールドとして添付することです。

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

97
Erik Allik

この問題に対処する1つの方法は、アップロードを2段階のプロセスにすることです。まず、POSTを使用してファイル自体をアップロードします。この場合、サーバーは何らかの識別子をクライアントに返します(識別子はファイルの内容のSHA1かもしれません)。次に、2番目のリクエストでメタデータをファイルデータに関連付けます。

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

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

もう1つの方法は、生ファイルデータのPOSTを使用することですが、HTTP要求ヘッダーにメタデータを含めます。ただし、これは基本的なREST操作からは少し外れているため、一部のHTTPクライアントライブラリでは面倒な場合があります。

30
Greg Hewgill

私はこれが非常に古い質問であることを認識しています、しかし私が同じ事を捜してこの記事に来たとき、これが他の誰かを助けるのを願っています。私のメタデータが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 { }
);
10
Greg Biles

ファイルとそのメタデータが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\
)%[email protected]
[
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--
4
Mike Ezzati

8年の間に誰も簡単な答えを投稿していないのはなぜでしょうか。ファイルをbase64としてエンコードするのではなく、jsonを文字列としてエンコードします。その後、サーバー側でJSONをデコードするだけです。

Javascriptでは:

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

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

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

(はい、それは素晴らしい仕事をします。私のアプリの1つでそれをやってください。)

1
ccleve