2015-04-16 2 views
26

Я хочу отправить Статья от и Android клиент к серверу REST. Вот Python модель с сервера:Как отправить multipart/form-data с помощью Retrofit?

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

Следующий интерфейс описывает прежнюю реализацию:

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

Теперь я хочу, чтобы отправить изображение с статьи данных. photo не является частью Артикул модель на Android клиент.

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

API-интерфейс готов и успешно испытан с Curl.

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

При отправке данных через createArticle() из Android клиента я получаю HTTP 400 статус о том, что поля обязательные для заполнения/недостающую.

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 

Это то, что воспринимается как request.data на стороне сервера:

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

Как преобразовать статьи объекта в многоголосных соответствовать типу данных? Я читал, что Retrofit может использовать для этого Converters. Это должно быть то, что реализует retrofit.mime.TypedOutput, насколько я понял для documentation.

Составные части использовать нейтрализатор RestAdapter «s, или они могут реализовать TypedOutput обрабатывать свои собственные сериализации.

Связанные

+2

Разве это не класс TypedFile, который можно использовать для этого? – user2511882

+0

Ваш метод кажется прекрасным. Почему бы вам не включить ведение журнала в «RestAdapter» и точно проверить, какие данные отправляются. – corsair992

+0

@ user2511882 Вы имеете в виду, что я должен использовать 'TypedFile' для данных JSON (' article') и изображения? Пожалуйста, укажите мне, как конвертировать данные./@ corsair992 Я обновил свой пост. – JJD

ответ

24

По вашему завитка запросу вы пытаетесь создать что-л так:

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

С дооснащения адаптером этот запрос может быть создан в следующем пути :

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

Использование:

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(<...>); 

который создает аналогичный вывод:

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

Основное различие в том, что вы использовали POJO Article article в многочастных пары, который по умолчанию преобразуется Converter в JSON. И ваш сервер ожидает вместо этого простой строки. С curl вы отправляете cURL, а не {"author":"cURL"}.

+0

Можно ли полностью преобразовать всю модель * Article * в 'Typed..' и передать ее API, а не вытягивать каждый член? Может быть, конвертер может это сделать? – JJD

+1

Да, абсолютно. Я не видел такого конвертера, вы можете написать его и адаптировать к вашим потребностям. Направление выборки: https://gist.github.com/plastiv/08538a095d2d35acab05. –

+0

Для справки: Я предложил [MultipartConverter как функцию для доработки] (https: // github.ком/квадрат/дооснащение/вопросы/819). – JJD

2

Сервер ожидает строку «автор», но вы пытаетесь передать ей объект «article». Передайте «String author» вместо «Article article».

Кроме того, я думаю, что ошибка «без файла отправлена» - это красная селедка, потому что файл явно присутствует в ваших «request.data».