users@jersey.java.net

Re: [Jersey] Multipart Post

From: Paul Sandoz <Paul.Sandoz_at_Sun.COM>
Date: Wed, 06 Jan 2010 12:27:39 +0100

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