在一个应用程序中,我正在开发RESTful API,我们希望客户端以JSON形式发送数据。这个应用程序的一部分要求客户端上传一个文件(通常是一个图像)以及关于图像的信息。
我很难在一个请求中找到这种情况。是否可以将文件数据Base64转换为JSON字符串?我是否需要向服务器发送2次帖子?我不应该使用JSON吗?
顺便说一句,我们在后端使用Grails,这些服务是由本地移动客户端(iPhone、Android等)访问的,如果有什么不同的话。
在一个应用程序中,我正在开发RESTful API,我们希望客户端以JSON形式发送数据。这个应用程序的一部分要求客户端上传一个文件(通常是一个图像)以及关于图像的信息。
我很难在一个请求中找到这种情况。是否可以将文件数据Base64转换为JSON字符串?我是否需要向服务器发送2次帖子?我不应该使用JSON吗?
顺便说一句,我们在后端使用Grails,这些服务是由本地移动客户端(iPhone、Android等)访问的,如果有什么不同的话。
当前回答
@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
}
其他回答
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
您可以尝试使用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());
由于唯一缺少的例子是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();
这是我的方法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}.
我知道这个问题很老了,但在过去的几天里,我搜索了整个网络来解决这个问题。我有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")
}
我不知道将来是否会有问题,但现在在生产环境中工作得很好。