Comment télécharger un fichier avec des métadonnées en utilisant un service REST web?

j'ai un service REST web qui expose actuellement cette URL:

http://server/data/media

où les utilisateurs peuvent POST le JSON suivant:

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

afin de créer de nouvelles métadonnées média.

Maintenant, j'ai besoin de la possibilité de télécharger un fichier en même temps que les médias de métadonnées. Quelle est la meilleure façon d'aller à ce sujet? Je pourrais introduire une nouvelle propriété appelé file et base64 encodent le fichier, mais je me demandais s'il y avait une meilleure façon.

il y a aussi l'utilisation de multipart/form-data comme ce qu'un formulaire HTML enverrait, mais j'utilise un service REST web et je veux m'en tenir à l'utilisation de JSON si possible.

200
demandé sur Daniel T. 2010-10-15 04:21:35

5 réponses

je suis d'accord avec Greg qui deux phases approche est une solution raisonnable, mais je voudrais le faire dans l'autre sens. Je ferais:

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

pour créer l'entrée de métadonnées et retourner une réponse comme:

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

le client peut alors utiliser ce ContentUrl et faire un PUT avec les données du fichier.

la bonne chose à propos de cette approche est lorsque votre serveur commence à être pesé avec d'immenses volumes de données, l'url que vous retournez peut simplement pointer vers un autre serveur avec plus d'espace / Capacité. Ou vous pouvez mettre en œuvre une sorte d'approche de type "Round robin" si la largeur de bande est un problème.

160
répondu Darrel Miller 2010-10-15 01:26:10

ce N'est pas parce que vous n'enveloppez pas tout le corps de la requête dans JSON, qu'il n'est pas rassurant d'utiliser multipart/form-data pour afficher à la fois le JSON et le fichier (ou plusieurs fichiers) dans une seule requête:

curl -F "metadata=<metadata.json" -F "file=@my-file.tar.gz" http://example.com/add-file

du côté serveur (utilisant Python comme la lingua franca de programmation ici):

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

pour télécharger plusieurs fichiers, il est possible d'utiliser des "champs de formulaire" séparés pour chacun:

curl -F "metadata=<metadata.json" -F "file1=@some-file.tar.gz" -F "file2=@some-other-file.tar.gz" http://example.com/add-file

... auquel cas le code du serveur sera request.args['file1'][0] et request.args['file2'][0]

ou réutiliser le même pour plusieurs:

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz" -F "files=@some-other-file.tar.gz" http://example.com/add-file

... auquel cas request.args['files'] sera simplement une liste de longueur 2.

ou passer réellement plusieurs fichiers dans un seul champ en une seule fois:

curl -F "metadata=<metadata.json" -F "files=@some-file.tar.gz,some-other-file.tar.gz" http://example.com/add-file

... auquel cas request.args['files'] sera une chaîne contenant tous les fichiers, que vous aurez à analyser vous - même-pas sûr de la façon de le faire, mais je suis sûr que ce n'est pas difficile, ou mieux utiliser les approches précédentes.

la différence entre @ et < est que @ provoque le fichier pour obtenir attaché comme un téléchargement de fichier, tandis que < attache le contenu du fichier comme un champ de texte.

P.S. ce n'est pas parce que j'utilise curl comme un moyen de générer les requêtes POST que les mêmes requêtes HTTP ne peuvent pas être envoyées à partir d'un langage de programmation tel que Python ou en utilisant un outil suffisamment performant.

95
répondu Erik Allik 2017-09-13 16:15:28

une façon d'aborder le problème est de faire du téléchargement un processus en deux phases. Tout d'abord, vous téléchargez le fichier lui-même en utilisant un poste, où le serveur renvoie un identifiant au client (un identifiant pourrait être le SHA1 du contenu du fichier). Ensuite, une deuxième requête associe les métadonnées aux données du fichier:

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

incluant les données de fichier base64 encodées dans la requête JSON elle-même augmentera la taille des données transférées de 33%. Ce peut ou peut ne pas être important en fonction de la taille globale du fichier.

une autre approche pourrait être d'utiliser un POST des données brutes du fichier, mais d'inclure toutes les métadonnées dans L'en-tête de la requête HTTP. Cependant, cela tombe un peu en dehors des opérations de repos de base et peut être plus délicat pour certaines bibliothèques de clients HTTP.

26
répondu Greg Hewgill 2010-10-15 00:42:02

je me rends compte que c'est une question très ancienne, mais j'espère que cela aidera quelqu'un d'autre que je suis venu sur ce post à la recherche de la même chose. J'ai eu un problème similaire, juste que mes métadonnées étaient un guide et int. La solution est la même si. Vous pouvez simplement faire les métadonnées nécessaires partie de L'URL.

méthode d'acceptation de poste dans votre classe "contrôleur":

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

puis dans ce que vous enregistrez les routes, WebApiConfig.Registre(HttpConfiguration config) pour moi dans ce cas.

config.Routes.MapHttpRoute(
    name: "FooController",
    routeTemplate: "api/{controller}/{name}/{latitude}/{longitude}",
    defaults: new { }
);
9
répondu Greg Biles 2013-02-25 16:49:57

si votre fichier et ses métadonnées ne créent qu'une seule ressource, il est parfaitement possible de les télécharger en une seule demande. Exemple de demande :

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\
)%!Y0i@.k
[
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
répondu Mike Ezzati 2017-09-07 14:11:40