Публикация файла и связанных данных на RESTful WebService предпочтительно в виде JSON - PullRequest
643 голосов
/ 03 ноября 2010

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

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

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

Ответы [ 10 ]

538 голосов
/ 03 ноября 2010

Я задал похожий вопрос здесь:

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

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

  1. Base64 кодирует файл за счет увеличения размера данных примерно на 33% и добавляет накладные расходы на обработку как на сервере, так и на клиенте для кодирования / декодирования.
  2. Отправка файласначала в multipart/form-data POST и верните ID клиенту.Затем клиент отправляет метаданные с идентификатором, а сервер повторно связывает файл и метаданные.
  3. Сначала отправьте метаданные и верните идентификатор клиенту.Затем клиент отправляет файл с идентификатором, а сервер повторно связывает файл и метаданные.
91 голосов
/ 03 ноября 2010

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

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

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

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

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

Content-Disposition: form-data;name = "user"

со значением, соответствующим записи в поле "user".

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

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

41 голосов
/ 23 мая 2016

Я знаю, что эта ветка довольно старая, однако мне здесь не хватает одного варианта. Если у вас есть метаданные (в любом формате), которые вы хотите отправить вместе с данными для загрузки, вы можете сделать один запрос 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--
11 голосов
/ 31 января 2012

Я знаю, что этот вопрос старый, но в последние дни я искал всю сеть, чтобы решить тот же вопрос. У меня есть веб-сервисы 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")

}

Я не знаю, возникнут ли у меня проблемы в будущем, но сейчас работает нормально в производственной среде.

6 голосов
/ 10 июля 2016

Вот мой подход 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, а затем отправляете второй запрос с файлом (снова POST).

6 голосов
/ 13 сентября 2015

Поскольку единственный отсутствующий пример - это 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();
5 голосов
/ 04 июля 2014

Объекты FormData: загрузка файлов с использованием Ajax

XMLHttpRequest Уровень 2 добавляет поддержку нового интерфейса FormData.Объекты FormData позволяют легко создавать набор пар ключ / значение, представляющих поля формы и их значения, которые затем можно легко отправить с помощью метода sendH) XMLHttpRequest.

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

1 голос
/ 01 сентября 2018

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

0 голосов
/ 10 октября 2015

Пожалуйста, убедитесь, что у вас есть следующий импорт. Конечно, другой стандартный импорт

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
    }
0 голосов
/ 30 марта 2015
@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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...