最好以 JSON 格式将文件和相关数据发布到 RESTful WebService

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4083702/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:38:11  来源:igfitidea点击:

Posting a File and Associated Data to a RESTful WebService preferably as JSON

jsonrestgrailsfile-upload

提问by Gregg

This is probably going to be a stupid question but I'm having one of those nights. In an application I am developing RESTful API and we want the client to send data as JSON. Part of this application requires the client to upload a file (usually an image) as well as information about the image.

这可能是一个愚蠢的问题,但我正在度过那些夜晚。在一个应用程序中,我正在开发 RESTful API,我们希望客户端将数据作为 JSON 发送。此应用程序的一部分要求客户端上传文件(通常是图像)以及有关图像的信息。

I'm having a hard time tracking down how this happens in a single request. Is it possible to Base64 the file data into a JSON string? Am I going to need to perform 2 posts to the server? Should I not be using JSON for this?

我很难在单个请求中跟踪这是如何发生的。是否可以将文件数据 Base64 转换为 JSON 字符串?我需要对服务器执行 2 个帖子吗?我不应该为此使用 JSON 吗?

As a side note, we're using Grails on the backend and these services are accessed by native mobile clients (iPhone, Android, etc), if any of that makes a difference.

附带说明一下,我们在后端使用 Grails,这些服务由本地移动客户端(iPhone、Android 等)访问,如果其中任何一个有所不同的话。

采纳答案by Daniel T.

I asked a similar question here:

我在这里问了一个类似的问题:

How do I upload a file with metadata using a REST web service?

如何使用 REST Web 服务上传带有元数据的文件?

You basically have three choices:

你基本上有三个选择:

  1. Base64 encode the file, at the expense of increasing the data size by around 33%, and add processing overhead in both the server and the client for encoding/decoding.
  2. Send the file first in a multipart/form-dataPOST, and return an ID to the client. The client then sends the metadata with the ID, and the server re-associates the file and the metadata.
  3. Send the metadata first, and return an ID to the client. The client then sends the file with the ID, and the server re-associates the file and the metadata.
  1. Base64 对文件进行编码,代价是数据大小增加了约 33%,并增加了服务器和客户端的编码/解码处理开销。
  2. 首先在multipart/form-dataPOST 中发送文件,然后将 ID 返回给客户端。然后客户端发送带有 ID 的元数据,服务器重新关联文件和元数据。
  3. 先发送元数据,返回一个ID给客户端。然后客户端发送带有 ID 的文件,服务器重新关联文件和元数据。

回答by McStretch

You can send the file and data over in one request using the multipart/form-datacontent type:

您可以使用multipart/form-data内容类型在一个请求中发送文件和数据:

In many applications, it is possible for a user to be presented with a form. The user will fill out the form, including information that is typed, generated by user input, or included from files that the user has selected. When the form is filled out, the data from the form is sent from the user to the receiving application.

The definition of MultiPart/Form-Data is derived from one of those applications...

在许多应用程序中,可以向用户呈现表单。用户将填写表单,包括键入的、由用户输入生成的或从用户选择的文件中包含的信息。填写表单后,表单中的数据会从用户发送到接收应用程序。

MultiPart/Form-Data 的定义源自这些应用程序之一......

From http://www.faqs.org/rfcs/rfc2388.html:

来自http://www.faqs.org/rfcs/rfc2388.html

"multipart/form-data" contains a series of parts. Each part is expected to contain a content-disposition header [RFC 2183] where the disposition type is "form-data", and where the disposition contains an (additional) parameter of "name", where the value of that parameter is the original field name in the form. For example, a part might contain a header:

Content-Disposition: form-data; name="user"

with the value corresponding to the entry of the "user" field.

“multipart/form-data”包含一系列部分。每个部分都应包含一个内容处置标头 [RFC 2183],其中处置类型为“表单数据”,并且处置包含“名称”的(附加)参数,其中该参数的值是原始的表单中的字段名称。例如,一个部分可能包含一个标题:

内容配置:表单数据;名称=“用户”

与“用户”字段的条目对应的值。

You can include file information or field information within each section between boundaries. I've successfully implemented a RESTful service that required the user to submit both data and a form, and multipart/form-data worked perfectly. The service was built using Java/Spring, and the client was using C#, so unfortunately I don't have any Grails examples to give you concerning how to set up the service. You don't need to use JSON in this case since each "form-data" section provides you a place to specify the name of the parameter and its value.

您可以在边界之间的每个部分中包含文件信息或字段信息。我已经成功实现了一个 RESTful 服务,它要求用户同时提交数据和表单,并且 multipart/form-data 完美地工作。该服务是使用 Java/Spring 构建的,而客户端使用的是 C#,因此很遗憾,我没有任何 Grails 示例可以向您介绍如何设置该服务。在这种情况下,您不需要使用 JSON,因为每个“表单数据”部分都为您提供了一个指定参数名称及其值的位置。

The good thing about using multipart/form-data is that you're using HTTP-defined headers, so you're sticking with the REST philosophy of using existing HTTP tools to create your service.

使用 multipart/form-data 的好处是您使用的是 HTTP 定义的标头,因此您坚持使用现有 HTTP 工具来创建服务的 REST 理念。

回答by pgiecek

I know that this thread is quite old, however, I am missing here one option. If you have metadata (in any format) that you want to send along with the data to upload, you can make a single multipart/relatedrequest.

我知道这个线程已经很老了,但是,我在这里缺少一个选项。如果您有要与要上传的数据一起发送的元数据(任何格式),您可以发出一个multipart/related请求。

The Multipart/Related media type is intended for compound objects consisting of several inter-related body parts.

Multipart/Related 媒体类型适用于由几个相互关联的身体部位组成的复合对象。

You can check RFC 2387specification for more in-depth details.

您可以查看RFC 2387规范以获取更深入的详细信息。

Basically each part of such a request can have content with different type and all parts are somehow related (e.g. an image and it metadata). The parts are identified by a boundary string, and the final boundary string is followed by two hyphens.

基本上,此类请求的每个部分都可以具有不同类型的内容,并且所有部分都以某种方式相关(例如图像及其元数据)。零件由边界字符串标识,最终边界字符串后跟两个连字符。

Example:

例子:

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--

回答by Kamil Kie?czewski

Here is my approach API (i use example) - as you can see, you I don't use any file_id(uploaded file identifier to the server) in API:

这是我的方法 API(我使用示例) - 如您所见,您file_id在 API 中没有使用任何(上传到服务器的文件标识符):

  1. Create photoobject on server:

    POST: /projects/{project_id}/photos   
    body: { name: "some_schema.jpg", comment: "blah"}
    response: photo_id
    
  2. Upload file (note that fileis in singular form because it is only one per photo):

    POST: /projects/{project_id}/photos/{photo_id}/file
    body: file to upload
    response: -
    
  1. photo在服务器上创建对象:

    POST: /projects/{project_id}/photos   
    body: { name: "some_schema.jpg", comment: "blah"}
    response: photo_id
    
  2. 上传文件(注意file是单数形式,因为每张照片只有一个):

    POST: /projects/{project_id}/photos/{photo_id}/file
    body: file to upload
    response: -
    

And then for instance:

然后例如:

  1. Read photos list

    GET: /projects/{project_id}/photos
    response: [ photo, photo, photo, ... ] (array of objects)
    
  2. Read some photo details

    GET: /projects/{project_id}/photos/{photo_id}
    response: { id: 666, name: 'some_schema.jpg', comment:'blah'} (photo object)
    
  3. Read photo file

    GET: /projects/{project_id}/photos/{photo_id}/file
    response: file content
    
  1. 阅读照片列表

    GET: /projects/{project_id}/photos
    response: [ photo, photo, photo, ... ] (array of objects)
    
  2. 阅读一些照片细节

    GET: /projects/{project_id}/photos/{photo_id}
    response: { id: 666, name: 'some_schema.jpg', comment:'blah'} (photo object)
    
  3. 读取照片文件

    GET: /projects/{project_id}/photos/{photo_id}/file
    response: file content
    

So the conclusion is that, first you create an object (photo) by POST, and then you send second request with the file (again POST).

所以结论是,首先你通过 POST 创建一个对象(照片),然后你发送带有文件的第二个请求(再次 POST)。

回答by Rscorreia

I know this question is old, but in the last days I had searched whole web to solution this same question. I have grails REST webservices and iPhone Client that send pictures, title and description.

我知道这个问题很老,但在最后几天我搜索了整个网络来解决同样的问题。我有 grails REST webservices 和 iPhone Client,可以发送图片、标题和描述。

I don't know if my approach is the best, but is so easy and simple.

我不知道我的方法是否是最好的,但它是如此简单和简单。

I take a picture using the UIImagePickerController and send to server the NSData using the header tags of request to send the picture's data.

我使用 UIImagePickerController 拍照并使用请求的标头标签将 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];

At the server side, I receive the photo using the code:

在服务器端,我使用代码接收照片:

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")

}

I don't know if I have problems in future, but now is working fine in production environment.

我不知道我将来是否有问题,但现在在生产环境中运行良好。

回答by lakhan_Ideavate

FormData Objects: Upload Files Using Ajax

FormData 对象:使用 Ajax 上传文件

XMLHttpRequest Level 2 adds support for the new FormData interface. FormData objects provide a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest send() method.

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

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

回答by lifeisfoo

Since the only missing example is the ANDROID example, I'll add it. This technique uses a custom AsyncTask that should be declared inside your Activity class.

由于唯一缺少的示例是ANDROID 示例,我将添加它。此技术使用应在您的 Activity 类中声明的自定义 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);
    }

}

So, when you want to upload your file just call:

因此,当您想上传文件时,只需调用:

new UploadFile().execute();

回答by Aslam anwer

I wanted send some strings to backend server. I didnt use json with multipart, I have used request params.

我想向后端服务器发送一些字符串。我没有将 json 与 multipart 一起使用,我使用了请求参数。

@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 would look like

网址看起来像

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

I am passing two params (uuid and type) along with file upload. Hope this will help who don't have the complex json data to send.

我正在传递两个参数(uuid 和 type)以及文件上传。希望这会帮助那些没有复杂的 json 数据要发送的人。

回答by OneXer

You could try using https://square.github.io/okhttp/library. You can set the request body to multipart and then add the file and json objects separately like so:

您可以尝试使用https://square.github.io/okhttp/库。您可以将请求正文设置为 multipart,然后分别添加文件和 json 对象,如下所示:

MultipartBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("uploadFile", uploadFile.getName(), okhttp3.RequestBody.create(uploadFile, MediaType.parse("image/png")))
                .addFormDataPart("file metadata", json)
                .build();

        Request request = new Request.Builder()
                .url("https://uploadurl.com/uploadFile")
                .post(requestBody)
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

            logger.info(response.body().string());

回答by sunleo

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