这可能是一个愚蠢的问题,但我有一个晚上.在我正在开发RESTful API的应用程序中,我们希望客户端以JSON格式发送数据.此应用程序的一部分要求客户端上载文件(通常是图像)以及有关图像的信息.
我很难跟踪单个请求中如何发生这种情况.是否可以将文件数据Base64转换为JSON字符串?我是否需要向服务器发送2个帖子?我不应该为此使用JSON吗?
作为旁注,我们在后端使用Grails,这些服务由本机移动客户端(iPhone,Android等)访问,如果其中任何一个有所不同.
我在这里问了一个类似的问题:
如何使用REST Web服务上载包含元数据的文件?
你基本上有三个选择:
Base64对文件进行编码,代价是将数据大小增加约33%.
首先在multipart/form-data
POST中发送文件,然后将ID返回给客户端.然后,客户端发送带有ID的元数据,服务器将文件和元数据重新关联.
首先发送元数据,然后将ID返回给客户端.然后,客户端发送带有ID的文件,服务器将文件和元数据重新关联.
您可以使用multipart/form-data 内容类型在一个请求中发送文件和数据:
在许多应用中,可以向用户呈现表格.用户将填写表单,包括键入的信息,由用户输入生成的信息,或者包含在用户选择的文件中的信息.填写表单后,表单中的数据将从用户发送到接收应用程序.
MultiPart/Form-Data的定义来自其中一个应用程序......
来自http://www.faqs.org/rfcs/rfc2388.html:
"multipart/form-data"包含一系列部分.每个部分都应包含内容处置标题[RFC 2183],其中处置类型为"form-data",并且处置包含"name"的(附加)参数,其中该参数的值为原始值表单中的字段名称.例如,部件可能包含标头:
内容处理:表格数据; 名称="用户"
具有与"user"字段的条目对应的值.
您可以在边界之间的每个部分中包含文件信息或字段信息.我已经成功实现了RESTful服务,该服务要求用户提交数据和表单,并且multipart/form-data完美地工作.该服务是使用Java/Spring构建的,客户端使用的是C#,所以很遗憾,我没有任何Grails示例可以为您提供有关如何设置服务的信息.在这种情况下,您不需要使用JSON,因为每个"form-data"部分都为您提供了指定参数名称及其值的位置.
使用multipart/form-data的好处在于您使用的是HTTP定义的头文件,因此您坚持使用现有HTTP工具创建服务的REST理念.
我知道这个帖子很老了,但是,我在这里缺少一个选项.如果您要将要发送的元数据(以任何格式)与要上载的数据一起发送,则可以发出单个multipart/related
请求.
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--
我知道这个问题已经过时了,但在最后几天我搜索了整个网络以解决同样的问题.我有grails REST webservices和iPhone Client发送图片,标题和描述.
我不知道我的方法是否最好,但是如此简单易行.
我使用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") }
我不知道将来是否有问题,但现在在生产环境中工作正常.
这是我的方法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).
由于唯一缺少的例子是ANDROID示例,我将添加它.此技术使用应在Activity类中声明的自定义AsyncTask.
private class UploadFile extends AsyncTask{ @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();
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