Вопрос по playframework-2.0 – Play 2.0 Как опубликовать MultipartFormData с помощью WS.url или WS.WSRequest

8

В запросе HTTP Http мы можем сделать это, чтобы сделать многочастный HTTP POST.

HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);

FileBody bin = new FileBody(new File(fileName));
StringBody comment = new StringBody("Filename: " + fileName);

MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("bin", bin);
reqEntity.addPart("comment", comment);
httppost.setEntity(reqEntity);

HttpResponse response = httpclient.execute(httppost);
HttpEntity resEntity = response.getEntity();

Как я могу добиться того же, используя WS.url или WS.WSRequest?

WSRequestHolder wsReq = WS.url("http//url");            
wsReq.setHeader("Content-type", "multipart/form-data");

Ваш Ответ

6   ответов
0

Принятый ответ не работает с игрой 2.5. Также ответ виграть 2.6 документация не работает для 2.5.
Ниже работал нормально:

Http.MultipartFormData.FilePart part = new Http.MultipartFormData.FilePart("fileKey",
                "abc.zip", "multipart/form-data",
                FileIO.fromFile(new File("/home/testData/abc.zip")));
List<Http.MultipartFormData.Part<Source<ByteString, ?>>> data = Arrays.asList(part);
Http.RequestBuilder requestBuilder = AuthFakeRequest.getAuthFakeRequest(routes.MyController.uploadZip()).method(POST)
                .bodyMultipart(data, mat);
Result result = route(app, requestBuilder);

Заmat а такжеapp объекты они получены при наследованииplay.test.WithApplication учебный класс.

0

Как предложил Ромен Сертелон, вы можете написать Writeable для обработки этого случая. Вот тот, который я написал:

package utilities

import java.io.{ByteArrayOutputStream, File}

import com.ning.http.client.FluentCaseInsensitiveStringsMap
import com.ning.http.multipart.{MultipartRequestEntity, FilePart, StringPart}
import play.api.http.HeaderNames._
import play.api.http.{ContentTypeOf, Writeable}
import play.api.mvc.{Codec, MultipartFormData}

object MultipartFormDataWriteable {

    implicit def contentTypeOf_MultipartFormData[A](implicit codec: Codec): ContentTypeOf[MultipartFormData[A]] = {
        ContentTypeOf[MultipartFormData[A]](Some("multipart/form-data; boundary=__X_PROCESS_STREET_BOUNDARY__"))
    }

    implicit def writeableOf_MultipartFormData(implicit contentType: ContentTypeOf[MultipartFormData[File]]): Writeable[MultipartFormData[File]] = {
        Writeable[MultipartFormData[File]]((formData: MultipartFormData[File]) => {

            val stringParts = formData.dataParts flatMap {
                case (key, values) => values map (new StringPart(key, _))
            }

            val fileParts = formData.files map { filePart =>
                new FilePart(filePart.key, filePart.ref, filePart.contentType getOrElse "application/octet-stream", null)
            }

            val parts = stringParts ++ fileParts

            val headers = new FluentCaseInsensitiveStringsMap().add(CONTENT_TYPE, contentType.mimeType.get)
            val entity = new MultipartRequestEntity(parts.toArray, headers)
            val outputStream = new ByteArrayOutputStream
            entity.writeRequest(outputStream)

            outputStream.toByteArray

        })(contentType)
    }

}

Вот как это использовать:

import utilities.MultipartFormDataWriteable._

...

val url = "https://example.com"

val dataParts = Map(
    "foo" -> Seq("bar"),
    "alice" -> Seq("bob")
)

val file = new jave.io.File(... path to a jpg ...)
val fileParts = Seq(new FilePart("attachment", "foo.jpg", Some("image/jpeg"), file)

val multipartFormData = MultipartFormData(dataParts, fileParts, Seq(), Seq())

WS.url(url).post(multipartFormData)
Действительно отличная идея! Однако он больше не компилируется. Ты имел ввидуimport com.ning.http.client.multipart ...? Также,stringParts ++ fileParts добавляет StringPart к Seq [FilePart], что приводит к Seq [BasePart].MultipartRequestEntity требует util.List [Part] (не Array [PartBase]). Компилируется ли он в последней версии Play 2.4.6 для вас?
5

Это неаккуратно и, безусловно, может быть исправлено, но вот что я сделал, чтобы заставить его работать. Не стесняйтесь сделать это намного лучше.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import play.libs.WS;

import com.ning.http.multipart.FilePart;
import com.ning.http.multipart.MultipartRequestEntity;
import com.ning.http.multipart.Part;

ByteArrayOutputStream bos = new ByteArrayOutputStream();

// Build up the Multiparts
List<Part> parts = new ArrayList<>();
parts.add(new FilePart("file", new File(filename)));
Part[] partsA = parts.toArray(new Part[parts.size()]);

// Add it to the MultipartRequestEntity
MultipartRequestEntity reqE = new MultipartRequestEntity(partsA, null);
reqE.writeRequest(bos);
InputStream reqIS = new ByteArrayInputStream(bos.toByteArray());
WS.WSRequestHolder req = WS.url(InterchangeConfig.conflateUrl+"dataset")
    .setContentType(reqE.getContentType());
req.post(reqIS).map(...);
// or req.post(reqIS).get();

Это все с использованием частей уже в рамках Play 2.0.

Глядя на источник для MultipartRequestEntity, похоже, что он взорвется, если вы передадите null для заголовков
Это работало для нас в Play 2.2, но, кажется, сломалось в Play 2.3 ... даже после исправленияplay.libs.ws менять. Ценю любую догадку, почему.
Нечто подобное работало у меня в скале. Для второго параметра Ctor я пишуnew FluentCaseInsensitiveStringsMapи я публикую как:.post(baos.toByteArray())(Writeable.wBytes, ContentTypeOf(Some(mpre.getContentType)))
@EricWilson, можешь ли ты быть более конкретным, чем "сломанный"?
К сожалению, я больше не работаю с Play и не имею доступа к этому коду.
0

Судя по документации play API, встроенных для составных частей POST нет.

Тем не менее, может быть возможно создать свое собственное составное тело, используя метод

post[T](body: T)(implicit wrt: Writeable[T], ct: ContentTypeOf[T]): Future[Response]

с типом T по вашему выбору, а также соответствующими типами Writeable и ContentTypeOf.

Но это подразумевало бы копание того, как составные части работают с HTTP.

На самом деле, я показал вам Scala API, и вы, похоже, используете Java, извините. В Java есть метод post (InputStream). Может быть, тогда вы сможете создать правильный контент во входном потоке :)
Не могли бы вы поделиться примером? angelokh
@angelokh: Вот пример:cdmckay.org/blog/2015/06/24/…
3

Рабочий пример для игры 2.3 с использованием вышеуказанного подхода, также добавлен contentType при загрузке файла.

public Promise<WSResponse> upload(Http.MultipartFormData.FilePart policyFilePart, String contentType) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    List<Part> parts = new ArrayList<>();
    try {
        parts.add(new FilePart("file", policyFilePart.getFile(), contentType, null));
        parts.add(new StringPart("param1", "value1"));
        parts.add(new StringPart("param2", "value2"));
        Part[] partsA = parts.toArray(new Part[parts.size()]);

        // Add it to the multipart request entity
        MultipartRequestEntity requestEntity = new MultipartRequestEntity(partsA, new FluentCaseInsensitiveStringsMap());
        requestEntity.writeRequest(bos);
        InputStream reqIS = new ByteArrayInputStream(bos.toByteArray());
        return WS.url(baseUrl + "upload")
                .setContentType(requestEntity.getContentType())
                .post(reqIS).map(new Function<WSResponse, WSResponse>() {
                    @Override
                    public WSResponse apply(WSResponse wsResponse) throws Throwable {
                            return wsResponse;
                    }
                });
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}
@ M.A.R. Я удалил ссылку, вместо этого я написал код только здесь
@live_alone Я пытаюсь использовать это в Play 2.4, но это приводит к 400 из-за неожиданного конца ввода
Спасибо! Намного лучше.
Ссылка на решение приветствуется, но, пожалуйста, убедитесь, что ваш ответ будет полезен без него:add context around the link поэтому ваши коллеги-пользователи будут иметь некоторое представление о том, что это такое и почему это так, а затем процитировать наиболее релевантную часть страницы, на которую вы ссылаетесь, в случае, если целевая страница недоступна.Answers that are little more than a link may be deleted.
3

На данный момент единственное решение, не опирающееся на внешние библиотеки, похоже, заключается в создании запроса Multipart Form Data вручную. Это пример того, как это можно сделать, используяplay.libs.WS.url:

WSRequestHolder wsRequestHolder = WS.url(URL);

String boundary = "--XYZ123--";

String body = "";
for (String key : data.keySet()) {
  body += "--" + boundary + "\r\n"
       + "Content-Disposition: form-data; name=\""
       + key + "\"\r\n\r\n"
       + data.get(key) + "\r\n";
}
body += "--" + boundary + "--";

wsRequestHolder.setHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
wsRequestHolder.setHeader("Content-length", String.valueOf(body.length()));

wsRequestHolder.post(body);

data будетjava.util.Map<String, String> содержит все пары имя / значение, которые вы хотите передать в качестве параметров формы.randomString является рандомизированным значением для изменения границы от запроса к запросу. Добавление двоичных данных будет работать так же.

этоhttp://www.htmlcodetutorial.com/forms/form_enctype.html это хорошее место для понимания спецификаций.

Похожие вопросы