【问题标题】:Play 2.0 How to Post MultipartFormData using WS.url or WS.WSRequestPlay 2.0 如何使用 WS.url 或 WS.WSRequest 发布 MultipartFormData
【发布时间】:2019-01-09 21:49:41
【问题描述】:

在 Java 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");

【问题讨论】:

    标签: playframework-2.0


    【解决方案1】:

    这很草率,绝对可以清理,但这是我为使其正常工作所做的。随意让它变得更好。

    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 框架中已经存在的部分。

    【讨论】:

    • 这在 Play 2.2 中对我们有用,但在 Play 2.3 中似乎已经损坏......即使在修复 play.libs.ws 更改之后也是如此。猜猜为什么。
    • @EricWilson 你能比“破碎”更具体吗?
    • 抱歉,我不再使用 play,也无权访问该代码。
    • 查看 MultipartRequestEntity 的源代码,如果您为标头传递 null,它看起来会爆炸
    • 类似的东西在 scala 中对我有用。对于第二个 ctor 参数,我写 new FluentCaseInsensitiveStringsMap,然后我发帖如下:.post(baos.toByteArray())(Writeable.wBytes, ContentTypeOf(Some(mpre.getContentType)))
    【解决方案2】:

    目前唯一不依赖外部库的解决方案似乎是手动创建多部分表单数据请求。这是一个使用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&lt;String, String&gt;,其中包含您要作为表单参数传递的所有名称/值对。 randomString 是一个随机值,用于使边界从请求到请求发生变化。添加二进制数据的工作方式相同。

    http://www.htmlcodetutorial.com/forms/form_enctype.html 是了解规格的好地方。

    【讨论】:

      【解决方案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;
          }
      }
      

      【讨论】:

      • 欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.
      • @M.A.R.我已经删除了链接,而是只在这里编写了代码
      • @live_alone 我正在尝试在 Play 2.4 中使用它,但由于输入意外结束而导致 400
      【解决方案4】:

      根据 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:这是一个例子:cdmckay.org/blog/2015/06/24/…
      【解决方案5】:

      正如 Romain Sertelon 所建议的,您可以编写一个 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 中为您编译?
      【解决方案6】:

      接受的答案不适用于 play 2.5。 play 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 类时获得的。

      【讨论】:

        猜你喜欢
        • 2012-06-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-14
        • 2021-01-23
        • 2012-09-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多