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.
11 answers
Hice una pregunta similar aquí:
¿Cómo subo un archivo con metadatos usando un servicio web REST?
Básicamente tienes tres opciones:
- Base64 codifica el archivo, a expensas de aumentar el tamaño de los datos en alrededor de un 33%.
- 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. - 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.
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.
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--
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.
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).
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();
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);
}
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
}
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.
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
}
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
- Haga que el cliente exponga el archivo a través de HTTP
- El cliente puede enviar la url con sus datos json, por ejemplo, un archivo de imagen
{"file_url":"http://cockwombles.com/blah.jpg"}
- El servidor puede descargar el archivo.
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