Pregunta Publicar un archivo y datos asociados en un servicio web RESTful preferiblemente como JSON


Probablemente sea una pregunta estúpida, pero estoy teniendo una de esas noches. En una aplicación, estoy desarrollando API RESTful 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.

Me está costando rastrear cómo sucede esto en una sola solicitud. ¿Es posible Base64 los datos del archivo en una cadena JSON? ¿Voy a necesitar realizar 2 publicaciones en el servidor? ¿No debería usar JSON para esto?

Como nota al margen, estamos utilizando Grails en el back-end y los clientes móviles nativos (iPhone, Android, etc.) acceden a estos servicios, si algo de eso marca la diferencia.


549
2017-11-03 02:07


origen


Respuestas:


Hice una pregunta similar aquí:

¿Cómo cargo 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 del 33%.
  2. Envíe el archivo primero en una multipart/form-data POST, y devolver una identificación al cliente. El cliente luego envía los metadatos con la ID, y el servidor vuelve a asociar el archivo y los metadatos.
  3. Envíe los metadatos primero y devuelva una identificación al cliente. El cliente luego envía el archivo con la ID, y el servidor vuelve a asociar el archivo y los metadatos.

459
2017-11-03 02:59



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

En muchas aplicaciones, es posible que se presente un usuario   una forma. El usuario completará el formulario, incluida información que   está escrito, generado por la entrada del usuario, o incluido a partir de archivos que el   el usuario ha seleccionado. Cuando se completa el formulario, los datos del   el formulario se envía desde el usuario a la aplicación receptora.

La definición de MultiPart / Form-Data se deriva de uno de esos   aplicaciones ...

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 "datos de formulario", y donde la disposición contiene   un parámetro (adicional) de "nombre", donde el valor de ese   parámetro es el nombre del campo original en el formulario. Por ejemplo, una parte   podría contener un encabezado:

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

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 límites. Implementé con éxito un servicio RESTful que requería que el usuario enviara tanto datos como un formulario, y multipart / form-data funcionó a la perfección. El servicio se creó usando Java / Spring, y el cliente estaba usando C #, por lo que desafortunadamente no tengo ningún ejemplo de Grails para darle acerca de cómo configurar el servicio. No necesita usar JSON en este caso ya que cada sección de "datos de formulario" le proporciona un lugar para especificar el nombre del parámetro y su valor.

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


75
2017-11-03 02:49



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

El tipo de medio Multipart / Related está destinado a objetos compuestos que constan de varias partes del cuerpo interrelacionadas.

Puedes comprobar RFC 2387 especificación para más detalles en profundidad.

Básicamente, cada parte de dicha solicitud puede tener contenido con diferente tipo y todas las partes están relacionadas de alguna manera (por ejemplo, una imagen y metadatos). Las partes se identifican mediante 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
2018-05-23 15:03



Sé que esta pregunta es antigua, pero en los últimos días busqué en toda la web para encontrar la solución a esta misma pregunta. Tengo los servicios web de REST y iPhone Client que envían imágenes, título y descripción.

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

Tomo una foto usando UIImagePickerController y envío al servidor NSData usando las etiquetas de encabezado de solicitud 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 sé si tengo problemas en el futuro, pero ahora funciona bien en el entorno de producción.


10
2018-01-31 17:49



Aquí está mi API de aproximación (uso el ejemplo): como puede ver, no utilizo ningún archivo_id (identificador de archivo cargado en el servidor) en la API:

1. Crear el 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 forma 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.Lista de fotos leídas

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

4.Leer 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 un archivo de foto

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

Entonces, la conclusión es que primero creas un objeto (foto) por POST, y luego envías una segunda solicitud con un archivo (nuevamente POST).


6
2017-07-10 20:31



Objetos FormData: carga archivos usando Ajax

XMLHttpRequest Level 2 agrega soporte para la nueva interfaz FormData. Los objetos FormData proporcionan una manera de construir fácilmente un conjunto de pares clave / valor que representan campos de formulario y sus valores, que luego pueden enviarse fácilmente utilizando 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
2017-07-04 09:07



Dado que el único ejemplo que falta es el Ejemplo de ANDROIDLo agregaré Esta técnica usa 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);
    }

}

Entonces, cuando quieras subir tu archivo simplemente llama:

new UploadFile().execute();

4
2017-09-13 09:40



@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
2018-03-30 09:47