web-dev-qa-db-ja.com

できればJSONとしてRESTful Webサービスにファイルと関連データを投稿する

これはおそらくばかげた質問になるでしょうが、私はそれらの夜のうちの1つをしています。私はRESTful APIを開発しているアプリケーションで、クライアントにJSONとしてデータを送信させたいと思います。このアプリケーションの一部では、クライアントはファイル(通常は画像)と画像に関する情報をアップロードする必要があります。

1回のリクエストでこれがどのように発生するのかを突き止めるのに苦労しています。ファイルデータをJSON文字列にBase64することは可能ですか?サーバーに2回投稿する必要がありますか。これにJSONを使用しないほうがいいでしょうか。

ちなみに、我々はバックエンドにGrailsを使用しており、これらのサービスはネイティブモバイルクライアント(iPhone、Androidなど)によってアクセスされます。

622
Gregg

私はここで同様の質問をしました:

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

基本的に3つの選択肢があります。

  1. Base64は、データサイズを約33%増加させることを犠牲にしてファイルをエンコードし、エンコードとデコードのためにサーバーとクライアントの両方に処理オーバーヘッドを追加します。
  2. 最初にmultipart/form-data POSTでファイルを送信し、クライアントにIDを返します。その後、クライアントはメタデータをIDとともに送信し、サーバーはファイルとメタデータを再度関連付けます。
  3. 最初にメタデータを送信し、そしてクライアントにIDを返します。その後、クライアントはファイルにIDを付けて送信し、サーバーはファイルとメタデータを再度関連付けます。
523
Daniel T.

multipart/form-data コンテンツタイプを使用して、ファイルとデータを1回の要求で送信できます。

多くのアプリケーションでは、ユーザーにフォームを提示することが可能です。ユーザーは、入力された情報、ユーザー入力によって生成された情報、ユーザーが選択したファイルに含まれている情報など、フォームに記入します。フォームに入力すると、フォームからのデータがユーザーから受信側アプリケーションに送信されます。

MultiPart/Form-Dataの定義は、これらのアプリケーションの1つから派生しています。

http://www.faqs.org/rfcs/rfc2388.html から

"multipart/form-data"は一連の部分を含みます。各部分には、後処理タイプが "form-data"であり、後処理が "name"の(追加の)パラメータを含むcontent-dispositionヘッダ[RFC 2183]が含まれることが期待されています。フォーム内のフィールド名たとえば、パーツにヘッダーが含まれているとします。

内容処理:フォームデータ。 name = "ユーザー"

"user"フィールドのエントリに対応する値を持ちます。

境界間の各セクション内にファイル情報またはフィールド情報を含めることができます。ユーザーがデータとフォームの両方を送信することを要求するRESTfulサービスを正常に実装し、multipart/form-dataが完全に機能しました。このサービスはJava/Springを使用して構築され、クライアントはC#を使用していたので、残念ながらこのサービスの設定方法に関するGrailsの例はありません。この場合、JSONを使用する必要はありません。各 "form-data"セクションには、パラメータの名前とその値を指定するための場所が用意されているからです。

Multipart/form-dataを使用することの良いところは、HTTP定義のヘッダを使用しているということです。そのため、既存のHTTPツールを使用してサービスを作成するというREST哲学に従います。

89
McStretch

私はこのスレッドがかなり古くなっていることを知っています、しかし、私はここで1つのオプションが欠けています。アップロードするデータと一緒に送信するメタデータ(任意の形式)がある場合は、multipart/relatedリクエストを1回作成できます。

Multipart/Relatedメディアタイプは、いくつかの相互関連のあるボディパーツからなる複合オブジェクトを対象としています。

より詳細な情報については RFC 2387 specificationをチェックすることができます。

基本的に、そのような要求の各部分は異なるタイプのコンテンツを持つことができ、すべての部分は何らかの形で関連しています(たとえば、画像とそのメタデータ)。部分は境界ストリングによって識別され、最後の境界ストリングの後には2つのハイフンが続きます。

例:

POST /upload HTTP/1.1
Host: www.hostname.com
Content-Type: multipart/related; boundary=xyz
Content-Length: [actual-content-length]

--xyz
Content-Type: application/json; charset=UTF-8

{
    "name": "Sample image",
    "desc": "...",
    ...
}

--xyz
Content-Type: image/jpeg

[image data]
[image data]
[image data]
...
--foo_bar_baz--
39
pgiecek

私はこの質問が古いことを知っています、しかし最近私はこの同じ質問を解決するためにウェブ全体を検索しました。写真、タイトル、説明文を送信するgrails REST webサービスとiPhoneクライアントがあります。

私のアプローチが最善かどうかはわかりませんが、それはとても簡単で簡単です。

UIImagePickerControllerを使って写真を撮り、requestのヘッダータグを使ってNSDataをサーバーに送信し、写真のデータを送信します。

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"myServerAddress"]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:UIImageJPEGRepresentation(picture, 0.5)];
[request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"myPhotoTitle" forHTTPHeaderField:@"Photo-Title"];
[request setValue:@"myPhotoDescription" forHTTPHeaderField:@"Photo-Description"];

NSURLResponse *response;

NSError *error;

[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

サーバーサイドで、私はコードを使って写真を受け取ります:

InputStream is = request.inputStream

def receivedPhotoFile = (IOUtils.toByteArray(is))

def photo = new Photo()
photo.photoFile = receivedPhotoFile //photoFile is a transient attribute
photo.title = request.getHeader("Photo-Title")
photo.description = request.getHeader("Photo-Description")
photo.imageURL = "temp"    

if (photo.save()) {    

    File saveLocation = grailsAttributes.getApplicationContext().getResource(File.separator + "images").getFile()
    saveLocation.mkdirs()

    File tempFile = File.createTempFile("photo", ".jpg", saveLocation)

    photo.imageURL = saveLocation.getName() + "/" + tempFile.getName()

    tempFile.append(photo.photoFile);

} else {

    println("Error")

}

将来問題があるかどうかはわかりませんが、現在は実稼働環境で問題なく機能しています。

11
Rscorreia

これが私のアプローチAPIです(私は例を使用します) - ご覧のとおり、APIではfile_id(サーバーにアップロードされたファイル識別子)を使用していません。

1.サーバー上に「写真」オブジェクトを作成します。

POST: /projects/{project_id}/photos   
params in: {name:some_schema.jpg, comment:blah}
return: photo_id

2.ファイルをアップロードします(「ファイル」は写真ごとに1つしかないため、単数形です)。

POST: /projects/{project_id}/photos/{photo_id}/file
params in: file to upload
return: -

そして、例えば:

3.写真リストを読む

GET: /projects/{project_id}/photos
params in: -
return: array of objects: [ photo, photo, photo, ... ]

4.写真の詳細を読む

GET: /projects/{project_id}/photos/{photo_id}
params in: -
return: photo = { id: 666, name:'some_schema.jpg', comment:'blah'}

5.写真ファイルを読む

GET: /projects/{project_id}/photos/{photo_id}/file
params in: -
return: file content

つまり、最初にPOSTでオブジェクト(写真)を作成し、次にファイルを使用して2番目のリクエスト(POST)を送信します。

6

足りない例は Androidの例 だけなので、追加します。この手法では、アクティビティクラス内で宣言する必要があるカスタムAsyncTaskを使用します。

private class UploadFile extends AsyncTask<Void, Integer, String> {
    @Override
    protected void onPreExecute() {
        // set a status bar or show a dialog to the user here
        super.onPreExecute();
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        // progress[0] is the current status (e.g. 10%)
        // here you can update the user interface with the current status
    }

    @Override
    protected String doInBackground(Void... params) {
        return uploadFile();
    }

    private String uploadFile() {

        String responseString = null;
        HttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost("http://example.com/upload-file");

        try {
            AndroidMultiPartEntity ampEntity = new AndroidMultiPartEntity(
                new ProgressListener() {
                    @Override
                        public void transferred(long num) {
                            // this trigger the progressUpdate event
                            publishProgress((int) ((num / (float) totalSize) * 100));
                        }
            });

            File myFile = new File("/my/image/path/example.jpg");

            ampEntity.addPart("fileFieldName", new FileBody(myFile));

            totalSize = ampEntity.getContentLength();
            httpPost.setEntity(ampEntity);

            // Making server call
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                responseString = EntityUtils.toString(httpEntity);
            } else {
                responseString = "Error, http status: "
                        + statusCode;
            }

        } catch (Exception e) {
            responseString = e.getMessage();
        }
        return responseString;
    }

    @Override
    protected void onPostExecute(String result) {
        // if you want update the user interface with upload result
        super.onPostExecute(result);
    }

}

それで、あなたがあなたのファイルをアップロードしたい時にちょうど電話をかけなさい:

new UploadFile().execute();
5
lifeisfoo

FormDataオブジェクト:Ajaxを使用したファイルのアップロード

XMLHttpRequest Level 2では、新しいFormDataインターフェイスのサポートが追加されました。 FormDataオブジェクトは、フォームフィールドとその値を表す一連のキーと値のペアを簡単に作成する方法を提供します。これは、XMLHttpRequestのsend()メソッドを使用して簡単に送信できます。

function AjaxFileUpload() {
    var file = document.getElementById("files");
    //var file = fileInput;
    var fd = new FormData();
    fd.append("imageFileData", file);
    var xhr = new XMLHttpRequest();
    xhr.open("POST", '/ws/fileUpload.do');
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
             alert('success');
        }
        else if (uploadResult == 'success')
             alert('error');
    };
    xhr.send(fd);
}

https://developer.mozilla.org/en-US/docs/Web/API/FormData

5
lakhan_Ideavate

私はいくつかの文字列をバックエンドサーバーに送信したいと思いました。私はmultipartでjsonを使わなかった、私はrequest paramsを使った。

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public void uploadFile(HttpServletRequest request,
        HttpServletResponse response, @RequestParam("uuid") String uuid,
        @RequestParam("type") DocType type,
        @RequestParam("file") MultipartFile uploadfile)

URLは次のようになります

http://localhost:8080/file/upload?uuid=46f073d0&type=PASSPORT

ファイルのアップロードと共に2つのパラメータ(uuidとtype)を渡しています。これが、送信する複雑なJSONデータを持たない人に役立つことを願っています。

1
Aslam anwer
@RequestMapping(value = "/uploadImageJson", method = RequestMethod.POST)
    public @ResponseBody Object jsongStrImage(@RequestParam(value="image") MultipartFile image, @RequestParam String jsonStr) {
-- use  com.fasterxml.jackson.databind.ObjectMapper convert Json String to Object
}
0
sunleo