Проводка файла и связанных данных в RESTful WebService предпочтительно в виде JSON

Вероятно, это будет глупый вопрос, но у меня одна из тех ночей. В приложении я разрабатываю API RESTful и мы хотим, чтобы клиент отправлял данные как JSON. Часть этого приложения требует, чтобы клиент загружал файл (обычно изображение), а также информацию об изображении.

Мне сложно отслеживать, как это происходит в одном запросе. Возможно ли Base64 данные файла в строку JSON? Мне нужно будет выполнить 2 сообщения на сервер? Должен ли я использовать JSON для этого?

В качестве побочного примечания мы используем Grails на бэкэнд, и к этим услугам обращаются местные мобильные клиенты (iPhone, Android и т. Д.), Если это имеет значение.

Я задал аналогичный вопрос:

Как загрузить файл с метаданными с помощью веб-службы REST?

У вас в основном есть три варианта:

  1. Base64 кодирует файл, за счет увеличения размера данных примерно на 33%.
  2. Сначала отправьте файл в POST с помощью multipart/form-data и верните идентификатор клиенту. Затем клиент отправляет метаданные с идентификатором, а сервер повторно ассоциирует файл и метаданные.
  3. Сначала отправьте метаданные и верните идентификатор клиенту. Затем клиент отправляет файл с идентификатором, а сервер повторно ассоциирует файл и метаданные.

Вы можете отправить файл и данные по одному запросу, используя тип содержимого multipart / form-data :

Во многих приложениях пользователю может быть предоставлена ​​форма. Пользователь заполнит форму, включая введенную информацию, сгенерированную пользовательским вводом или включенную из файлов, выбранных пользователем. Когда форма заполняется, данные из формы отправляются от пользователя в принимающее приложение.

Определение MultiPart / Form-Data происходит от одного из этих приложений …

С http://www.faqs.org/rfcs/rfc2388.html :

«multipart / form-data» содержит ряд частей. Предполагается, что каждая часть будет содержать заголовок Content-disposition [RFC 2183], где тип размещения является «form-data» и где расположение содержит (дополнительный) параметр «name», где значение этого параметра является оригиналом имя поля в форме. Например, часть может содержать заголовок:

Content-Disposition: form-data; имя = «пользователь»

со значением, соответствующим входу поля «пользователь».

Вы можете включать информацию о файле или информацию о поле в каждом разделе между границами. Я успешно реализовал службу RESTful, которая требовала от пользователя отправки как данных, так и формы, а multipart / form-data работали отлично. Служба была построена с использованием Java / Spring, а клиент использовал C #, поэтому, к сожалению, у меня нет примеров Grails, которые расскажут вам о том, как настроить службу. В этом случае вам не нужно использовать JSON, так как каждый раздел «form-data» предоставляет вам место для указания имени параметра и его значения.

Хорошая идея использования multipart / form-data заключается в том, что вы используете HTTP-определенные заголовки, поэтому вы придерживаетесь философии REST использования существующих инструментов HTTP для создания вашей службы.

Я знаю, что эта ветка довольно старая, но здесь я отсутствую один вариант. Если у вас есть метаданные (в любом формате), которые вы хотите отправить вместе с загружаемыми данными, вы можете сделать один запрос на multipart/related .

Тип Multipart / Related media предназначен для составных объектов, состоящих из нескольких взаимосвязанных частей тела.

Вы можете проверить спецификацию 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 и iPhone Client, которые отправляют фотографии, название и описание.

Я не знаю, лучший ли мой подход, но так просто и просто.

Я делаю снимок с помощью UIImagePickerController и отправляю на сервер NSData, используя tags заголовка запроса для отправки данных изображения.

 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:

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, а затем отправляете запрос на кодирование с файлом (снова 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); } 

https://developer.mozilla.org/en-US/docs/Web/API/FormData

Поскольку единственный недостающий пример – пример ANDROID , я добавлю его. Этот метод использует пользовательскую AsyncTask, которая должна быть объявлена ​​внутри вашего classа Activity.

 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 (eg 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(); 
 @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 } 

Мне нужно отправить несколько строк на сервер. Я не использовал 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 для отправки.

Убедитесь, что у вас есть импорт. Конечно, другой стандартный импорт

 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 form = new LinkedMultiValueMap() 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 } 

Если вы разрабатываете сервер restа, вы можете это сделать

  1. Попросите клиента выставить файл по HTTP
  2. Затем клиент может отправить URL-адрес с вашими json-данными, например файлом изображения {"file_url":"http://cockwombles.com/blah.jpg"}
  3. Затем сервер может загрузить файл.
  • Django REST Framework: добавление дополнительного поля в ModelSerializer
  • Разделить сервер и клиент API REST JSON?
  • Spring MVC - Как вернуть простую строку в JSON в Rest Controller
  • Аутентификация Token для RESTful API: следует ли периодически менять токен?
  • Каков правильный способ отправки файла из веб-сервиса REST клиенту?
  • Firefox Add-on RESTclient - Как вводить параметры POST?
  • HTTP GET с телом запроса
  • Исключение в REST Jersey
  • Как управлять версиями REST API с весной?
  • JAX-RS / Джерси, как настроить обработку ошибок?
  • Как удалить расширение .svc в службе RESTful WCF?
  • Давайте будем гением компьютера.