Play 2.0 How to Post MultipartFormData using WS.url or WS.WSRequest

后端 未结 6 1315
小蘑菇
小蘑菇 2021-02-08 22:42

In Java Http request, we can do this to make multipart HTTP POST.

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

FileBo         


        
相关标签:
6条回答
  • 2021-02-08 23:27

    The only solution for now, without relying to external libraries, seems to be creating the Multipart Form Data request manually. This is an example how it can be done, using 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 would be a java.util.Map<String, String> containing all the name/value pairs you want to pass as form parameters. randomString is a randomized value to make the boundary change from request to request. Adding binary data would work the same way.

    This http://www.htmlcodetutorial.com/forms/form_enctype.html is a good place to refer to for understanding the specs.

    0 讨论(0)
  • 2021-02-08 23:28

    As Romain Sertelon suggested, you can write a Writeable to handle this case. Here's one I wrote:

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

    Here's how to use it:

    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)
    
    0 讨论(0)
  • 2021-02-08 23:30

    It seems, based on play API documentation, that there is no built-in for multipart POST bodies.

    However, it may be possible to create your own multipart body using the method

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

    with a type T of your choice, and the corresponding Writeable and ContentTypeOf types too.

    But this would imply digging in how multipart bodies work with HTTP.

    0 讨论(0)
  • 2021-02-08 23:33

    The accepted answer didn't work with play 2.5. Also the answer in play 2.6 documentation didn't work for 2.5.
    The below worked fine:

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

    For mat and app objects they are obtained when inheriting play.test.WithApplication class.

    0 讨论(0)
  • 2021-02-08 23:42

    This is sloppy, and can definitely be cleaned up, but here's what I did to get it working. Feel free to make this so much better.

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

    This is all using pieces already in the Play 2.0 framework.

    0 讨论(0)
  • 2021-02-08 23:45

    Working example for play 2.3 using above approach, also added contentType while uploading the file.

    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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题