984 Stimmen

Senden einer Datei und zugehöriger Daten an einen RESTful WebService vorzugsweise als JSON

In einer Anwendung, die ich entwickle RESTful API und wir wollen den Client, um Daten als JSON senden. Ein Teil dieser Anwendung erfordert, dass der Client eine Datei (normalerweise ein Bild) sowie Informationen über das Bild hochlädt.

Es fällt mir schwer, herauszufinden, wie dies in einer einzigen Anfrage geschieht. Ist es möglich, Base64 die Dateidaten in eine JSON-Zeichenfolge? Muss ich 2 Anfragen an den Server stellen? Sollte ich JSON nicht für diese Aufgabe verwenden?

Als Randbemerkung, wir sind mit Grails auf dem Backend und diese Dienste werden von nativen mobilen Clients (iPhone, Android, etc.) zugegriffen, wenn etwas davon einen Unterschied macht.

3 Stimmen

Wie geht man also am besten vor?

8 Stimmen

Senden Sie die Metadaten im URL-Abfrage-String anstelle von JSON.

0 Stimmen

835voto

Daniel T. Punkte 35346

Ich habe hier eine ähnliche Frage gestellt:

Wie kann ich eine Datei mit Metadaten über einen REST-Webdienst hochladen?

Sie haben grundsätzlich drei Möglichkeiten:

  1. Die Base64-Kodierung der Datei geht mit einer Erhöhung der Datengröße um etwa 33 % einher, und sowohl auf dem Server als auch auf dem Client entsteht zusätzlicher Verarbeitungsaufwand für die Kodierung/Dekodierung.
  2. Senden Sie die Datei zunächst in einer multipart/form-data POST, und geben eine ID an den Client zurück. Der Client sendet dann die Metadaten mit der ID, und der Server ordnet die Datei und die Metadaten erneut zu.
  3. Senden Sie zuerst die Metadaten und geben Sie eine ID an den Client zurück. Der Client sendet dann die Datei mit der ID, und der Server ordnet die Datei und die Metadaten erneut zu.

146voto

McStretch Punkte 20235

Sie können die Datei und die Daten in einer Anfrage senden, indem Sie die multipart/form-data Inhaltstyp:

In vielen Anwendungen ist es möglich, dass ein Benutzer mit ein Formular. Der Benutzer füllt das Formular aus, einschließlich der Informationen, die getippt, durch Benutzereingaben generiert oder aus Dateien übernommen werden, die der Benutzer ausgewählt hat. Wenn das Formular ausgefüllt ist, werden die Daten aus dem Formular vom Benutzer an die empfangende Anwendung gesendet.

Die Definition von MultiPart/Form-Data ist von einer dieser Anwendungen abgeleitet Anwendungen...

De http://www.faqs.org/rfcs/rfc2388.html :

"multipart/form-data" enthält eine Reihe von Teilen. Jeder Teil ist einen content-disposition-Header [RFC 2183] enthalten, wobei der Dispositionstyp "form-data" ist und die Disposition einen einen (zusätzlichen) Parameter "name" enthält, wobei der Wert dieses Parameters der ursprüngliche Feldname im Formular ist. Zum Beispiel könnte ein Teil eine Kopfzeile enthalten:

Inhalt-Disposition: form-data; name="user"

mit dem Wert, der dem Eintrag im Feld "Benutzer" entspricht.

Sie können in jedem Abschnitt zwischen den Grenzen Informationen zu Dateien oder Feldern einfügen. Ich habe erfolgreich einen RESTful-Dienst implementiert, bei dem der Benutzer sowohl Daten als auch ein Formular übermitteln musste, und multipart/form-data hat perfekt funktioniert. Der Dienst wurde mit Java/Spring erstellt, und der Client verwendete C#, daher habe ich leider keine Grails-Beispiele, die ich Ihnen geben kann, wie der Dienst einzurichten ist. Sie brauchen in diesem Fall kein JSON zu verwenden, da jeder "form-data"-Abschnitt Ihnen einen Platz bietet, um den Namen des Parameters und seinen Wert anzugeben.

Das Gute an der Verwendung von multipart/form-data ist, dass Sie HTTP-definierte Header verwenden, so dass Sie sich an die REST-Philosophie halten und vorhandene HTTP-Tools zur Erstellung Ihres Dienstes verwenden.

97voto

pgiecek Punkte 7578

Ich weiß, dass dieser Thread schon ziemlich alt ist, aber ich vermisse hier eine Möglichkeit. Wenn Sie Metadaten (in einem beliebigen Format) haben, die Sie zusammen mit den hochzuladenden Daten senden möchten, können Sie eine einzelne multipart/related Anfrage.

Der Medientyp Multipart/Related ist für zusammengesetzte Objekte gedacht, die aus mehreren zusammenhängenden Körperteilen bestehen.

Sie können prüfen RFC 2387 Spezifikation für genauere Details.

Grundsätzlich kann jeder Teil einer solchen Anfrage einen Inhalt unterschiedlichen Typs haben und alle Teile sind irgendwie miteinander verbunden (z.B. ein Bild und seine Metadaten). Die Teile werden durch eine Begrenzungszeichenkette identifiziert, und die letzte Begrenzungszeichenkette wird von zwei Bindestrichen gefolgt.

Exemple :

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

31voto

Kamil Kiełczewski Punkte 69048

Hier ist mein Ansatz API (ich verwenden Beispiel) - wie Sie sehen können, Sie ich nicht verwenden alle file_id (Kennung der auf den Server hochgeladenen Datei) in der API:

  1. erstellen. photo Objekt auf dem Server:

     POST: /projects/{project_id}/photos   
     body: { name: "some_schema.jpg", comment: "blah"}
     response: photo_id
  2. Datei hochladen (beachten Sie, dass file ist in der Einzahl, weil es nur ein Foto pro Foto gibt):

     POST: /projects/{project_id}/photos/{photo_id}/file
     body: file to upload
     response: -

Und dann zum Beispiel:

  1. Liste der Fotos lesen

     GET: /projects/{project_id}/photos
     response: [ photo, photo, photo, ... ] (array of objects)
  2. Lesen Sie einige Fotodetails

     GET: /projects/{project_id}/photos/{photo_id}
     response: { id: 666, name: 'some_schema.jpg', comment:'blah'} (photo object)
  3. Fotodatei lesen

     GET: /projects/{project_id}/photos/{photo_id}/file
     response: file content

Die Schlussfolgerung ist also, dass Sie zuerst ein Objekt (Foto) per POST erstellen und dann eine zweite Anfrage mit der Datei senden (wieder POST). Um bei diesem Ansatz keine Probleme mit CACHE zu haben, gehen wir davon aus, dass wir nur alte Fotos löschen und neue hinzufügen können - keine Aktualisierung von binären Fotodateien (denn eine neue binäre Datei ist in der Tat ein NEUES Foto). Wenn Sie jedoch in der Lage sein müssen, Binärdateien zu aktualisieren und im Cache zu speichern, dann müssen Sie in Punkt 4 Rückkehr auch fileId und Veränderung 5 zu GET: /projects/{project_id}/photos/{photo_id}/files/{fileId}.

16voto

Rscorreia Punkte 201

Ich weiß, dass diese Frage schon alt ist, aber in den letzten Tagen habe ich das ganze Internet nach einer Lösung für diese Frage durchsucht. Ich habe Grails REST Webservices und iPhone Client, die Bilder, Titel und Beschreibung senden.

Ich weiß nicht, ob mein Ansatz der beste ist, aber er ist so leicht und einfach.

Ich nehme ein Bild mit dem UIImagePickerController und senden Sie an Server die NSData mit den Header-Tags der Anforderung, die Daten des Bildes zu senden.

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

Auf der Serverseite erhalte ich das Foto unter Verwendung des Codes:

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")

}

Ich weiß nicht, ob ich in Zukunft Probleme habe, aber jetzt funktioniert es in der Produktionsumgebung gut.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X