在我正在开发 RESTful API 的应用程序中,我们希望客户端以 JSON 格式发送数据。此应用程序的一部分要求客户端上传文件(通常是图像)以及有关图像的信息。
我很难追踪这在单个请求中是如何发生的。是否可以将文件数据 Base64 转换为 JSON 字符串?我需要向服务器执行 2 个帖子吗?我不应该为此使用 JSON 吗?
附带说明一下,我们在后端使用 Grails,并且这些服务由本地移动客户端(iPhone、Android 等)访问,如果其中有任何影响的话。
我在这里问了一个类似的问题:
How do I upload a file with metadata using a REST web service?
你基本上有三个选择:
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],其中处置类型为“表单数据”,处置包含“名称”的(附加)参数,其中该参数的值是原始的表单中的字段名称。例如,一个部分可能包含一个标题: Content-Disposition: form-data; name="user",其值对应于“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--
这是我的方法 API(我使用示例) - 如您所见,您在 API 中没有使用任何 file_id
(将文件标识符上传到服务器):
在服务器上创建照片对象: POST: /projects/{project_id}/photos body: { name: "some_schema.jpg", comment: "blah"} response: photo_id 上传文件(注意文件是单数形式,因为它只是每张照片一张):POST:/projects/{project_id}/photos/{photo_id}/file body:要上传的文件响应:-
然后例如:
读取照片列表 GET:/projects/{project_id}/photos 响应:[ photo, photo, photo, ... ](对象数组) 读取一些照片详细信息 GET:/projects/{project_id}/photos/{photo_id} 响应: { id: 666, name: 'some_schema.jpg', comment:'blah'} (照片对象) 读取照片文件 GET: /projects/{project_id}/photos/{photo_id}/file 响应:文件内容
所以结论是,首先你通过 POST 创建一个对象(照片),然后你发送带有文件的第二个请求(再次 POST)。为了在这种方法中没有 CACHE 问题,我们假设我们只能删除旧照片并添加新照片 - 不能更新二进制照片文件(因为新的二进制文件实际上是......新照片)。但是,如果您需要能够更新二进制文件并缓存它们,那么在点 4
中还返回 fileId
并将 5
更改为 GET:/projects/{project_id}/photos/{photo_id}/files/{文件ID}。
我知道这个问题很老,但在过去的几天里,我搜索了整个网络来解决同样的问题。我有发送图片、标题和描述的 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
由于唯一缺少的示例是 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);
}
}
因此,当您要上传文件时,只需调用:
new UploadFile().execute();
我想向后端服务器发送一些字符串。我没有使用多部分的 json,我使用了请求参数。
@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 和类型)以及文件上传。希望这会帮助那些没有复杂的 json 数据要发送的人。
您可以尝试使用 https://square.github.io/okhttp/ 库。您可以将请求正文设置为多部分,然后分别添加文件和 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());
@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
}
请确保您有以下导入。当然其他标准进口
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
}
java.lang.ClassCastException: org.springframework.core.io.FileSystemResource cannot be cast to java.lang.String