Multipart Post

classic Classic list List threaded Threaded
13 messages Options
Reply | Threaded
Open this post in threaded view
|

Multipart Post

Alexander Birmingham
Hi Everyone:

I've been trying to do a multipart post using the Jersey Client, and it's just about near driving me crazy. The examples I've found have been helpful in modelling my code, but haven't taken me quite to the end.

I am looking to duplicate the following curl command:

curl -F "file=@test.zip;type=application/octet-stream" -F "e=[hidden email]" -F "l=iQdad" -F "t=5Cp" -F "client_id=1000076" http://my.url.here.com/home/upload

This is my java code:

        MultivaluedMap<String, String> queryParams;
        File file = new File("/tmp/test.zip");

        addArgument(SessionUser.getCURRENTUSER()); // This adds the parameters you see above to queryParams

        MultiPart multiPart = new MultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

     webResource.queryParams(queryParams).type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class, multiPart);

The response indicates that the post is missing field "file". I am unable to determine if this is because the binary is not being attached correctly, or if the attachment is not being sent with the correct name. The name has to be "file", as occurs in the above curl, or it will not be recognized.

Please help!

Best Regards,
Alexander Birmingham
Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Paul Sandoz
Administrator
Hi Alexander,

You client code looks fine. Although strictly speaking you should use an instance of FormDataMultiPart.

What version of Jersey are you using?

What is the server side that consumes the request?

Attached is a very simple client and server using Jersey which works fine. And the server-side also works fine when i use curl to do send an equivalent request.

If you run this example (suitably modifying the file to send) you will notice that Jersey will log the request received from the client:

Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter filter
INFO: 3 * Server in-bound request
3 > content-type: multipart/form-data;boundary=Boundary_1_2384204_1262683784353
3 > mime-version: 1.0
3 > user-agent: Java/1.5.0_20
3 > host: localhost:8080
3 > accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
3 > connection: keep-alive
3 > content-length: 1210
3 > 

--Boundary_1_2384204_1262683784353
Content-Type: application/octet-stream
Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Users/paulsandoz/Projects/atmosphere/trunk/atmosphere/modules/jersey/src/main/java/org/atmosphere/jersey
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and \n newlines.
# Above lines and this line are ignored by the patching process.
Index: AtmosphereFilter.java
--- AtmosphereFilter.java Base (BASE)
+++ AtmosphereFilter.java Locally Modified (Based On LOCAL)
@@ -156,6 +156,9 @@
          * @return the {@link ContainerResponse}
          */
         public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
+            if (response.getMappedThrowable() != null)
+                return response;
+            
             AtmosphereResource r = (AtmosphereResource) servletReq
                     .getAttribute(ReflectorServletProcessor.ATMOSPHERE_RESOURCE);
 

--Boundary_1_2384204_1262683784353--


Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter$Adapter finish
INFO: 3 * Server out-bound response
3 < 204
3 < 


Notice the Content-Disposition header:

  Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

That contains the property "name" of the value "file".

Note that you can also log from the client side as well by doing:

 Client c = ...
 c.addFilter(new LoggingFilter());

to also verify the Content-Disposition header.

Paul.



On Jan 5, 2010, at 1:31 AM, Alexander Birmingham wrote:

Hi Everyone:

I've been trying to do a multipart post using the Jersey Client, and it's just about near driving me crazy. The examples I've found have been helpful in modelling my code, but haven't taken me quite to the end.

I am looking to duplicate the following curl command:

curl -F "[hidden email];type=application/octet-stream" -F "e=[hidden email]" -F "l=iQdad" -F "t=5Cp" -F "client_id=1000076" http://my.url.here.com/home/upload

This is my java code:

        MultivaluedMap<String, String> queryParams;
        File file = new File("/tmp/test.zip");

        addArgument(SessionUser.getCURRENTUSER()); // This adds the parameters you see above to queryParams

        MultiPart multiPart = new MultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

     webResource.queryParams(queryParams).type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class, multiPart);

The response indicates that the post is missing field "file". I am unable to determine if this is because the binary is not being attached correctly, or if the attachment is not being sent with the correct name. The name has to be "file", as occurs in the above curl, or it will not be recognized.

Please help!

Best Regards,
Alexander Birmingham


mavenproject9.zip (8K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Alexander Birmingham
Paul -

Thanks for the quick response! I'm referencing the following libraries:
jersey-client-1.1.4.1.jar
jersey-core-1.1.4.1.jar
jsr311-api-1.1.jar
activation-1.1.jar
jersey-multipart-1.1.4.1.jar
mail-1.4.jar
mimepull-1.3.jar


Additionally, it may help to know that the Jersey client prints the following blurb when created:
Jan 5, 2010 9:16:32 AM com.sun.jersey.api.client.Client <init>
INFO: Adding the following classes declared in META-INF/services/jersey-client-components to the client configuration:
  class com.sun.jersey.multipart.impl.MultiPartConfigProvider
  class com.sun.jersey.multipart.impl.MultiPartReader
  class com.sun.jersey.multipart.impl.MultiPartWriter


The server side processor is an internal tool to my company written in perl. Frankly I don't know a lot about it, but it is verified as functional according to the curl request. Additionally, Jersey has worked fine so far for my plain-jane getting/posting needs, so long as the request is composed entirely of ordinary query parameters. I should mention also that my client setup is fairly minimalistic: CLIENT = Client.create();. That's it :)

Finally, I have revised the following line of code to use FormDataMultiPart as per your recommendation:
MultiPart multiPart = new FormDataMultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

Other than above... I'm not sure what to think. Your code looks identical to mine, as far as I can tell, except that your request doesn't include query parameters in addition to the MultiPart. Could that be where I'm stumbling?

Best Regards,
Alexander

On Tue, Jan 5, 2010 at 1:44 AM, Paul Sandoz <[hidden email]> wrote:
Hi Alexander,

You client code looks fine. Although strictly speaking you should use an instance of FormDataMultiPart.

What version of Jersey are you using?

What is the server side that consumes the request?

Attached is a very simple client and server using Jersey which works fine. And the server-side also works fine when i use curl to do send an equivalent request.

If you run this example (suitably modifying the file to send) you will notice that Jersey will log the request received from the client:

Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter filter
INFO: 3 * Server in-bound request
3 > content-type: multipart/form-data;boundary=Boundary_1_2384204_1262683784353
3 > mime-version: 1.0
3 > user-agent: Java/1.5.0_20
3 > host: localhost:8080
3 > accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
3 > connection: keep-alive
3 > content-length: 1210
3 > 

--Boundary_1_2384204_1262683784353
Content-Type: application/octet-stream
Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Users/paulsandoz/Projects/atmosphere/trunk/atmosphere/modules/jersey/src/main/java/org/atmosphere/jersey
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and \n newlines.
# Above lines and this line are ignored by the patching process.
Index: AtmosphereFilter.java
--- AtmosphereFilter.java Base (BASE)
+++ AtmosphereFilter.java Locally Modified (Based On LOCAL)
@@ -156,6 +156,9 @@
          * @return the {@link ContainerResponse}
          */
         public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
+            if (response.getMappedThrowable() != null)
+                return response;
+            
             AtmosphereResource r = (AtmosphereResource) servletReq
                     .getAttribute(ReflectorServletProcessor.ATMOSPHERE_RESOURCE);
 

--Boundary_1_2384204_1262683784353--


Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter$Adapter finish
INFO: 3 * Server out-bound response
3 < 204
3 < 


Notice the Content-Disposition header:

  Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

That contains the property "name" of the value "file".

Note that you can also log from the client side as well by doing:

 Client c = ...
 c.addFilter(new LoggingFilter());

to also verify the Content-Disposition header.

Paul.



On Jan 5, 2010, at 1:31 AM, Alexander Birmingham wrote:

Hi Everyone:

I've been trying to do a multipart post using the Jersey Client, and it's just about near driving me crazy. The examples I've found have been helpful in modelling my code, but haven't taken me quite to the end.

I am looking to duplicate the following curl command:

curl -F "[hidden email];type=application/octet-stream" -F "e=[hidden email]" -F "l=iQdad" -F "t=5Cp" -F "client_id=1000076" http://my.url.here.com/home/upload

This is my java code:

        MultivaluedMap<String, String> queryParams;
        File file = new File("/tmp/test.zip");

        addArgument(SessionUser.getCURRENTUSER()); // This adds the parameters you see above to queryParams

        MultiPart multiPart = new MultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

     webResource.queryParams(queryParams).type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class, multiPart);

The response indicates that the post is missing field "file". I am unable to determine if this is because the binary is not being attached correctly, or if the attachment is not being sent with the correct name. The name has to be "file", as occurs in the above curl, or it will not be recognized.

Please help!

Best Regards,
Alexander Birmingham



Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Paul Sandoz
Administrator

On Jan 5, 2010, at 6:30 PM, Alexander Birmingham wrote:

Paul -

Thanks for the quick response! I'm referencing the following libraries:
jersey-client-1.1.4.1.jar
jersey-core-1.1.4.1.jar
jsr311-api-1.1.jar
activation-1.1.jar
jersey-multipart-1.1.4.1.jar
mail-1.4.jar
mimepull-1.3.jar


Additionally, it may help to know that the Jersey client prints the following blurb when created:
Jan 5, 2010 9:16:32 AM com.sun.jersey.api.client.Client <init>
INFO: Adding the following classes declared in META-INF/services/jersey-client-components to the client configuration:
  class com.sun.jersey.multipart.impl.MultiPartConfigProvider
  class com.sun.jersey.multipart.impl.MultiPartReader
  class com.sun.jersey.multipart.impl.MultiPartWriter



The above is OK.


The server side processor is an internal tool to my company written in perl. Frankly I don't know a lot about it, but it is verified as functional according to the curl request. Additionally, Jersey has worked fine so far for my plain-jane getting/posting needs, so long as the request is composed entirely of ordinary query parameters. I should mention also that my client setup is fairly minimalistic: CLIENT = Client.create();. That's it :)

Finally, I have revised the following line of code to use FormDataMultiPart as per your recommendation:
MultiPart multiPart = new FormDataMultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

Other than above... I'm not sure what to think. Your code looks identical to mine, as far as I can tell, except that your request doesn't include query parameters in addition to the MultiPart. Could that be where I'm stumbling?


I suspect the server side is not capable of correctly processing multipart/form-data representations, and specifically it has problems with the Content-Dispostion header.

You could try this:

  MultiPart multiPart = new FormDataMultiPart().field("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE);

which will send a Content-Dispostion header as follows:

  Content-Disposition: form-data;name="file"

You might want to try:

        FormDataBodyPart f = new FormDataBodyPart(
                FormDataContentDisposition.name("file").fileName(file.getName()).build(),
                file, MediaType.APPLICATION_OCTET_STREAM_TYPE);
        MultiPart multiPart = new FormDataMultiPart().bodyPart(f);

which will send a Content-Dispostion header as follows:

  Content-Disposition: form-data;filename="a.patch";name="file"

Failing that you might have to add you own Content-Disposition header such that it is compatible with your server. For example,

        FormDataBodyPart f = new FormDataBodyPart(
                FormDataContentDisposition.name("file").fileName(file.getName()).build(),
                file, MediaType.APPLICATION_OCTET_STREAM_TYPE);
        f.getHeaders().putSingle("Content-Disposition", "form-data;name=\"file\";filename=\"a.patch\"");
        MultiPart multiPart = new FormDataMultiPart().bodyPart(f);

Paul.

Best Regards,
Alexander

On Tue, Jan 5, 2010 at 1:44 AM, Paul Sandoz <[hidden email]> wrote:
Hi Alexander,

You client code looks fine. Although strictly speaking you should use an instance of FormDataMultiPart.

What version of Jersey are you using?

What is the server side that consumes the request?

Attached is a very simple client and server using Jersey which works fine. And the server-side also works fine when i use curl to do send an equivalent request.

If you run this example (suitably modifying the file to send) you will notice that Jersey will log the request received from the client:

Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter filter
INFO: 3 * Server in-bound request
3 > content-type: multipart/form-data;boundary=Boundary_1_2384204_1262683784353
3 > mime-version: 1.0
3 > user-agent: Java/1.5.0_20
3 > host: localhost:8080
3 > accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
3 > connection: keep-alive
3 > content-length: 1210
3 > 

--Boundary_1_2384204_1262683784353
Content-Type: application/octet-stream
Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Users/paulsandoz/Projects/atmosphere/trunk/atmosphere/modules/jersey/src/main/java/org/atmosphere/jersey
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and \n newlines.
# Above lines and this line are ignored by the patching process.
Index: AtmosphereFilter.java
--- AtmosphereFilter.java Base (BASE)
+++ AtmosphereFilter.java Locally Modified (Based On LOCAL)
@@ -156,6 +156,9 @@
          * @return the {@link ContainerResponse}
          */
         public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
+            if (response.getMappedThrowable() != null)
+                return response;
+            
             AtmosphereResource r = (AtmosphereResource) servletReq
                     .getAttribute(ReflectorServletProcessor.ATMOSPHERE_RESOURCE);
 

--Boundary_1_2384204_1262683784353--


Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter$Adapter finish
INFO: 3 * Server out-bound response
3 < 204
3 < 


Notice the Content-Disposition header:

  Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

That contains the property "name" of the value "file".

Note that you can also log from the client side as well by doing:

 Client c = ...
 c.addFilter(new LoggingFilter());

to also verify the Content-Disposition header.

Paul.



On Jan 5, 2010, at 1:31 AM, Alexander Birmingham wrote:

Hi Everyone:

I've been trying to do a multipart post using the Jersey Client, and it's just about near driving me crazy. The examples I've found have been helpful in modelling my code, but haven't taken me quite to the end.

I am looking to duplicate the following curl command:

curl -F "[hidden email];type=application/octet-stream" -F "e=[hidden email]" -F "l=iQdad" -F "t=5Cp" -F "client_id=1000076" http://my.url.here.com/home/upload

This is my java code:

        MultivaluedMap<String, String> queryParams;
        File file = new File("/tmp/test.zip");

        addArgument(SessionUser.getCURRENTUSER()); // This adds the parameters you see above to queryParams

        MultiPart multiPart = new MultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

     webResource.queryParams(queryParams).type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class, multiPart);

The response indicates that the post is missing field "file". I am unable to determine if this is because the binary is not being attached correctly, or if the attachment is not being sent with the correct name. The name has to be "file", as occurs in the above curl, or it will not be recognized.

Please help!

Best Regards,
Alexander Birmingham




Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Alexander Birmingham
In reply to this post by Alexander Birmingham
Paul -

Through a lot of brute force debugging, I've narrowed down the problem to a single line in the request.

This works:
Content-Type: multipart/form-data; boundary=----------------------------4e1144df2bb2

While this does not:
Content-Type: multipart/form-data;boundary=----------------------------4e1144df2bb2

Don't even want to admit how long it took for us to figure that one out. Apparently the server gets confused if there is whitespace after the semi-colon. This should probably become a bug report, but in the meantime can I specify that line manually with the Jersey client?

Best Regards,
Alexander

On Tue, Jan 5, 2010 at 9:30 AM, Alexander Birmingham <[hidden email]> wrote:
Paul -

Thanks for the quick response! I'm referencing the following libraries:
jersey-client-1.1.4.1.jar
jersey-core-1.1.4.1.jar
jsr311-api-1.1.jar
activation-1.1.jar
jersey-multipart-1.1.4.1.jar
mail-1.4.jar
mimepull-1.3.jar


Additionally, it may help to know that the Jersey client prints the following blurb when created:
Jan 5, 2010 9:16:32 AM com.sun.jersey.api.client.Client <init>
INFO: Adding the following classes declared in META-INF/services/jersey-client-components to the client configuration:
  class com.sun.jersey.multipart.impl.MultiPartConfigProvider
  class com.sun.jersey.multipart.impl.MultiPartReader
  class com.sun.jersey.multipart.impl.MultiPartWriter


The server side processor is an internal tool to my company written in perl. Frankly I don't know a lot about it, but it is verified as functional according to the curl request. Additionally, Jersey has worked fine so far for my plain-jane getting/posting needs, so long as the request is composed entirely of ordinary query parameters. I should mention also that my client setup is fairly minimalistic: CLIENT = Client.create();. That's it :)

Finally, I have revised the following line of code to use FormDataMultiPart as per your recommendation:
MultiPart multiPart = new FormDataMultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

Other than above... I'm not sure what to think. Your code looks identical to mine, as far as I can tell, except that your request doesn't include query parameters in addition to the MultiPart. Could that be where I'm stumbling?

Best Regards,
Alexander

On Tue, Jan 5, 2010 at 1:44 AM, Paul Sandoz <[hidden email]> wrote:
Hi Alexander,

You client code looks fine. Although strictly speaking you should use an instance of FormDataMultiPart.

What version of Jersey are you using?

What is the server side that consumes the request?

Attached is a very simple client and server using Jersey which works fine. And the server-side also works fine when i use curl to do send an equivalent request.

If you run this example (suitably modifying the file to send) you will notice that Jersey will log the request received from the client:

Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter filter
INFO: 3 * Server in-bound request
3 > content-type: multipart/form-data;boundary=Boundary_1_2384204_1262683784353
3 > mime-version: 1.0
3 > user-agent: Java/1.5.0_20
3 > host: localhost:8080
3 > accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
3 > connection: keep-alive
3 > content-length: 1210
3 > 

--Boundary_1_2384204_1262683784353
Content-Type: application/octet-stream
Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Users/paulsandoz/Projects/atmosphere/trunk/atmosphere/modules/jersey/src/main/java/org/atmosphere/jersey
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and \n newlines.
# Above lines and this line are ignored by the patching process.
Index: AtmosphereFilter.java
--- AtmosphereFilter.java Base (BASE)
+++ AtmosphereFilter.java Locally Modified (Based On LOCAL)
@@ -156,6 +156,9 @@
          * @return the {@link ContainerResponse}
          */
         public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
+            if (response.getMappedThrowable() != null)
+                return response;
+            
             AtmosphereResource r = (AtmosphereResource) servletReq
                     .getAttribute(ReflectorServletProcessor.ATMOSPHERE_RESOURCE);
 

--Boundary_1_2384204_1262683784353--


Jan 5, 2010 10:29:44 AM com.sun.jersey.api.container.filter.LoggingFilter$Adapter finish
INFO: 3 * Server out-bound response
3 < 204
3 < 


Notice the Content-Disposition header:

  Content-Disposition: form-data;filename="a.patch";modification-date="Mon, 07 Dec 2009 09:48:27 GMT";size=968;name="file"

That contains the property "name" of the value "file".

Note that you can also log from the client side as well by doing:

 Client c = ...
 c.addFilter(new LoggingFilter());

to also verify the Content-Disposition header.

Paul.



On Jan 5, 2010, at 1:31 AM, Alexander Birmingham wrote:

Hi Everyone:

I've been trying to do a multipart post using the Jersey Client, and it's just about near driving me crazy. The examples I've found have been helpful in modelling my code, but haven't taken me quite to the end.

I am looking to duplicate the following curl command:

curl -F "[hidden email];type=application/octet-stream" -F "e=[hidden email]" -F "l=iQdad" -F "t=5Cp" -F "client_id=1000076" http://my.url.here.com/home/upload

This is my java code:

        MultivaluedMap<String, String> queryParams;
        File file = new File("/tmp/test.zip");

        addArgument(SessionUser.getCURRENTUSER()); // This adds the parameters you see above to queryParams

        MultiPart multiPart = new MultiPart().bodyPart(new FileDataBodyPart("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE));

     webResource.queryParams(queryParams).type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class, multiPart);

The response indicates that the post is missing field "file". I am unable to determine if this is because the binary is not being attached correctly, or if the attachment is not being sent with the correct name. The name has to be "file", as occurs in the above curl, or it will not be recognized.

Please help!

Best Regards,
Alexander Birmingham




Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Paul Sandoz
Administrator

On Jan 5, 2010, at 8:40 PM, Alexander Birmingham wrote:

Paul -

Through a lot of brute force debugging, I've narrowed down the problem to a single line in the request.

This works:
Content-Type: multipart/form-data; boundary=----------------------------4e1144df2bb2

While this does not:
Content-Type: multipart/form-data;boundary=----------------------------4e1144df2bb2


Ugh.


Don't even want to admit how long it took for us to figure that one out. Apparently the server gets confused if there is whitespace after the semi-colon. This should probably become a bug report, but in the meantime can I specify that line manually with the Jersey client?


I think it may be possible to register a new writer for javax.ws.rs.core.MediaType.

In your client code create the following file:

 META-INF/services/com.sun.jersey.spi.HeaderDelegateProvider

And add the following line to that file:

  com.foo.provider.MediaTypeProvider

Also ensure that your client classes take precedence over Jersey jars in the class path. This will ensure that your MediaType writer will take precedence of Jersey's.

Then add the class defined at the end of the email to your code.

Of course all this is rather hacky. 

The only other solution i can think of is to write your own message body writer that defers to the MultiPart message body writer, To do this you need to wrap the MultiPart in your own type. Then this provider can write things out to a ByetArrayOutputStream and this gives you an opportunity to modify the Content-Type. In fact that might be cleanest solution.

@Produces("multipart/*")
public class MyMultiPartWriter implements MessageBodyWriter<MyMultiPart> {
  private final Providers p;

  public MyMultiPartWriter(@Context Providers p) {
    this.p = p;
  }

  public long getSize(MyMultiPart entity, Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType) {
      return -1;
  }

  public boolean isWriteable(Class<?> type, Type genericType,         
        Annotation[] annotations, MediaType mediaType) {
      return MyMultiPart.class.isAssignableFrom(type);
  }

  public void writeTo(MyMultiPart entity, Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType,
        MultivaluedMap<String, Object> headers,
        OutputStream stream) throws IOException, WebApplicationException {

    MessageBodyWriter<MultiPart> w = p.getMessageBodyWriter(MultiPart.class, MultiPart.class, null, MediaTypes.MULTIPART_FORM_DATA);

    // Note that you can avoid buffering the message if you write your own stream implementation
    // that modifies the header before the first byte is written
    ByteArrayOutputStream baos = ...
    w.write(entity.getMultiPart(), type, genericType, annotations, mediaType, headers, stream);

    // modify Content-Type from headers

    out.write(baos.toByteArray());
  }
}

You will need to register this provider with a ClientConfg:

  ClientConfig cc = new DefaultClientConfig();
  cc.getClasses().add(MyMultiPartWriter.class)
  Client c = Client.create(cc);

Hth,
Paul.

package com.foo.provider

import com.sun.jersey.core.header.reader.HttpHeaderReader;
import com.sun.jersey.spi.HeaderDelegateProvider;
import java.text.ParseException;
import java.util.Map;
import javax.ws.rs.core.MediaType;

public class MediaTypeProvider implements HeaderDelegateProvider<MediaType> {
    
    public boolean supports(Class<?> type) {
        return MediaType.class.isAssignableFrom(type);
    }

    public String toString(MediaType header) {
        StringBuilder b = new StringBuilder();
        b.append(header.getType()).
            append('/').
            append(header.getSubtype());
        boolean first = true;
        for (Map.Entry<String, String> e : header.getParameters().entrySet()) {
            b.append(';').
            if (first) {
                b.append(' ');
                first = false;
            }
            b.append(e.getKey()).
                append('=');
            WriterUtil.appendQuotedMediaType(b, e.getValue());
        }
        return b.toString();
    }

    public MediaType fromString(String header) {
        if (header == null)
            throw new IllegalArgumentException("Media type is null");
        
        try {
            return valueOf(HttpHeaderReader.newInstance(header));
        } catch (ParseException ex) {
            throw new IllegalArgumentException(
                    "Error parsing media type '" + header + "'", ex);
        }
    }

    public static MediaType valueOf(HttpHeaderReader reader) throws ParseException {
        // Skip any white space
        reader.hasNext();

        // Get the type
        String type = reader.nextToken();
        reader.nextSeparator('/');
        // Get the subtype
        String subType = reader.nextToken();

        Map<String, String> params = null;

        if (reader.hasNext())
            params = HttpHeaderReader.readParameters(reader);

        return new MediaType(type, subType, params);
    }
}
Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

sshapero
Hi Paul

Paul Sandoz wrote
> Through a lot of brute force debugging, I've narrowed down the  
> problem to a single line in the request.
>
> This works:
> Content-Type: multipart/form-data;  
> boundary=----------------------------4e1144df2bb2
>
> While this does not:
> Content-Type: multipart/form-
> data;boundary=----------------------------4e1144df2bb2
>
I believe I am running into this same problem when trying to post a jpg to a Ruby on Rails "service end point".  I'm coding a RESTful client using jersey, which produces this output:

--Boundary_1_7290583_1268425967724
Content-Type: text/plain
Content-Disposition: form-data;name="filename"

IMG_0168.jpg
--Boundary_1_6270141_1268366891364
Content-Type: image/jpg
Content-Disposition: form-data;name="file"

Notice the lack of whitespace between the semi-colon and the tokens around it.  

I copy and pasted your code as provided below.  My current, non-working code looks like this:

       File file = new File("/Users/steve/Desktop/IMG_0168.jpg");
       FormDataMultiPart multiPart = new FormDataMultiPart().field("filename", file.getName())
           .field("file", file, MediaType.valueOf("image/jpg"));
       
       ClientResponse response = r.type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class, multiPart);

I'm wondering, how do I consume the MediaTypeProvider that I have created in the above snippet?  Also, I noticed in the toString method this:

WriterUtil.appendQuotedMediaType(b, e.getValue());

What is WriterUtil?  I just put b.append(e.getValue()) since I couldn't find anything out about WriterUtil.

Thanks so much for any insight you can provide.
-Steve
Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Paul Sandoz
Administrator
Hi Steve,

This seems to be the opposite or at least a problem.

In Alexander's case it was a white space between the semi-colon and the boundary parameter of the media type that cause an issue:

  This works:
  Content-Type: multipart/form-data; boundary=----------------------------4e1144df2bb2

  While this does not:
  Content-Type: multipart/form-data;boundary=----------------------------4e1144df2bb2


In your case it is *not* a white space between a semi-colon and a parameter.

  Content-Disposition: form-data;name="file"

I think this is a bug in the RoR end point. Note that the spec of Content-Disposition does not require a space character:


     disposition := "Content-Disposition" ":"
                    disposition-type
                    *(";" disposition-parm)

     disposition-type := "inline"
                       / "attachment"
                       / extension-token
                       ; values are not case-sensitive

     disposition-parm := filename-parm
                       / creation-date-parm
                       / modification-date-parm
                       / read-date-parm
                       / size-parm
                       / parameter


For WriterUtil see here:


The appendQuotedMediaType determines if a value needs to be quoted or not.


I do not understand what you mean when you say:

  I'm wondering, how do I consume the MediaTypeProvider that I have created in
  the above snippet?

Paul.

On Mar 12, 2010, at 9:41 PM, sshapero wrote:


Hi Paul


Paul Sandoz wrote:

Through a lot of brute force debugging, I've narrowed down the  
problem to a single line in the request.

This works:
Content-Type: multipart/form-data;  
boundary=----------------------------4e1144df2bb2

While this does not:
Content-Type: multipart/form-
data;boundary=----------------------------4e1144df2bb2



I believe I am running into this same problem when trying to post a jpg to a
Ruby on Rails "service end point".  I'm coding a RESTful client using
jersey, which produces this output:

--Boundary_1_7290583_1268425967724
Content-Type: text/plain
Content-Disposition: form-data;name="filename"

IMG_0168.jpg
--Boundary_1_6270141_1268366891364
Content-Type: image/jpg
Content-Disposition: form-data;name="file"

Notice the lack of whitespace between the semi-colon and the tokens around
it.  

I copy and pasted your code as provided below.  My current, non-working code
looks like this:

      File file = new File("/Users/steve/Desktop/IMG_0168.jpg");
      FormDataMultiPart multiPart = new
FormDataMultiPart().field("filename", file.getName())
          .field("file", file, MediaType.valueOf("image/jpg"));

      ClientResponse response =
r.type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class,
multiPart);

I'm wondering, how do I consume the MediaTypeProvider that I have created in
the above snippet?  Also, I noticed in the toString method this:

WriterUtil.appendQuotedMediaType(b, e.getValue());

What is WriterUtil?  I just put b.append(e.getValue()) since I couldn't find
anything out about WriterUtil.

Thanks so much for any insight you can provide.
-Steve

--
View this message in context: http://n2.nabble.com/Multipart-Post-tp4252846p4724316.html
Sent from the Jersey mailing list archive at Nabble.com.

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

sshapero
Hi Paul

I decided to change strategies and wrote an implementation of MessageBodyWriter and created MyMultiPart to go with it.  While going against what I know to be best practices, the former is just a copy of the standard MultiPartWriter, while the ladder extends FormDataMultiPart.  I added this crass bit to MyMultiPartWriter.writeTo to solve my issue:

    String hackedContentDisposition = bodyPart.getContentDisposition().toString();
    hackedContentDisposition = hackedContentDisposition.replace(";name", "; name");
    bodyHeaders.putSingle("Content-Disposition", hackedContentDisposition);

Horrid I know but it gets 'er done.  I didn't quite grasp how I could use the BAOS to avoid doing what I did.  I feel ok about it, as we are going to change the architecture down the road to eliminate the need for this particular service end point (hahahaha I know famous last words).  

Thanks for taking the time to read my post and help out, it is sincerely appreciated.  We've invested a lot of time in using the jersey client libraries so I'm happy we found a way to work it out.

Thanks again
Steve
Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Paul Sandoz
Administrator
Hi Steve,

Glad you found a way to solve this, since this is particular tricky to  
override.

IMHO i would also try and log an issue against RoR.

Paul.

On Mar 16, 2010, at 8:18 AM, sshapero wrote:

>
> Hi Paul
>
> I decided to change strategies and wrote an implementation of
> MessageBodyWriter and created MyMultiPart to go with it.  While going
> against what I know to be best practices, the former is just a copy  
> of the
> standard MultiPartWriter, while the ladder extends  
> FormDataMultiPart.  I
> added this crass bit to MyMultiPartWriter.writeTo to solve my issue:
>
>    String hackedContentDisposition =
> bodyPart.getContentDisposition().toString();
>    hackedContentDisposition =  
> hackedContentDisposition.replace(";name", ";
> name");
>    bodyHeaders.putSingle("Content-Disposition",  
> hackedContentDisposition);
>
> Horrid I know but it gets 'er done.  I didn't quite grasp how I  
> could use
> the BAOS to avoid doing what I did.  I feel ok about it, as we are  
> going to
> change the architecture down the road to eliminate the need for this
> particular service end point (hahahaha I know famous last words).
>
> Thanks for taking the time to read my post and help out, it is  
> sincerely
> appreciated.  We've invested a lot of time in using the jersey client
> libraries so I'm happy we found a way to work it out.
>
> Thanks again
> Steve
> --
> View this message in context: http://n2.nabble.com/Multipart-Post-tp4252846p4742164.html
> Sent from the Jersey mailing list archive at Nabble.com.
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

sshapero
 Hi Paul

There is an issue logged against RoR already for the issue, but because RoR requires you to hardcode dependencies into your actual code, we weren't able to use the work around (you have to search and replace for version numbers in the Rails source code, which then requires changing your deployment process to use your hand-rolled Rails stack).  This whole experience has me very thankful for Maven.

So I've now got my Content-Disposition with the space after the semi-colon, resolving part 1 of my problem.  Here's my next question.  I'd like the Content-Disposition header to look like this:

Content-Disposition: form-data; name="image"; filename="foo.jpg"
Content-Type: image/jpeg

What kind of MultiPart allows me to have the 2 key-value pairs as a part of one header?  Right now I do this:

MyMultiPart multiPart = new MyMultiPart().field("filename", file.getName())
           .field("image", file, MediaType.valueOf("image/jpg"));

(Recall MyMultiPart inherits from FormDataMultiPart).  I get this output:

--Boundary_1_5579641_1268720872443
Content-Type: text/plain
Content-Disposition: form-data; name="filename"

foo.jpg
--Boundary_1_5579641_1268720872443
Content-Type: image/jpg
Content-Disposition: form-data; name="image"

TIA
Steve
Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

Paul Sandoz
Administrator
Hi Steve,

See the class FormDataContentDisposition class and the builder of that:

https://jersey.dev.java.net/nonav/apidocs/latest/jersey/com/sun/jersey/core/header/FormDataContentDisposition.FormDataContentDispositionBuilder.html

you can add instances of FormDataBodyPart to the multipart:

https://jersey.dev.java.net/nonav/apidocs/latest/contribs/jersey-multipart/com/sun/jersey/multipart/FormDataBodyPart.html 
#FormDataBodyPart
%28com.sun.jersey.core.header.FormDataContentDisposition,
%20java.lang.Object,%20javax.ws.rs.core.MediaType%29

Also take a look at FileDataBodyPart:

https://jersey.dev.java.net/nonav/apidocs/latest/contribs/jersey-multipart/com/sun/jersey/multipart/file/FileDataBodyPart.html

which might better suite your needs, which will also set the  
modification date and size

Paul.

On Mar 16, 2010, at 6:15 PM, sshapero wrote:

>
> Hi Paul
>
> There is an issue logged against RoR already for the issue, but  
> because RoR
> requires you to hardcode dependencies into your actual code, we  
> weren't able
> to use the work around (you have to search and replace for version  
> numbers
> in the Rails source code, which then requires changing your deployment
> process to use your hand-rolled Rails stack).  This whole experience  
> has me
> very thankful for Maven.
>
> So I've now got my Content-Disposition with the space after the semi-
> colon,
> resolving part 1 of my problem.  Here's my next question.  I'd like  
> the
> Content-Disposition header to look like this:
>
> Content-Disposition: form-data; name="image"; filename="foo.jpg"
> Content-Type: image/jpeg
>
> What kind of MultiPart allows me to have the 2 key-value pairs as a  
> part of
> one header?  Right now I do this:
>
> MyMultiPart multiPart = new MyMultiPart().field("filename",  
> file.getName())
>           .field("image", file, MediaType.valueOf("image/jpg"));
>
> (Recall MyMultiPart inherits from FormDataMultiPart).  I get this  
> output:
>
> --Boundary_1_5579641_1268720872443
> Content-Type: text/plain
> Content-Disposition: form-data; name="filename"
>
> foo.jpg
> --Boundary_1_5579641_1268720872443
> Content-Type: image/jpg
> Content-Disposition: form-data; name="image"
>
> TIA
> Steve
> --
> View this message in context: http://n2.nabble.com/Multipart-Post-tp4252846p4745023.html
> Sent from the Jersey mailing list archive at Nabble.com.
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Multipart Post

sshapero
In reply to this post by sshapero
Agh, sorry, I found FileDataBodyPart.  This works perfectly!  Image successfully uploaded.  For those wondering or who google this in the future:

       MyMultiPart multiPart = new MyMultiPart();
       multiPart.bodyPart(new FileDataBodyPart("image", file, MediaType.valueOf("image/jpg")));

public class MyMultiPart extends FormDataMultiPart { }

Thanks
Steve