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

这可能会是一个愚蠢的问题,但我有一个晚上。 在一个应用程序中,我正在开发RESTful API,我们希望客户端以JSON的forms发送数据。 这个应用程序的一部分需要客户端上传一个文件(通常是图片)以及关于图片的信息。

我很难跟踪单个请求中的情况。 是否有可能Base64文件数据到一个JSONstring? 我需要执行2个职位到服务器? 我应该不使用JSON吗?

作为一个方面说明,我们在后端使用Grails,并且这些服务被本地移动客户端(iPhone,Android等)访问,如果有任何改变的话。

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

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

你基本上有三个select:

  1. Base64对文件进行编码,代价是将数据大小增加了大约33%。
  2. 首先以multipart/form-data POST multipart/form-data发送文件,然后返回一个ID给客户端。 然后,客户端使用ID发送元数据,服务器将文件和元数据重新关联。
  3. 先发送元数据,然后返回一个ID给客户端。 然后客户端发送带有ID的文件,服务器重新关联文件和元数据。

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

在许多应用中,用户可能被呈现一个表格。 用户将填写表单,包括键入的信息,用户input生成的信息或用户select的文件中包含的信息。 填写表格后,表格中的数据将从用户发送到接收应用程序。

MultiPart / Form-Data的定义来源于这些应用程序之一。

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

“multipart / form-data”包含一系列的部分。 预期每个部分都包含一个内容处置标题[RFC 2183],其中处置types是“form-data”,处置包含一个(附加)参数“name”,其中参数的值是原始的字段名称的forms。 例如,一个零件可能包含一个标题:

内容处理:表单数据; 名称=“用户”

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

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

关于使用multipart / form-data的好处是您使用的是HTTP定义的头文件,所以您坚持使用现有HTTP工具创build您的服务的REST理念。

我知道这个线程是相当老,但是,我在这里失踪一个选项。 如果您想要随要上传的数据一起发送元数据(以任何格式),则可以进行单个multipart/related请求。

多部分/相关介质types适用于由多个相互关联的身体部位组成的复合对象。

您可以查看RFC 2387规范了解更多详细信息。

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

例:

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

我知道这个问题是旧的,但在最后几天,我search整个networking来解决这个问题。 我有grails REST web服务和iPhone客户端发送图片,标题和说明。

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

我使用UIImagePickerController拍摄一张照片,并使用请求的标头标签向服务器发送图片数据。

 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对象提供了一种方法来轻松构build一组代表表单字段和它们的值的键/值对,然后可以使用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

这里是我的方法API(我使用的例子) – 正如你所看到的,你没有在API中使用任何file_id(上传的文件identyicator在服务器):

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

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

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

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

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

 private class UploadImage 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 (eg 10%) // here you can update the user interface with the current status } @Override protected String doInBackground(Void... params) { return uploadImage(); } private String uploadImage() { String responseString = null; HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost("http://example.com/upload-picture"); 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 imageFile = new File("/my/image/path/example.jpg"); ampEntity.addPart("image", new FileBody(imageFile)); 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 UploadImage().execute(); 
 @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 } 

如果你正在开发一个rest服务器,你可以这样做

  1. 让客户端通过HTTP公开文件
  2. 客户端然后可以发送您的JSON数据的url,例如图像文件{"file_url":"http://cockwombles.com/blah.jpg"}
  3. 服务器然后可以下载文件。

请确保您有以下导入。 当然其他标准import

 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 }