在一个应用程序中,我正在开发RESTful API,我们希望客户端以JSON形式发送数据。这个应用程序的一部分要求客户端上传一个文件(通常是一个图像)以及关于图像的信息。

我很难在一个请求中找到这种情况。是否可以将文件数据Base64转换为JSON字符串?我是否需要向服务器发送2次帖子?我不应该使用JSON吗?

顺便说一句,我们在后端使用Grails,这些服务是由本地移动客户端(iPhone、Android等)访问的,如果有什么不同的话。


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

在许多应用程序中,可能会向用户显示 一种形式。用户将填写表单,包括以下信息 类型的、由用户输入生成的或包含在 用户已选择。表单填写后,从 表单从用户发送到接收应用程序。 MultiPart/Form-Data的定义就是从这些定义中派生出来的 应用程序…

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

“multipart/form-data”包含一系列的部分。每个部分是 期望包含一个内容处理头[RFC 2183] 处置类型为“form-data”,其中处置包含 一个(额外的)参数“name”,其中的值 参数是表单中的原始字段名。例如,一个部分 可能包含一个头文件: 附加:格式;name = "用户" 与“user”字段的条目对应的值。

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.

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

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

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

你基本上有三个选择:

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. Send the file first in a multipart/form-data POST, 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. 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.

我知道这个问题很老了,但在过去的几天里,我搜索了整个网络来解决这个问题。我有grails REST web服务和iPhone客户端发送图片,标题和描述。

我不知道我的方法是不是最好的,但是很简单。

我使用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];

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

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

}

我不知道将来是否会有问题,但现在在生产环境中工作得很好。

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

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

由于唯一缺少的例子是ANDROID的例子,我将添加它。 该技术使用一个自定义AsyncTask,应该在Activity类中声明。

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();

请确保您有以下导入。当然还有其他标准的进口

import org.springframework.core.io.FileSystemResource


    void uploadzipFiles(String token) {

        RestBuilder rest = new RestBuilder(connectTimeout:10000, readTimeout:20000)

        def zipFile = new File("testdata.zip")
        def Id = "001G00000"
        MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>()
        form.add("id", id)
        form.add('file',new FileSystemResource(zipFile))
        def urld ='''http://URL''';
        def resp = rest.post(urld) {
            header('X-Auth-Token', clientSecret)
            contentType "multipart/form-data"
            body(form)
        }
        println "resp::"+resp
        println "resp::"+resp.text
        println "resp::"+resp.headers
        println "resp::"+resp.body
        println "resp::"+resp.status
    }

我知道这个线程是相当旧的,但是,我在这里错过了一个选项。如果您有元数据(任何格式),希望将其与要上传的数据一起发送,则可以发出单个多部分/相关请求。

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

您可以查看RFC 2387规范以获得更深入的细节。

基本上,这样的请求的每个部分都可以有不同类型的内容,并且所有部分都以某种方式相关(例如,图像和它的元数据)。各部分由一个边界字符串标识,最后的边界字符串后面跟着两个连字符。

例子:

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

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

在服务器上创建照片对象: 职位:/项目/ {project_id} /照片 正文:{名称:"some_schema.jpg",评论:"blah"} 回应:photo_id 上传文件(注意文件是单数形式,因为每张照片只有一个): 职位:/项目/ {project_id} / {photo_id} /文件/照片 正文:要上传的文件 响应:

举个例子:

阅读照片列表 得到:/项目/ {project_id} /照片 回复:[照片,照片,照片,…](对象数组) 阅读一些照片细节 得到:/项目/ {project_id} /照片/ {photo_id} 响应:{id: 666,名称:' some_schema.jpg',评论:'blah'}(照片对象) 读取照片文件 得到:/项目/ {project_id} / {photo_id} /文件/照片 响应:文件内容

So the conclusion is that, first you create an object (photo) by POST, and then you send second request with the file (again POST). To not have problems with CACHE in this approach we assume that we can only delete old photos and add new - no update binary photo files (because new binary file is in fact... NEW photo). However if you need to be able to update binary files and cache them, then in point 4 return also fileId and change 5 to GET: /projects/{project_id}/photos/{photo_id}/files/{fileId}.

我想发送一些字符串到后端服务器。我没有使用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看起来像这样

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

我在文件上传时传递了两个参数(uuid和type)。 希望这将帮助谁没有复杂的json数据发送。

您可以尝试使用https://square.github.io/okhttp/ library。 你可以设置请求主体为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());