这可能是一个愚蠢的问题,但我有一个夜晚。在一个应用程序中,我正在开发RESTful API,我们希望客户端将数据作为JSON发送。此应用程序的一部分要求客户端上载文件(通常是图像)以及有关该图像的信息。
我很难跟踪单个请求中的情况。是否可以将文件数据Base64转换为JSON字符串?我需要在服务器上执行2个帖子吗?我不应该为此使用JSON吗?
附带说明一下,我们在后端使用Grails,并且本机移动客户端(iPhone,Android等)可以访问这些服务(如果有区别的话)。
这可能是一个愚蠢的问题,但我有一个夜晚。在一个应用程序中,我正在开发RESTful API,我们希望客户端将数据作为JSON发送。此应用程序的一部分要求客户端上载文件(通常是图像)以及有关该图像的信息。
我很难跟踪单个请求中的情况。是否可以将文件数据Base64转换为JSON字符串?我需要在服务器上执行2个帖子吗?我不应该为此使用JSON吗?
附带说明一下,我们在后端使用Grails,并且本机移动客户端(iPhone,Android等)可以访问这些服务(如果有区别的话)。
Answers:
我在这里问了类似的问题:
您基本上有三个选择:
multipart/form-data
POST中发送文件,然后将ID返回给客户端。然后,客户端发送带有ID的元数据,然后服务器将文件和元数据重新关联。您可以使用multipart / form-data 内容类型在一个请求中发送文件和数据:
在许多应用中,有可能向用户呈现表格。用户将填写该表单,包括键入的信息,通过用户输入生成的信息或包含在用户选择的文件中的信息。填写表单后,表单中的数据将从用户发送到接收应用程序。
MultiPart / Form-Data的定义源自这些应用程序之一。
从http://www.faqs.org/rfcs/rfc2388.html:
“ multipart / form-data”包含一系列部分。每个部分均应包含一个内容处理标头[RFC 2183],其中处理类型为“ form-data”,并且该处理包含一个(附加)参数“ name”,其中该参数的值为原始值表单中的字段名称。例如,零件可能包含标题:
内容处置:表单数据;名称=“用户”
其值与“用户”字段的输入相对应。
您可以在边界之间的每个部分中包括文件信息或字段信息。我已经成功实现了RESTful服务,该服务要求用户提交数据和表单,并且multipart / form-data可以完美地工作。该服务是使用Java / Spring构建的,而客户端使用的是C#,因此,不幸的是,我没有任何Grails示例可以向您提供有关如何设置服务的信息。在这种情况下,您不需要使用JSON,因为每个“表单数据”部分都为您提供了一个位置,用于指定参数的名称及其值。
使用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 Web服务和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(我使用的示例)-如您所见,您file_id
在API中不使用任何(将文件标识符上传到服务器):
photo
在服务器上创建对象:
POST: /projects/{project_id}/photos
body: { name: "some_schema.jpg", comment: "blah"}
response: photo_id
上载文件(请注意file
采用单数形式,因为每张照片只有一个):
POST: /projects/{project_id}/photos/{photo_id}/file
body: file to upload
response: -
然后例如:
阅读照片列表
GET: /projects/{project_id}/photos
response: [ photo, photo, photo, ... ] (array of objects)
阅读一些照片细节
GET: /projects/{project_id}/photos/{photo_id}
response: { id: 666, name: 'some_schema.jpg', comment:'blah'} (photo object)
读取照片文件
GET: /projects/{project_id}/photos/{photo_id}/file
response: file content
因此得出的结论是,首先通过POST创建对象(照片),然后发送带有文件的第二个请求(同样是POST)。
FormData对象:使用Ajax上传文件
XMLHttpRequest级别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);
}
由于唯一缺少的示例是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与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数据发送的人有所帮助。
您可以尝试使用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());
请确保您具有以下导入。当然其他标准进口
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