首页 文章

将文件和关联数据发布到RESTful WebService,最好是JSON

提问于
浏览
577

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

我很难在单个请求中跟踪这种情况 . 是否可以将文件数据Base64转换为JSON字符串?我需要在服务器上执行2个帖子吗?我不应该为此使用JSON吗?

作为旁注,我们在后端使用Grails,这些服务由本机移动客户端(iPhone,Android等)访问,如果其中任何一个有所不同 .

11 回答

  • 483

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

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

    你基本上有三个选择:

    • Base64对文件进行编码,代价是将数据大小增加约33% .

    • 首先在 multipart/form-data POST中发送文件,然后将ID返回给客户端 . 然后,客户端发送带有ID的元数据,服务器重新关联文件和元数据 .

    • 首先发送元数据,然后将ID返回给客户端 . 然后,客户端发送带有ID的文件,服务器将文件和元数据重新关联 .

  • 0

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

    在许多应用程序中,可以向用户呈现表单 . 用户将填写表单,包括键入的信息,由用户输入生成的信息,或包含在用户选择的文件中的信息 . 填写表单后,表单中的数据将从用户发送到接收应用程序 . MultiPart / Form-Data的定义来自其中一个应用程序......

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

    “multipart / form-data”包含一系列部分 . 每个部分都应包含内容处置 Headers [RFC 2183],其中处置类型为“form-data”,并且处置包含“name”的(附加)参数,其中该参数的值为原始值表单中的字段名称 . 例如,一个部分可能包含一个 Headers :Content-Disposition:form-data; name =“user”,其值对应于“user”字段的条目 .

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

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

  • 10

    我想发送一些字符串到后端服务器 . 我没有使用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)
    

    网址看起来像

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

    我正在传递两个参数(uuid和type)以及文件上传 . 希望这将有助于谁没有复杂的json数据发送 .

  • -6
    @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
    }
    
  • 1

    由于唯一缺少的例子是 ANDROID example ,我会添加它 . 此技术使用应在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);
        }
    
    }
    

    因此,当您要上传文件时,只需致电:

    new UploadFile().execute();
    
  • 33

    如果您正在开发休息服务器,则可以执行此操作

    • 让客户端通过HTTP公开文件

    • 客户端然后可以使用您的json数据发送url,例如图像文件 {"file_url":"http://cockwombles.com/blah.jpg"}

    • 然后服务器可以下载该文件 .

  • 6

    我知道这个帖子很老了,但是,我在这里错过了一个选项 . 如果您要将要发送的元数据(以任何格式)与要上载的数据一起发送,则可以发出单个 multipart/related 请求 .

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

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

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

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

    这是我的方法API(我使用示例) - 正如您所看到的,您在API中不使用任何file_id(在服务器中上传的文件identyicator):

    1.在服务器上创建“照片”对象:

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

    2.上传文件(注意'文件'是单数形式,因为每张照片只有一个):

    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创建对象(照片),然后然后你发送带文件的secod请求(再次POST) .

  • -5

    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

  • 6

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

    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
        }
    
  • 5

    我知道这个问题已经过时了,但是在最后几天我搜索了整个网络以解决同样的问题 . 我有grails REST webservices和iPhone Client发送图片, Headers 和描述 .

    我不知道我的方法是否最好,但是简单易行 .

    我使用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")
    
    }
    

    我不知道将来是否有问题,但现在在 生产环境 环境中工作正常 .

相关问题