-- Brice
On Tue, Nov 6, 2012 at 11:09 AM, Sergey Beryozkin <[email protected]>wrote: > Hi > > On 05/11/12 23:00, Brice Dutheil wrote: > >> Hi, >> >> To get a bigger picture let me explain what I would like to actually >> craft : >> >> In a multipart POST request, I'd like to have form params and a file >> attachement (like the example above). And I would like to handle myself >> the >> inputstream of the file. In order do stuff like >> - checking some headers, for example Content-Length on one of the >> Attachement, Content-Disposition etc >> - consuming the content of the given inputstream of this part to store >> it >> in a temporary file >> >> However in the MessageBodyReader, the entityStream looks like it's been >> closed and already consumed. Debugging reveals that an >> AttatchmentDeserializer already consumed the stream, and created an >> Attachement collection, however my provider wasn't called at that time. If >> the opportunity is available I would like to copy these bytes to another >> outputstream. >> >> The provider for TemporaryBinaryFile is called later, when individual > parts are deserialized. > > > Is it possible or should I use attachments ? I'd like as much as possible >> avoid technical code in the resource, and have a reference to a >> TemporaryBinaryFile. >> >> > You can use org.apache.cxf.jaxrs.ext.**multipart.Attachment instead of > TemporaryBinaryFile, check Content-Type and Content-Disposition, and then > do 'attachment.getObject(**TemporaryBinaryFile.class)': > > post(@Multipart("someid") Attachment attachment) { > attachment.getContentType(); > attachment.**getContentDisposition(); > attachment.getObject(**TemporaryBinaryFile.class) > } > > Actually, you can optimize it slightly by adding a 'type' parameter to > @Multipart(value = "someid", type = "text/plain") > Ok, thx for that :) Do you think it will be possible to stream directly the content of the attachment to another outputstream ? The attachment can have a large size like 20 MB maybe more, I'd like to keep memory consumption as low as possible. > More comments below > > >> >> >> Here's my comment on Content-Disposition : >> >> >> >> On Mon, Nov 5, 2012 at 11:17 PM, Sergey Beryozkin<[email protected]** >> >wrote: >> >> Hi >>> >>> >>> On 05/11/12 19:27, Brice Dutheil wrote: >>> >>> Hi, >>>> >>>> I'm crafting a resource that should accept multipart POST request. >>>> >>>> Here's the method : >>>> >>>> ==============================****================== >>>> @POST >>>> @Produces({MediaType.****APPLICATION_JSON}) >>>> @Consumes(MediaType.MULTIPART_****FORM_DATA) >>>> >>>> public MetaData archive(@FormParam("title") String title, >>>> @FormParam("revision") String >>>> revision, >>>> @Multipart("archive") >>>> TemporaryBinaryFile >>>> temporaryBinaryFile) { >>>> ==============================****================== >>>> >>>> >>>> Also I tried with @Multipart instead of @FormParam >>>> >>>> ==============================****================== >>>> @POST >>>> @Produces({MediaType.****APPLICATION_JSON}) >>>> @Consumes(MediaType.MULTIPART_****FORM_DATA) >>>> >>>> public DocumentMetaData archive(@Multipart(value = "title", >>>> required = >>>> false) @FormParam("title") String title, >>>> @Multipart(value = "revision", >>>> required = >>>> false) String revision, >>>> @Multipart("archive") >>>> TemporaryBinaryFile >>>> temporaryBinaryFile) { >>>> >>>> >>> You have @FormParam and @Multipart attached to 'title', drop @FormParam, >>> I >>> think it only works because 'title' is a simple parameter. >>> >>> >>> >> Yes I wrongly copied/ modified the code in the mail, however I tested both >> setup separately. >> Anyway, as you advised me I will inly use Multipart now. >> >> >> >> >>> ==============================****================== >>> >>>> >>>> And here is the raw request : >>>> ==============================****================== >>>> Address: >>>> http://localhost:8080/api/v1.****0/document/archive<http://localhost:8080/api/v1.**0/document/archive> >>>> <http://**localhost:8080/api/v1.0/**document/archive<http://localhost:8080/api/v1.0/document/archive> >>>> > >>>> Encoding: ISO-8859-1 >>>> Http-Method: POST >>>> Content-Type: multipart/form-data;boundary=****partie >>>> Headers: {Accept=[*/*], accept-charset=[ISO-8859-1,** >>>> utf-8;q=0.7,*;q=0.3], >>>> accept-encoding=[gzip,deflate,****sdch], Content-Length=[301], >>>> content-type=[multipart/form-****data;boundary=partie]} >>>> >>>> Payload: >>>> --partie >>>> Content-Disposition: form-data; name="title" >>>> Content-ID: title >>>> >>>> the.title >>>> --partie >>>> Content-Disposition: form-data; name="revision" >>>> Content-ID: revision >>>> >>>> some.revision >>>> --partie >>>> Content-Disposition: form-data; name="archive"; filename="file.txt" >>>> Content-Type: text/plain >>>> >>>> I've got a woman, way over town... >>>> --partie >>>> ==============================****================== >>>> >>>> >>>> However the title and revision values are incorrect because they are >>>> ended >>>> by a new line char '\n'. Hence these parameters are not validated by my >>>> validator (which is using Message.getContent), >>>> >>>> I don't think this is a normal behavior, but I might be wrong, maybe >>>> about >>>> the specs, or my request. Note that I had to add the Content-ID when >>>> using >>>> the Multipart annotation. >>>> >>>> >>> What CXF version is it ? Content-Disposition 'name' is definitely checked >>> too. >>> >>> >>> >> >> Also I found part of the code that should check the Content-Disposition, >> however I have found that the first letter 'C' disappeared and the key in >> the attachment header is now 'ontent-Disposition' which can complicate >> things further, and probably explains why, I needed a Content-ID header in >> each part. Although the first part got his header Content-Disposition >> always correctly decoded. Adding another new line after the boundary fixes >> looks like a workaround though, but i'd rather not impose this on the API >> users :/ >> >> I couldn't figure out yet where the code could is consuming the additional >> char. I just know that at some point, the LazyAttachmentCollection has the >> remaining attachment (AttachmentImpl), and the first header is wrong. >> >> > I think it is the bug of the code the posts the multipart, I recall > exactly the same issue reported when RESTClient was used > Isn't it this issue ? https://issues.apache.org/jira/browse/CXF-2704 > >> About Content-Disposition name, it is checked only if there is no >> Content-ID, however it seems at some point the default Content-ID is >> added " >> [email protected]", which defeats the purpose of the following >> code. >> >> private static boolean *matchAttachmentId(Attachment at, Multipart >> mid, >> MediaType multipartType)* { >> if (at.getContentId().equals(mid.**value())) { >> return true; >> } >> ContentDisposition cd = at.getContentDisposition(); >> if (cd != null&& mid.value().equals(cd.**getParameter("name"))) >> { >> return true; >> } >> return false; >> } >> >> default Content-ID is added on the output, it is not added during the > read... > I'm not 100% sure how everything worked, but at some point the MultipartProvider.readFrom is called from the JAXRSUtils.readFromMessageBodyReader, which will indirectly call the above code : public Object *readFrom*(Class<Object> c, Type t, Annotation[] anns, MediaType mt, MultivaluedMap<String, String> headers, InputStream is) throws IOException, WebApplicationException { // ... Multipart id = AnnotationUtils.getAnnotation(anns, Multipart.class); Attachment multipart = *AttachmentUtils.getMultipart(c, id, mt, infos)*; if (multipart != null) { return fromAttachment(multipart, c, t, anns); } else if (id != null && !id.required()) { // ... } public static Attachment getMultipart(Class<Object> c, Multipart id, MediaType mt, List<Attachment> infos) throws IOException { if (id != null) { for (Attachment a : infos) { if (*matchAttachmentId(a, id, mt)*) { checkMediaTypes(a.getContentType(), id.type()); return a; } } // ... } I'm not sure of the implications, but it might be possible to fix this with the following code : private static boolean matchAttachmentId(Attachment at, Multipart mid, MediaType multipartType) { ContentDisposition cd = at.getContentDisposition(); boolean matchContentDispositionName = cd != null && mid.value().equals(cd.getParameter("name")); boolean matchContentId = at.getContentId().equals(mid.value()); return matchContentId || matchContentDispositionName; } Many thanx for your support ! Cheers --Brice
