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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

Из http://www.faqs.org/rfcs/rfc2388.html:

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

Content-Disposition: form-data; name= "пользователь"

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

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

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

Ответ 3

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

Ответ 4

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

}

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

Ответ 5

Поскольку единственный недостающий пример - пример 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();

Ответ 6

Вот мой подход 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).

Ответ 7

Объекты 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

Ответ 8

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

Ответ 9

@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
}

Ответ 10

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

  • Попросите клиента выставить файл через HTTP
  • Затем клиент может отправить URL-адрес с вашими данными json, например, файлом изображения {"file_url":"http://cockwombles.com/blah.jpg"}
  • Затем сервер может загрузить файл.

Ответ 11

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

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
    }