it-swarm.com.de

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

Dies wird wahrscheinlich eine dumme Frage sein, aber ich habe einen dieser Abende. In einer Anwendung entwickle ich RESTful API und wir möchten, dass der Client Daten als JSON sendet. 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 einzelnen Anfrage geschieht. Ist es möglich, die Dateidaten in eine JSON-Zeichenfolge zu base64? Muss ich 2 Posts auf dem Server ausführen? Sollte ich dafür nicht JSON verwenden?

Als Randnotiz verwenden wir Grails im Backend und auf diese Dienste greifen native mobile Clients (iPhone, Android usw.) zu, wenn dies einen Unterschied macht.

662
Gregg

Ich habe hier eine ähnliche Frage gestellt:

Wie lade ich eine Datei mit Metadaten über einen REST Webservice hoch?

Sie haben grundsätzlich drei Möglichkeiten:

  1. Base64 verschlüsselt die Datei auf Kosten der Erhöhung der Datengröße um ca. 33% und erhöht den Verarbeitungsaufwand sowohl auf dem Server als auch auf dem Client für die Codierung/Decodierung.
  2. Senden Sie die Datei zuerst in einem multipart/form-data POST und geben Sie 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 neu 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 neu zu.
553
Daniel T.

Sie können die Datei und die Daten in einer Anfrage mit dem Inhaltstyp mehrteilig/Formulardaten senden:

In vielen Anwendungen kann einem Benutzer ein Formular angezeigt werden. Der Benutzer füllt das Formular aus, einschließlich der Informationen, die eingegeben, durch Benutzereingaben generiert oder aus Dateien stammen, 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 leitet sich aus einer dieser Anwendungen ab ...

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

"multipart/form-data" enthält eine Reihe von Teilen. Es wird erwartet, dass jeder Teil einen Inhaltsdisposition-Header [RFC 2183] enthält, in dem der Dispositionstyp "Formulardaten" ist und in dem die Disposition einen (zusätzlichen) Parameter "name" enthält, wobei der Wert dieses Parameters das Original ist Feldname im Formular. Ein Teil kann beispielsweise einen Header enthalten:

Inhaltsdisposition: Formulardaten; name = "user"

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

Sie können Datei- oder Feldinformationen in jeden Abschnitt zwischen den Grenzen einfügen. Ich habe erfolgreich einen RESTful-Service implementiert, bei dem der Benutzer sowohl Daten als auch ein Formular senden musste, und Multipart-/Formulardaten funktionierten einwandfrei. Der Dienst wurde mit Java/Spring erstellt, und der Client verwendete C #. Leider liegen mir keine Grails-Beispiele zur Einrichtung des Dienstes vor. In diesem Fall muss JSON nicht verwendet werden, da in jedem Abschnitt "Formulardaten" der Name des Parameters und sein Wert angegeben werden können.

Das Gute an der Verwendung von Multipart-/Formulardaten ist, dass Sie HTTP-definierte Header verwenden. Sie halten sich also an die REST -Philosophie, vorhandene HTTP-Tools zum Erstellen Ihres Service zu verwenden.

94
McStretch

Ich weiß, dass dieser Thread ziemlich alt ist, aber mir fehlt hier eine Option. 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 -Anforderung stellen.

Der Medientyp Multipart/Related ist für zusammengesetzte Objekte vorgesehen, die aus mehreren miteinander verbundenen Körperteilen bestehen.

Sie können die Spezifikation RFC 2387 überprüfen, um detailliertere Informationen zu erhalten.

Grundsätzlich kann jeder Teil einer solchen Anfrage Inhalt mit unterschiedlichem Typ haben und alle Teile sind irgendwie miteinander verbunden (z. B. ein Bild und seine Metadaten). Die Teile sind durch eine Begrenzungszeichenfolge gekennzeichnet, und auf die letzte Begrenzungszeichenfolge folgen zwei Bindestriche.

Beispiel:

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

Ich weiß, dass diese Frage alt ist, aber in den letzten Tagen hatte ich das ganze Web durchsucht, um dieselbe Frage zu lösen. 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 einfach und unkompliziert.

Ich mache ein Bild mit dem UIImagePickerController und sende die NSData mit den Header-Tags der Anfrage an den Server, um die Bilddaten 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 mit dem Code:

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 einwandfrei in der Produktionsumgebung.

12
Rscorreia

Hier ist meine Annäherungs-API (ich verwende ein Beispiel) - wie Sie sehen können, verwende ich keine file_id (ID der hochgeladenen Datei auf dem Server) in der API:

1.Erstelle ein 'Foto'-Objekt auf dem Server:

POST: /projects/{project_id}/photos   
params in: {name:some_schema.jpg, comment:blah}
return: photo_id

2. Datei hochladen (beachten Sie, dass 'Datei' in Singularform vorliegt, da es sich nur um eine Datei pro Foto handelt):

POST: /projects/{project_id}/photos/{photo_id}/file
params in: file to upload
return: -

Und dann zum Beispiel:

3.Lesen Sie die Liste der Fotos

GET: /projects/{project_id}/photos
params in: -
return: array of objects: [ photo, photo, photo, ... ]

4.Lesen Sie einige Fotodetails

GET: /projects/{project_id}/photos/{photo_id}
params in: -
return: photo = { id: 666, name:'some_schema.jpg', comment:'blah'}

5.Lesen Sie die Fotodatei

GET: /projects/{project_id}/photos/{photo_id}/file
params in: -
return: file content

Die Schlussfolgerung ist also, dass Sie zuerst ein Objekt (Foto) per POST erstellen und dann eine zweite Anfrage mit Datei (erneut POST) senden.

8

Da das einzige fehlende Beispiel Android-Beispiel ist, werde ich es hinzufügen. Diese Technik verwendet eine benutzerdefinierte AsyncTask, die in Ihrer Activity-Klasse deklariert werden sollte.

private class UploadFile extends AsyncTask<Void, Integer, String> {
    @Override
    protected void onPreExecute() {
        // set a status bar or show a dialog to the user here
        super.onPreExecute();
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        // progress[0] is the current status (e.g. 10%)
        // here you can update the user interface with the current status
    }

    @Override
    protected String doInBackground(Void... params) {
        return uploadFile();
    }

    private String uploadFile() {

        String responseString = null;
        HttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost("http://example.com/upload-file");

        try {
            AndroidMultiPartEntity ampEntity = new AndroidMultiPartEntity(
                new ProgressListener() {
                    @Override
                        public void transferred(long num) {
                            // this trigger the progressUpdate event
                            publishProgress((int) ((num / (float) totalSize) * 100));
                        }
            });

            File myFile = new File("/my/image/path/example.jpg");

            ampEntity.addPart("fileFieldName", new FileBody(myFile));

            totalSize = ampEntity.getContentLength();
            httpPost.setEntity(ampEntity);

            // Making server call
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();

            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                responseString = EntityUtils.toString(httpEntity);
            } else {
                responseString = "Error, http status: "
                        + statusCode;
            }

        } catch (Exception e) {
            responseString = e.getMessage();
        }
        return responseString;
    }

    @Override
    protected void onPostExecute(String result) {
        // if you want update the user interface with upload result
        super.onPostExecute(result);
    }

}

Wenn Sie Ihre Datei hochladen möchten, rufen Sie einfach an:

new UploadFile().execute();
6
lifeisfoo

FormData-Objekte: Hochladen von Dateien mit Ajax

XMLHttpRequest Level 2 fügt Unterstützung für die neue FormData-Schnittstelle hinzu. FormData-Objekte bieten die Möglichkeit, auf einfache Weise eine Reihe von Schlüssel/Wert-Paaren zu erstellen, die Formularfelder und deren Werte darstellen. Diese können dann auf einfache Weise mit der Methode XMLHttpRequest send () gesendet werden.

function AjaxFileUpload() {
    var file = document.getElementById("files");
    //var file = fileInput;
    var fd = new FormData();
    fd.append("imageFileData", file);
    var xhr = new XMLHttpRequest();
    xhr.open("POST", '/ws/fileUpload.do');
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
             alert('success');
        }
        else if (uploadResult == 'success')
             alert('error');
    };
    xhr.send(fd);
}

https://developer.mozilla.org/en-US/docs/Web/API/FormData

5
lakhan_Ideavate

Ich wollte ein paar Zeichenfolgen an den Backend-Server senden. Ich habe json nicht mit Multipart verwendet, sondern Request-Parameter.

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public void uploadFile(HttpServletRequest request,
        HttpServletResponse response, @RequestParam("uuid") String uuid,
        @RequestParam("type") DocType type,
        @RequestParam("file") MultipartFile uploadfile)

URL würde so aussehen

http://localhost:8080/file/upload?uuid=46f073d0&type=PASSPORT

Ich übergebe zwei Parameter (UUID und Typ) zusammen mit Datei-Upload. Hoffe, dies wird helfen, wer nicht die komplexen JSON-Daten zu senden hat.

1
Aslam anwer
@RequestMapping(value = "/uploadImageJson", method = RequestMethod.POST)
    public @ResponseBody Object jsongStrImage(@RequestParam(value="image") MultipartFile image, @RequestParam String jsonStr) {
-- use  com.fasterxml.jackson.databind.ObjectMapper convert Json String to Object
}
0
sunleo