Publicar un Archivo y los Datos Asociados a un servicio web RESTful preferiblemente como JSON


Esta es probablemente va a ser una pregunta estúpida, pero estoy teniendo una de esas noches. En una aplicación estoy desarrollando RESTful API y queremos que el cliente envíe datos como JSON. Parte de esta aplicación requiere que el cliente cargue un archivo (generalmente una imagen), así como información sobre la imagen.

Estoy teniendo dificultades para rastrear cómo sucede esto en una sola solicitud. Es posible Base64 el archivo de datos en una cadena JSON? ¿Voy a tener que realizar 2 puestos a la servidor? ¿No debería usar JSON para esto?

Como nota al margen, estamos usando Grails en el backend y estos servicios son accedidos por clientes móviles nativos (iPhone, Android, etc.), si algo de eso hace una diferencia.

Author: Dhruvan Ganesh, 2010-11-03

11 answers

Hice una pregunta similar aquí:

¿Cómo subo un archivo con metadatos usando un servicio web REST?

Básicamente tienes tres opciones:

  1. Base64 codifica el archivo, a expensas de aumentar el tamaño de los datos en alrededor de un 33%.
  2. Envíe el archivo primero en un MENSAJE multipart/form-data y devuelva un ID al cliente. A continuación, el cliente envía los metadatos con el ID y el servidor vuelve a asociar el archivo y los metadatos.
  3. Envíe primero los metadatos, y devuelve un ID al cliente. A continuación, el cliente envía el archivo con el ID y el servidor vuelve a asociar el archivo y los metadatos.
 472
Author: Daniel T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:25

Puede enviar el archivo y los datos en una solicitud utilizando el tipo de contenido multipart/form-data:

En muchas aplicaciones, es posible que un usuario se presente con forma. El usuario rellenará el formulario, incluyendo información que se escribe, se genera por la entrada del usuario, o se incluye a partir de el usuario ha seleccionado. Cuando se rellena el formulario, los datos de la el formulario se envía desde el usuario a la aplicación receptora.

La definición de Multiparte / Formulario-Los datos se derivan de uno de los aplicación...

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

"multipart/form-data" contiene una serie de partes. Cada parte es se espera que contenga un encabezado de disposición de contenido [RFC 2183] donde el tipo de disposición es "form-data", y donde la disposición contiene un parámetro (adicional) de "name", donde el valor de ese parámetro es el nombre original del campo en el formulario. Por ejemplo, un parte puede contener un encabezado:

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

Con el valor correspondiente a la entrada del campo "usuario".

Puede incluir información de archivo o información de campo dentro de cada sección entre los límites. He implementado con éxito un servicio RESTful que requería que el usuario enviara datos y un formulario, y multipart/form-data funcionó perfectamente. El servicio fue construido usando Java / Spring, y el cliente estaba usando C#, así que desafortunadamente, no tengo ejemplos de Grails para darle sobre cómo configurar el servicio. No es necesario usar JSON en este caso, ya que cada sección "form-data" le proporciona un lugar para especificar el nombre del parámetro y su valor.

Lo bueno de usar multipart/form-data es que está utilizando encabezados definidos por HTTP, por lo que se adhiere a la filosofía REST de usar herramientas HTTP existentes para crear su servicio.

 78
Author: McStretch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-03 02:49:09

Sé que este hilo es bastante viejo, sin embargo, me falta aquí una opción. Si tiene metadatos (en cualquier formato) que desea enviar junto con los datos a cargar, puede hacer una sola solicitud multipart/related.

El tipo de medio Multiparte/Relacionado está diseñado para objetos compuestos que consisten en varias partes del cuerpo relacionadas entre sí.

Puede consultar la especificación RFC 2387 para obtener más detalles en profundidad.

Básicamente cada parte de dicha solicitud puede tener el contenido con diferentes tipos y todas las partes están de alguna manera relacionadas (por ejemplo, una imagen y metadatos de ti). Las partes se identifican por una cadena de límite, y la cadena de límite final es seguida por dos guiones.

Ejemplo:

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--
 32
Author: pgiecek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-23 15:03:28

Sé que esta pregunta es antigua, pero en los últimos días había buscado toda la red para resolver esta misma pregunta. Tengo grails REST webservices y Cliente de iPhone que envían imágenes, título y descripción.

No se si mi enfoque es el mejor, pero es tan fácil y simple.

Tomo una foto usando el controlador UIImagePickerController y envío al servidor los NSData usando las etiquetas de encabezado de request para enviar los datos de la imagen.

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];

En el lado del servidor, recibo la foto usando el código:

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")

}

No se si tengo problemas en el futuro, pero ahora está funcionando bien en el entorno de producción.

 10
Author: Rscorreia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-28 13:38:06

Aquí está mi API de enfoque (uso ejemplo) - como se puede ver, no uso ningún file_id (identyicator archivo cargado en el servidor) en API:

1.Crear objeto 'foto'en el servidor:

POST: /projects/{project_id}/photos   
params in: {name:some_schema.jpg, comment:blah}
return: photo_id

2.Subir archivo (tenga en cuenta que 'archivo' está en singular porque es solo uno por foto):

POST: /projects/{project_id}/photos/{photo_id}/file
params in: file to upload
return: -

Y luego por ejemplo:

3.Leer lista de fotos

GET: /projects/{project_id}/photos
params in: -
return: array of objects: [ photo, photo, photo, ... ]

4.Lea algunos detalles de la foto

GET: /projects/{project_id}/photos/{photo_id}
params in: -
return: photo = { id: 666, name:'some_schema.jpg', comment:'blah'}

5.Leer archivo de fotos

GET: /projects/{project_id}/photos/{photo_id}/file
params in: -
return: file content

Así que la conclusión es que, primero crear objeto (foto) por CORREO, y luego enviar solicitud secod con archivo (de nuevo POST).

 6
Author: Kamil Kiełczewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-05 15:20:53

Dado que el único ejemplo que falta es el ANDROID example, lo agregaré. Esta técnica utiliza una AsyncTask personalizada que debe declararse dentro de su clase de actividad.

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);
    }

}

Así que, cuando quieras subir tu archivo solo tienes que llamar:

new UploadFile().execute();
 6
Author: lifeisfoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-10 09:45:23

Objetos FormData: Subir Archivos Usando Ajax

XMLHttpRequest Nivel 2 añade soporte para la nueva interfaz FormData. Los objetos FormData proporcionan una forma de construir fácilmente un conjunto de pares clave/valor que representan campos de formulario y sus valores, que luego se pueden enviar fácilmente usando el método 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

 5
Author: lakhan_Ideavate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-17 13:29:47
@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
}
 0
Author: sunleo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-30 09:47:22

Quería enviar algunas cadenas al servidor backend. No utilicé json con multipart, he usado parámetros de solicitud.

@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 se vería como

http://localhost:8080/file/upload?uuid=46f073d0&type=PASSPORT

Estoy pasando dos parámetros (uuid y type) junto con la carga de archivos. Espero que esto ayude a quienes no tienen los complejos datos json para enviar.

 0
Author: Aslam anwer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-18 18:22:30

Por favor, asegúrese de que tiene la siguiente importación. Por supuesto, otras importaciones a tanto alzado

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
    }
 -5
Author: Mak Kul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-10 14:54:12

Si está desarrollando un servidor rest, puede hacer esto

  1. Haga que el cliente exponga el archivo a través de HTTP
  2. El cliente puede enviar la url con sus datos json, por ejemplo, un archivo de imagen {"file_url":"http://cockwombles.com/blah.jpg"}
  3. El servidor puede descargar el archivo.
 -5
Author: jim smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-17 14:20:07