2010-10-15 20 views
188

Tengo un servicio web REST que actualmente expone esta URL:¿Cómo cargo un archivo con metadatos usando un servicio web REST?

http://server/data/media

donde los usuarios pueden POST el siguiente JSON:

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

con el fin de crear una nueva metadatos de medios.

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

También está usando multipart/form-data como lo que enviaría un formulario HTML, pero estoy usando un servicio web REST y quiero mantener el uso de JSON si es posible.

+26

El pegarse a usar solamente JSON no es realmente necesario tener una web REST Servicio. REST es básicamente todo lo que sigue los principios básicos de los métodos HTTP y algunas otras reglas (posiblemente no estandarizadas). –

Respuesta

150

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. 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" 
} 

continuación, el cliente puede utilizar este contentURL y hacer un PUT con los datos del archivo.

Lo bueno de este enfoque es que cuando el servidor comienza a estar agobiado por inmensos volúmenes de datos, la URL que usted devuelve puede apuntar a otro servidor con más espacio/capacidad. O podría implementar algún tipo de aproximación por turnos si el ancho de banda es un problema.

+5

Una ventaja de enviar el contenido primero es que, en el momento en que existen los metadatos, el contenido ya está presente. En última instancia, la respuesta correcta depende de la organización de los datos en el sistema. –

+0

Gracias, marqué esta como la respuesta correcta porque esto es lo que quería hacer. Desafortunadamente, debido a una regla comercial extraña, tenemos que permitir que la carga se realice en cualquier orden (metadatos primero o archivo primero). Me preguntaba si había una manera de combinar los dos para evitar el dolor de cabeza de lidiar con ambas situaciones. –

+0

@Daniel Si PUBLICA primero el archivo de datos, puede tomar la URL devuelta en Ubicación y agregarla al atributo ContentUrl en los metadatos. De esta forma, cuando el servidor recibe los metadatos, si existe una ContentUrl, entonces ya sabe dónde está el archivo. Si no hay ContentUrl, entonces sabe que debe crear uno. –

26

Una forma de abordar el problema es hacer que la carga sea un proceso de dos fases. En primer lugar, debería cargar 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). A continuación, una segunda solicitud asocia los metadatos con los datos del archivo:

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

Incluyendo la base64 datos de archivo codificado en la solicitud JSON sí aumentará el tamaño de los datos transferidos en un 33%. Esto puede o no ser importante dependiendo del tamaño general del archivo.

Otro enfoque podría ser utilizar un POST de los datos del archivo sin procesar, pero incluir cualquier metadato en el encabezado de 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.

+0

Puede usar Ascii85 aumentando solo en 1/4. – Singagirl

91

El hecho de que usted no está envolviendo todo el cuerpo de la petición en JSON, no quería decir que no es REST utilizar multipart/form-data para publicar tanto el JSON y el archivo (o varios archivos) en una sola petición:

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 mu ltiple archivos, es posible utilizar cualquiera de los dos "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 volver a usar 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 efectivamente transcurridos 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'] habrá una cadena que contiene todos los archivos, que usted tendrá que analizar a ti mismo - no seguro de cómo hacerlo, pero estoy seguro de que no es difícil, o mejor simplemente use los enfoques anteriores.

La diferencia entre @ y < es que @ hace que el archivo a quedar atado como un archivo de carga, mientras que < concede el contenido del archivo como un campo de texto.

P.S. El hecho de que esté usando curl como una forma de generar las solicitudes POST no significa que las mismas solicitudes HTTP no pudieron ser enviadas desde un lenguaje de programación como Python o usando cualquier herramienta suficientemente capaz.

+4

Me he estado preguntando acerca de este enfoque yo mismo, y por qué no había visto a nadie más presentarlo aún. Estoy de acuerdo, parece perfectamente RESTANTE para mí. – soupdog

+1

¡SÍ! Este es un enfoque muy práctico, y no es menos RESTful que usar "application/json" como tipo de contenido para toda la solicitud. – sickill

+0

..pero eso solo es posible si tiene los datos en a.json file y subirlo, que no es el caso – itsjavi

9

Me doy cuenta de que esta es una pregunta muy antigua, pero espero que esto ayude a alguien más ya que encontré esta publicación buscando la misma cosa. Tuve un problema similar, solo que mis metadatos eran un Guid e int. La solución es la misma sin embargo. Puede hacer que los metadatos necesarios sean parte de la URL.

POSTAL aceptar método en su clase "Controller":

rutas
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 lo que sea que se registren, WebApiConfig.Register (HttpConfiguration config) para mí en este caso.

config.Routes.MapHttpRoute(
    name: "FooController", 
    routeTemplate: "api/{controller}/{name}/{latitude}/{longitude}", 
    defaults: new { } 
); 
2

Si su archivo y sus metadatos crean un recurso, está perfectamente bien cargarlos en una sola 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-- 
Cuestiones relacionadas