Question Publication d'un fichier et de données associées dans un WebService RESTful, de préférence en tant que JSON


Cela va probablement être une question stupide, mais je vais avoir une de ces nuits. Dans une application, je développe une API RESTful et nous voulons que le client envoie des données au format JSON. Une partie de cette application nécessite que le client télécharge un fichier (généralement une image) ainsi que des informations sur l'image.

J'ai du mal à trouver comment cela se passe dans une seule requête. Est-il possible de Base64 les données de fichier dans une chaîne JSON? Est-ce que je vais devoir effectuer 2 messages sur le serveur? Ne devrais-je pas utiliser JSON pour cela?

En guise de remarque, nous utilisons Grails sur le backend et ces services sont accessibles par les clients mobiles natifs (iPhone, Android, etc), si l'un de ces éléments fait une différence.


549
2017-11-03 02:07


origine


Réponses:


J'ai posé une question similaire ici:

Comment télécharger un fichier avec des métadonnées à l'aide d'un service Web REST?

Vous avez essentiellement trois choix:

  1. Base64 encoder le fichier, au détriment de l'augmentation de la taille des données d'environ 33%.
  2. Envoyer le fichier d'abord dans un multipart/form-data POST, et renvoyez un ID au client. Le client envoie ensuite les métadonnées avec l'ID et le serveur réassocie le fichier et les métadonnées.
  3. Envoyez d'abord les métadonnées et renvoyez un identifiant au client. Le client envoie ensuite le fichier avec l'ID et le serveur réassocie le fichier et les métadonnées.

459
2017-11-03 02:59



Vous pouvez envoyer le fichier et les données dans une requête en utilisant multipart / forme-données  type de contenu:

Dans de nombreuses applications, il est possible qu'un utilisateur soit présenté avec   une forme. L'utilisateur remplira le formulaire, y compris les informations   est tapé, généré par l'entrée de l'utilisateur, ou inclus à partir de fichiers que le   l'utilisateur a sélectionné. Lorsque le formulaire est rempli, les données du   Le formulaire est envoyé par l'utilisateur à l'application destinataire.

La définition de MultiPart / Form-Data est dérivée d'un de ces   applications...

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

"multipart / form-data" contient une série de parties. Chaque partie est   devrait contenir un en-tête de disposition de contenu [RFC 2183] où le   le type de disposition est "form-data" et lorsque la disposition contient   un paramètre (supplémentaire) de "name", où la valeur de ce   paramètre est le nom du champ d'origine dans le formulaire. Par exemple, une partie   pourrait contenir un en-tête:

Content-Disposition: données de formulaire; name = "utilisateur"

avec la valeur correspondant à l'entrée du champ "utilisateur".

Vous pouvez inclure des informations de fichier ou des informations de champ dans chaque section entre les limites. J'ai implémenté avec succès un service RESTful qui exigeait que l'utilisateur soumette à la fois des données et un formulaire, et multipart / form-data fonctionnait parfaitement. Le service a été construit en utilisant Java / Spring, et le client utilisait C #, donc malheureusement je n'ai pas d'exemples de Grails à vous donner concernant la configuration du service. Vous n'avez pas besoin d'utiliser JSON dans ce cas, car chaque section "formulaire-données" vous fournit un emplacement pour spécifier le nom du paramètre et sa valeur.

La bonne chose à propos de l'utilisation de multipart / form-data est que vous utilisez des entêtes définis par HTTP, donc vous respectez la philosophie REST d'utiliser les outils HTTP existants pour créer votre service.


75
2017-11-03 02:49



Je sais que ce fil est assez vieux, cependant, il me manque une option. Si vous avez des métadonnées (dans n'importe quel format) que vous souhaitez envoyer avec les données à télécharger, vous pouvez effectuer une seule multipart/related demande.

Le type Multipart / Related media est destiné aux objets composés composés de plusieurs parties de corps interdépendantes.

Tu peux vérifier RFC 2387 spécification pour plus de détails en profondeur.

Fondamentalement, chaque partie d'une telle requête peut avoir un contenu avec un type différent et toutes les parties sont en quelque sorte liées (par exemple, une image et des métadonnées). Les parties sont identifiées par une chaîne de délimitation et la chaîne de délimitation finale est suivie de deux traits d'union.

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

32
2018-05-23 15:03



Je sais que cette question est ancienne, mais dans les derniers jours j'avais cherché tout le web pour résoudre cette même question. J'ai des services web REST grails et iPhone Client qui envoie des images, des titres et des descriptions.

Je ne sais pas si mon approche est la meilleure, mais si simple et si simple.

Je prends une photo en utilisant le UIImagePickerController et envoie au serveur le NSData en utilisant les balises d'en-tête de la requête pour envoyer les données de l'image.

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

Du côté serveur, je reçois la photo en utilisant le 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")

}

Je ne sais pas si j'ai des problèmes à l'avenir, mais maintenant fonctionne bien dans l'environnement de production.


10
2018-01-31 17:49



Voici mon API d'approche (j'utilise l'exemple) - comme vous pouvez le voir, je n'utilise aucun fichier file_id (identyicator de fichier uploadé dans le serveur) dans l'API:

1.Créer un objet 'photo' sur le serveur:

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

2.Télécharger le fichier (notez que 'fichier' est au singulier car il est seulement un par photo):

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

Et puis par exemple:

3.Lire la liste des photos

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

4. Lisez quelques détails de la photo

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

5. Lire le fichier photo

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

Donc la conclusion est que, d'abord vous créez un objet (photo) par POST, et ensuite vous envoyez une requête secod avec le fichier (encore POST).


6
2017-07-10 20:31



Objets FormData: Télécharger des fichiers à l'aide d'Ajax

XMLHttpRequest Level 2 ajoute le support pour la nouvelle interface FormData. Les objets FormData permettent de créer facilement un ensemble de paires clé / valeur représentant les champs de formulaire et leurs valeurs, qui peuvent ensuite être facilement envoyés à l'aide de la méthode XMLHttpRequest send ().

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
2017-07-04 09:07



Puisque le seul exemple manquant est le Exemple ANDROIDJe vais l'ajouter Cette technique utilise une asyncTask personnalisée qui doit être déclarée dans votre classe Activity.

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

}

Donc, quand vous voulez télécharger votre fichier, appelez simplement:

new UploadFile().execute();

4
2017-09-13 09:40