Публикация файла и связанных данных на RESTful WebService предпочтительно в виде JSON
Вероятно, это будет глупый вопрос, но у меня одна из тех ночей. В приложении я разрабатываю RESTful API, и мы хотим, чтобы клиент отправлял данные в формате JSON. Часть этого приложения требует, чтобы клиент загружал файл (обычно изображение), а также информацию об изображении.
Мне трудно отследить, как это происходит в одном запросе. Можно ли Base64 данных файла в строку JSON? Я собираюсь выполнить 2 сообщения на сервере? Я не должен использовать JSON для этого?
В качестве дополнительного примечания мы используем Grails на бэкэнде, и к этим службам обращаются нативные мобильные клиенты (iPhone, Android и т. Д.), Если что-то из этого имеет значение.
11 ответов
Я задал подобный вопрос здесь:
Как загрузить файл с метаданными с помощью веб-службы REST?
У вас есть три варианта:
- Base64 кодирует файл за счет увеличения размера данных примерно на 33%.
- Отправьте файл первым в
multipart/form-data
POST и верните ID клиенту. Затем клиент отправляет метаданные с идентификатором, а сервер повторно связывает файл и метаданные. - Сначала отправьте метаданные и верните идентификатор клиенту. Затем клиент отправляет файл с идентификатором, а сервер повторно связывает файл и метаданные.
Вы можете отправить файл и данные за один запрос, используя тип содержимого multipart/form-data:
Во многих приложениях пользователю может быть предоставлена форма. Пользователь заполняет форму, включая информацию, которая вводится, генерируется с помощью ввода пользователя или включается из файлов, выбранных пользователем. Когда форма заполнена, данные из формы отправляются от пользователя к получающему приложению.
Определение MultiPart/Form-Data получено из одного из этих приложений...
С http://www.faqs.org/rfcs/rfc2388.html:
"multipart/form-data" содержит ряд частей. Ожидается, что каждая часть будет содержать заголовок размещения содержимого [RFC 2183], где тип расположения - "данные формы", а расположение - (дополнительный) параметр "name", где значение этого параметра является исходным Имя поля в форме. Например, часть может содержать заголовок:
Content-Disposition: форма-данные; имя = "пользователь"
со значением, соответствующим записи в поле "пользователь".
Вы можете включить информацию о файле или информацию о поле в каждый раздел между границами. Я успешно реализовал сервис RESTful, который требовал от пользователя отправки как данных, так и формы, и multipart/form-data работали отлично. Служба была построена с использованием Java/Spring, а клиент использовал C#, поэтому, к сожалению, у меня нет примеров Grails, чтобы дать вам информацию о том, как настроить службу. Вам не нужно использовать JSON в этом случае, так как каждый раздел "form-data" предоставляет вам место для указания имени параметра и его значения.
Преимущество использования multipart/form-data заключается в том, что вы используете HTTP-определенные заголовки, поэтому вы придерживаетесь философии REST об использовании существующих инструментов HTTP для создания вашего сервиса.
Я знаю, что эта ветка довольно старая, но мне здесь не хватает одного варианта. Если у вас есть метаданные (в любом формате), которые вы хотите отправить вместе с данными для загрузки, вы можете сделать один 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 (я использую пример) - как вы можете видеть, вы не используете file_id (идентификатор загруженного файла на сервере) в API:
1. Создайте объект 'photo' на сервере:
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).
Я знаю, что этот вопрос старый, но в последние дни я искал всю сеть, чтобы решить тот же вопрос. У меня есть веб-сервисы Grails REST и 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")
}
Я не знаю, возникнут ли у меня проблемы в будущем, но сейчас работает нормально в производственной среде.
Объекты 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);
}
Поскольку единственным отсутствующим примером является пример 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();
Я хотел отправить несколько строк на бэкэнд-сервер. Я не использовал 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)
URL будет выглядеть
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());
@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
}
Если вы разрабатываете сервер отдыха, вы можете сделать это
- Пусть клиент выставит файл через HTTP
- Затем клиент может отправить URL с вашими данными JSON, например, файл изображения
{"file_url":"http://cockwombles.com/blah.jpg"}
- Затем сервер может загрузить файл.