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


Tengo un servicio web REST que actualmente expone esta URL:

Http://server/data/media

Donde los usuarios pueden POST el siguiente JSON:

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}

Para crear nuevos metadatos de medios.

Ahora necesito la capacidad de cargar un archivo al mismo tiempo que los metadatos multimedia. ¿Cuál es la mejor manera de hacer esto? Podría introducir una nueva propiedad llamada file y base64 codificar el archivo, pero me preguntaba si había una mejor manera.

Hay también usando multipart/form-data como lo que un formulario HTML enviaría, pero estoy usando un servicio web REST y quiero seguir usando JSON si es posible.

Author: Daniel T., 2010-10-15

5 answers

Estoy de acuerdo con Greg en que un enfoque de dos fases es una solución razonable, sin embargo, lo haría al revés. Yo haría:

POST http://server/data/media
body:
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873
}

Para crear la entrada de metadatos y devolver una respuesta como:

201 Created
Location: http://server/data/media/21323
{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentUrl": "http://server/data/media/21323/content"
}

El cliente puede entonces usar este contentURL y hacer un PUT con los datos del archivo.

Lo bueno de este enfoque es que cuando su servidor comienza a sobrecargarse con inmensos volúmenes de datos, la url que devuelve solo puede apuntar a otro servidor con más espacio/capacidad. O podría implementar algún tipo de enfoque round robin si el ancho de banda es un problema.

 160
Author: Darrel Miller,
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-10-15 01:26:10

Solo porque no esté envolviendo todo el cuerpo de la solicitud en JSON, no significa que no sea RESTful usar multipart/form-data para publicar tanto el JSON como el archivo (o varios archivos) en una sola solicitud:

curl -F "metadata=<metadata.json" -F "[email protected]" http://example.com/add-file

En el lado del servidor (usando Python como la lengua franca de programación aquí):

class AddFileResource(Resource):
    def render_POST(self, request):
        metadata = json.loads(request.args['metadata'][0])
        file_body = request.args['file'][0]
        ...

Para cargar varios archivos, es posible usar "campos de formulario" separados para cada uno:

curl -F "metadata=<metadata.json" -F "[email protected]" -F "[email protected]" http://example.com/add-file

...en cuyo caso el código del servidor tendrá request.args['file1'][0] y request.args['file2'][0]

O reutilizar el mismo para muchos:

curl -F "metadata=<metadata.json" -F "[email protected]" -F "[email protected]" http://example.com/add-file

...en cuyo caso request.args['files'] será simplemente una lista de longitud 2.

O en realidad pasar varios archivos en un solo campo de una sola vez:

curl -F "metadata=<metadata.json" -F "[email protected],some-other-file.tar.gz" http://example.com/add-file

{en cuyo caso request.args['files'] será una cadena que contiene todos los archivos, que tendrá que analizar usted mismo - no estoy seguro de cómo hacerlo, pero estoy seguro de que no es difícil, o mejor simplemente usar los enfoques anteriores.

La diferencia entre @ y < es que @ hace que el archivo se adjunte como una carga de archivo, mientras que < adjunta el contenido del archivo como un campo de texto.

P.d. Solo porque estoy usando curl como una forma de generar las solicitudes POST no significa que las mismas solicitudes HTTP no puedan ser enviadas desde un lenguaje de programación como Python o usando cualquier herramienta suficientemente capaz.

 95
Author: Erik Allik,
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-09-13 16:15:28

Una forma de abordar el problema es hacer que la carga sea un proceso de dos fases. Primero, subiría el archivo usando un POST, donde el servidor devuelve algún identificador al cliente (un identificador podría ser el SHA1 del contenido del archivo). Luego, una segunda solicitud asocia los metadatos con los datos del archivo:

{
    "Name": "Test",
    "Latitude": 12.59817,
    "Longitude": 52.12873,
    "ContentID": "7a788f56fa49ae0ba5ebde780efe4d6a89b5db47"
}

Incluir el archivo data base64 codificado en la propia solicitud JSON aumentará el tamaño de los datos transferidos en un 33%. Esto puede o no ser importante dependiendo del tamaño total del archivo.

Otro enfoque podría ser utilizar un POST de los datos del archivo sin procesar, pero incluir cualquier metadata en el encabezado de la solicitud HTTP. Sin embargo, esto queda un poco fuera de las operaciones REST básicas y puede ser más incómodo para algunas bibliotecas de clientes HTTP.

 26
Author: Greg Hewgill,
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-10-15 00:42:02

Me doy cuenta de que esta es una pregunta muy antigua, pero espero que esto ayude a alguien más cuando me topé con este post buscando lo mismo. Tuve un problema similar, solo que mis metadatos eran un Guid e int. La solución es la misma. Solo puede hacer que los metadatos necesarios formen parte de la URL.

POST aceptar método en su clase "Controlador":

public Task<HttpResponseMessage> PostFile(string name, float latitude, float longitude)
{
    //See http://stackoverflow.com/a/10327789/431906 for how to accept a file
    return null;
}

Luego, en cualquier ruta que esté registrando, WebApiConfig.Registro (HttpConfiguration config) para mí en este caso.

config.Routes.MapHttpRoute(
    name: "FooController",
    routeTemplate: "api/{controller}/{name}/{latitude}/{longitude}",
    defaults: new { }
);
 9
Author: Greg Biles,
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-02-25 16:49:57

Si su archivo y sus metadatos crean un recurso, está perfectamente bien cargarlos ambos en una solicitud. La solicitud de muestra sería:

POST https://target.com/myresources/resourcename HTTP/1.1

Accept: application/json

Content-Type: multipart/form-data; 

boundary=-----------------------------28947758029299

Host: target.com

-------------------------------28947758029299

Content-Disposition: form-data; name="application/json"

{"markers": [
        {
            "point":new GLatLng(40.266044,-74.718479), 
            "homeTeam":"Lawrence Library",
            "awayTeam":"LUGip",
            "markerImage":"images/red.png",
            "information": "Linux users group meets second Wednesday of each month.",
            "fixture":"Wednesday 7pm",
            "capacity":"",
            "previousScore":""
        },
        {
            "point":new GLatLng(40.211600,-74.695702),
            "homeTeam":"Hamilton Library",
            "awayTeam":"LUGip HW SIG",
            "markerImage":"images/white.png",
            "information": "Linux users can meet the first Tuesday of the month to work out harward and configuration issues.",
            "fixture":"Tuesday 7pm",
            "capacity":"",
            "tv":""
        },
        {
            "point":new GLatLng(40.294535,-74.682012),
            "homeTeam":"Applebees",
            "awayTeam":"After LUPip Mtg Spot",
            "markerImage":"images/newcastle.png",
            "information": "Some of us go there after the main LUGip meeting, drink brews, and talk.",
            "fixture":"Wednesday whenever",
            "capacity":"2 to 4 pints",
            "tv":""
        },
] }

-------------------------------28947758029299

Content-Disposition: form-data; name="name"; filename="myfilename.pdf"

Content-Type: application/octet-stream

%PDF-1.4
%
2 0 obj
<</Length 57/Filter/FlateDecode>>stream
x+r
26S00SI2P0Qn
F
!i\
)%[email protected]
[
endstream
endobj
4 0 obj
<</Type/Page/MediaBox[0 0 595 842]/Resources<</Font<</F1 1 0 R>>>>/Contents 2 0 R/Parent 3 0 R>>
endobj
1 0 obj
<</Type/Font/Subtype/Type1/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>
endobj
3 0 obj
<</Type/Pages/Count 1/Kids[4 0 R]>>
endobj
5 0 obj
<</Type/Catalog/Pages 3 0 R>>
endobj
6 0 obj
<</Producer(iTextSharp 5.5.11 2000-2017 iText Group NV \(AGPL-version\))/CreationDate(D:20170630120636+02'00')/ModDate(D:20170630120636+02'00')>>
endobj
xref
0 7
0000000000 65535 f 
0000000250 00000 n 
0000000015 00000 n 
0000000338 00000 n 
0000000138 00000 n 
0000000389 00000 n 
0000000434 00000 n 
trailer
<</Size 7/Root 5 0 R/Info 6 0 R/ID [<c7c34272c2e618698de73f4e1a65a1b5><c7c34272c2e618698de73f4e1a65a1b5>]>>
%iText-5.5.11
startxref
597
%%EOF

-------------------------------28947758029299--
 4
Author: Mike Ezzati,
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-09-07 14:11:40