wake-up-neo.net

Wie lade ich eine Datei mit Metadaten mithilfe eines REST Webdienstes hoch?

Ich habe einen REST Webdienst, der derzeit diese URL verfügbar macht:

http: // server/data/media

dabei können Benutzer POST den folgenden JSON-Code verwenden:

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

um neue Medien-Metadaten zu erstellen.

Jetzt muss ich die Möglichkeit haben, eine Datei gleichzeitig mit den Medienmetadaten hochzuladen. Wie geht man am besten vor? Ich könnte eine neue Eigenschaft namens file einführen und die Datei mit base64 codieren, aber ich habe mich gefragt, ob es einen besseren Weg gibt.

Es gibt auch mit multipart/form-data wie das, was ein HTML-Formular senden würde, aber ich verwende einen REST Web-Service und ich möchte bei der Verwendung von JSON bleiben, wenn dies überhaupt möglich ist.

229
Daniel T.

Ich stimme Greg zu, dass ein Zwei-Phasen-Ansatz eine vernünftige Lösung ist, aber ich würde es umgekehrt tun. Ich würde tun:

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

So erstellen Sie den Metadateneintrag und geben eine Antwort wie folgt zurück:

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

Der Client kann dann diese ContentUrl verwenden und ein PUT mit den Dateidaten durchführen.

Das Schöne an diesem Ansatz ist, wenn Ihr Server mit immensen Datenmengen überlastet wird. Die URL, die Sie zurückgeben, kann nur auf einen anderen Server mit mehr Speicherplatz/Kapazität verweisen. Oder Sie können einen Round-Robin-Ansatz implementieren, wenn die Bandbreite ein Problem darstellt.

177
Darrel Miller

Nur weil Sie nicht den gesamten Anforderungshauptteil in JSON einschließen, bedeutet dies nicht, dass die Verwendung von multipart/form-data, um sowohl den JSON-Code als auch die Datei (en) in einer einzelnen Anforderung zu veröffentlichen:

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

auf der Serverseite (mit Python für Pseudocode):

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

m mehrere Dateien hochzuladen, können Sie entweder separate "Formularfelder" für jede verwenden:

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

... in diesem Fall hat der Servercode request.args['file1'][0] und request.args['file2'][0]

oder verwende das gleiche für viele:

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

...in welchem ​​Fall request.args['files'] wird einfach eine Liste der Länge 2 sein.

oder übergebe tatsächlich mehrere Dateien auf einmal in ein einziges Feld:

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

...in welchem ​​Fall request.args['files'] ist eine Zeichenfolge, die alle Dateien enthält, die Sie selbst analysieren müssen. Sie wissen nicht, wie Sie dies tun sollen, aber ich bin sicher, dass es nicht schwierig ist, oder verwenden Sie einfach die vorherigen Ansätze.

Der Unterschied zwischen @ und < ist das @ bewirkt, dass die Datei als Datei-Upload angehängt wird, während < fügt den Inhalt der Datei als Textfeld hinzu.

PS Nur weil ich curl verwende, um die POST -Anforderungen zu generieren, bedeutet dies nicht, dass die genauen Anforderungen erfüllt sind Dieselben HTTP-Anforderungen konnten nicht aus einer Programmiersprache wie Python oder mit einem ausreichend leistungsfähigen Tool gesendet werden.

98
Erik Allik

Eine Möglichkeit, sich dem Problem zu nähern, besteht darin, den Upload in zwei Phasen durchzuführen. Zuerst würden Sie die Datei selbst mit einem POST hochladen, wobei der Server eine Kennung an den Client zurückgibt (eine Kennung könnte die SHA1 des Dateiinhalts sein). Dann ordnet eine zweite Anforderung die Metadaten den Dateidaten zu:

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

Durch die Einbeziehung der in der JSON-Anforderung selbst codierten Dateidatenbank64 wird die Größe der übertragenen Daten um 33% erhöht. Dies kann abhängig von der Gesamtgröße der Datei wichtig sein oder auch nicht.

Ein anderer Ansatz könnte darin bestehen, ein POST der Rohdateidaten zu verwenden, aber alle Metadaten in den HTTP-Anforderungsheader aufzunehmen. Dies liegt jedoch etwas außerhalb von basic REST = Operationen und kann für einige HTTP-Client-Bibliotheken umständlicher sein.

31
Greg Hewgill

Mir ist klar, dass dies eine sehr alte Frage ist, aber ich hoffe, dass dies jemand anderem hilft, als ich auf diesen Beitrag stieß und nach dem gleichen suchte. Ich hatte ein ähnliches Problem, nur dass meine Metadaten eine Guid und eine int waren. Die Lösung ist jedoch die gleiche. Sie können einfach die benötigten Metadaten in die URL einfügen.

POST-Annahmemethode in Ihrer "Controller" -Klasse:

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

Dann in was auch immer Sie Routen registrieren, WebApiConfig.Register (HttpConfiguration config) für mich in diesem Fall.

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

Wenn Ihre Datei und ihre Metadaten eine Ressource erstellen, ist es vollkommen in Ordnung, beide in einer Anfrage hochzuladen. Beispielanfrage wäre:

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
Mike Ezzati

Ich verstehe nicht, warum im Laufe von acht Jahren niemand die einfache Antwort veröffentlicht hat. Codieren Sie die Datei nicht als base64, sondern als Zeichenfolge. Dann dekodiere einfach den json auf der Serverseite.

In Javascript:

let formData = new FormData();
formData.append("file", myfile);
formData.append("myjson", JSON.stringify(myJsonObject));

POST es mit Content-Type: Multipart/Form-Daten

Rufen Sie auf der Serverseite die Datei normal ab und rufen Sie den json als Zeichenfolge ab. Konvertieren Sie die Zeichenfolge in ein Objekt. Dabei handelt es sich normalerweise um eine Codezeile, unabhängig von der verwendeten Programmiersprache.

(Ja, es funktioniert großartig. Mach es in einer meiner Apps.)

1
ccleve