web-dev-qa-db-fra.com

Comment envoyer des données en plusieurs parties / formulaires avec Retrofit?

Je veux envoyer un client Article depuis et Android vers un serveur REST. Voici le Python modèle du serveur:

class Article(models.Model):
    author = models.CharField(max_length=256, blank=False)
    photo = models.ImageField()

L'interface suivante décrit l'ancienne implémentation:

@POST("/api/v1/articles/")
public Observable<CreateArticleResponse> createArticle(
        @Body Article article
);

Maintenant, je veux envoyer une image avec les données Article. Le photo ne fait pas partie du modèle Article sur le client Android.

@Multipart
@POST("/api/v1/articles/")
public Observable<CreateArticleResponse> createArticle(
        @Part("article") Article article,
        @Part("photo") TypedFile photo
);

L'API est préparée et testée avec succès avec cURL.

$ curl -vX POST http://localhost:8000/api/v1/articles/ \
    -H "Content-Type: multipart/form-data" \
    -H "Accept:application/json" \
    -F "author=cURL" \
    -F "photo=@/home/user/Desktop/article-photo.png"

Lorsque j'envoie des données via createArticle() depuis le client Android, je reçois un état HTTP 400 Indiquant que les champs sont obligatoires/manquant .

D  <--- HTTP 400 http://192.168.1.1/articles/ (2670ms)
D  Date: Mon, 20 Apr 2015 12:00:00 GMT
D  Server: WSGIServer/0.1 Python/2.7.8
D  Vary: Accept, Cookie
D  X-Frame-Options: SAMEORIGIN
D  Content-Type: application/json
D  Allow: GET, POST, HEAD, OPTIONS
D  OkHttp-Selected-Protocol: http/1.0
D  OkHttp-Sent-Millis: 1429545450469
D  OkHttp-Received-Millis: 1429545453120
D  {"author":["This field is required."],"photo":["No file was submitted."]}
D  <--- END HTTP (166-byte body)
E  400 BAD REQUEST

Voici ce qui est reçu en tant que request.data Côté serveur:

ipdb> print request.data  
  <QueryDict: {u'article': [u'{"author":"me"}'], \
  u'photo': [<TemporaryUploadedFile: IMG_1759215522.jpg \
  (multipart/form-data)>]}>

Comment convertir l'objet Article dans un type de données conforme à plusieurs parties? J'ai lu que Retrofit pourrait permettre d'utiliser Converters pour cela. Cela devrait être quelque chose qui implémente un retrofit.mime.TypedOutput Pour autant que j'ai compris pour le documentation .

Les pièces en plusieurs parties utilisent le convertisseur de RestAdapter ou elles peuvent implémenter TypedOutput pour gérer leur propre sérialisation.

En relation

29
JJD

Selon votre demande de boucle, vous essayez de créer un smth comme ceci:

POST http://localhost:8000/api/v1/articles/ HTTP/1.1
User-Agent: curl/7.30.0
Host: localhost
Connection: Keep-Alive
Accept: application/json
Content-Length: 183431
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------23473c7acabb

------------------------------23473c7acabb
Content-Disposition: form-data; name="author"

cURL
------------------------------23473c7acabb
Content-Disposition: form-data; name="photo"; filename="article-photo.png"
Content-Type: application/octet-stream

‰PNG

<!RAW BYTES HERE!>

M\UUÕ+4qUUU¯°WUUU¿×ß¿þ Naa…k¿    IEND®B`‚
------------------------------23473c7acabb--

Avec l'adaptateur de modification, cette demande peut être créée de la manière suivante:

@Multipart
@POST("/api/v1/articles/")
Observable<Response> uploadFile(@Part("author") TypedString authorString,
                                @Part("photo") TypedFile photoFile);

Usage:

TypedString author = new TypedString("cURL");
File photoFile = new File("/home/user/Desktop/article-photo.png");
TypedFile photoTypedFile = new TypedFile("image/*", photoFile);
retrofitAdapter.uploadFile(author, photoTypedFile)
               .subscribe(<...>);

Ce qui crée une sortie similaire:

POST http://localhost:8000/api/v1/articles/ HTTP/1.1
Content-Type: multipart/form-data; boundary=32230279-83af-4480-abfc-88a880b21b19
Content-Length: 709
Host: localhost
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.3.0

--32230279-83af-4480-abfc-88a880b21b19
Content-Disposition: form-data; name="author"
Content-Type: text/plain; charset=UTF-8
Content-Length: 4
Content-Transfer-Encoding: binary

cUrl
--32230279-83af-4480-abfc-88a880b21b19
Content-Disposition: form-data; name="photo"; filename="article-photo.png"
Content-Type: image/*
Content-Length: 254
Content-Transfer-Encoding: binary

<!RAW BYTES HERE!>

--32230279-83af-4480-abfc-88a880b21b19--

La principale différence ici est que vous avez utilisé POJO Article article comme paramètre multipartie, qui par défaut est converti par Converter en json. Et votre serveur attend à la place une chaîne simple. Avec curl, vous envoyez cURL, pas {"author":"cURL"}.

26
Sergii Pechenizkyi

Le serveur attend une chaîne "auteur" mais vous essayez de lui passer un objet "article". Passez-le "String author" au lieu de "Article article".

En outre, je pense que l'erreur "aucun fichier soumis" est un redingue car le fichier est clairement présent dans votre "request.data".

2
Bob Lee