Размещение файла и связанных данных в веб-сервис RESTful предпочтительно как JSON

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

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

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

11 ответов


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

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

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

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

вы можете отправить файл и данные в одном запросе, используя 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= "user"

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

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

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


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

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

Я делаю снимок с помощью 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 (загруженный файл identyicator на сервере) в 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

таким образом, вывод заключается в том, что сначала вы создаете объект (фото) по почте, а затем отправляете запрос secod с файлом (снова POST).


поскольку отсутствует пример: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();

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


@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 и тип) вместе с загрузкой файла. Надеюсь, это поможет тем, у кого нет сложных данных 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<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
    }

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

  1. у клиента есть файл через HTTP
  2. затем клиент может отправить url-адрес с вашими данными json e.g файл изображения {"file_url":"http://cockwombles.com/blah.jpg"}
  3. затем сервер может загрузить файл.