Jersey 2 multipart/form-data issue. InputStream is empty (available=0) - java

I am facing issue with jersey 2 file upload. Input stream is coming empty to server side. Using jersey 2.21, jackson 2.5.4, spring 4.1.6.RELEASE (for DI only) & spring security 4.0.2.RELEASE for security. Using JDK 1.8.0_25 and Tomcat 8.0.26.
Code:
#POST
#Path("/upload")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
public SimpleResult categoryImageUpload(
#FormDataParam("file") InputStream file,
#FormDataParam("file") FormDataBodyPart bodyPart) {
return SimpleResult.success("File Uploaded successfully!!!");
}
File Details is coming in FormDataBodyPart, but InputStream is coming empty(available=0).
Jersey configuration:
#ApplicationPath("api-business")
public class BusinessApplicationConfig extends ResourceConfig {
public BusinessApplicationConfig() {
register(RequestContextFilter.class);
register(MultiPartFeature.class);
packages("com.smx.biz.api");
}
}
dependencies in pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.4</version>
</dependency>
<!--Jersey-->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>2.21</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.21</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.21</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.21</version>
</dependency>
<!-- Jersey + Spring -->
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.21</version>
</dependency>
Could somebody help with this issue? Am I missing something???
PS: Spring REST file upload code is working well & InputStream is coming. But Jersey code is not working. Using same client side code to test apis.
Working Spring REST api code:
#ResponseStatus(HttpStatus.OK)
#RequestMapping(value = "/business/upload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
ImageItem categoryPhotoUpload(#RequestBody MultipartFile file) {
return uploadService.uploadFile(file);
}
I want to use Jersey for apis & I don't want to use Spring REST.
Could somebody help with this issue?

I found that when you use the #FormDataParam("file") InputStream file method, under the hood the parameter never gets processed because jersey is actually looking for a File. What happens (at least from what i have read so far) is that when the request comes in jersey does some mime checking using the mimepull library and in turn saves the incoming file as a temporary file. The issue is that if your parameter type is InputStream, jersey does not handle it because there is no ValueFactory registered for InputStream. So in order for this to work you have to do the following.
Inside FormDataParamValueFactoryProvider
Add the following implementation:
private final class InputStreamFactory extends ValueFactory<InputStream> {
private final String name;
public InputStreamFactory(final String name) {
this.name = name;
}
#Override
public InputStream provide() {
LOG.info("Processing paramaeter [" + name + "]");
final FormDataBodyPart part = getEntity().getField(name);
final BodyPartEntity entity = part != null ? part.getEntityAs(BodyPartEntity.class) : null;
if (entity != null) {
try {
// Create a temporary file.
final File file = Utils.createTempFile();
// Move the part (represented either via stream or file) to the specific temporary file.
entity.moveTo(file);
//Retreive file via a FileInputStream
return new FileInputStream(file);
} catch (final Exception ex) {
// Unable to create a temporary file or move the file.
LOG.warn("Error while processing InputStream. " + ex);
}
}
return null;
}
}
This will allow jersey to detect the InputStream.
You also have to change the createValueFactory method to reflect the new ValueFactoryProvider.
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
final Class<?> rawType = parameter.getRawType();
if (Parameter.Source.ENTITY == parameter.getSource()) {
if (FormDataMultiPart.class.isAssignableFrom(rawType)) {
return new FormDataMultiPartFactory();
} else {
return null;
}
} else if (parameter.getSourceAnnotation().annotationType() == FormDataParam.class) {
final String paramName = parameter.getSourceName();
if (paramName == null || paramName.isEmpty()) {
// Invalid query parameter name
return null;
}
if (Collection.class == rawType || List.class == rawType) {
final Class clazz = ReflectionHelper.getGenericTypeArgumentClasses(parameter.getType()).get(0);
if (FormDataBodyPart.class == clazz) {
// Return a collection of form data body part.
return new ListFormDataBodyPartValueFactory(paramName);
} else if (FormDataContentDisposition.class == clazz) {
// Return a collection of form data content disposition.
return new ListFormDataContentDispositionFactory(paramName);
} else {
// Return a collection of specific type.
return new FormDataParamValueFactory(parameter, get(parameter));
}
} else if (FormDataBodyPart.class == rawType) {
return new FormDataBodyPartFactory(paramName);
} else if (FormDataContentDisposition.class == rawType) {
return new FormDataContentDispositionFactory(paramName);
} else if (File.class == rawType) {
return new FileFactory(paramName);
} else if (InputStream.class == rawType) {
return new InputStreamFactory(paramName);
} else {
return new FormDataParamValueFactory(parameter, get(parameter));
}
}
return null;
}
Now... here's where it becomes a pain... if you dont pull the module source from github and and compile with the changes.... you have to basically recreate the following classes in order to reference the new class through the reference chain.
Classes:
FormDataParamValueFactoryProvider
FormDataParamInjectionFeature (References: FormDataParamValueFactoryProvider)
MultiPartFeature (References: FormDataParamInjectionFeature)
Once this is done, you can then use #FormDataParam("file") InputStream and it will work as expected.

Make sure that the string inside the annotation
#FormDataParam("theSameStringUsedInAnnotation") InputStream file
exactly matches the name of the resource that you are posting.
In my case I was using ExtJs fileupload and when you define there a fileupload like:
xtype: 'filefield',
name: 'theSameStringUsedInAnnotation',
you have to define the same string

In My case I replaced #FormDataParam("file") InputStream file with #FormDataParam("file") File file then it started working fine.

Related

how to use multipart/form-data in spring mvc

#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Path("/uploadFile")
public POSResponse singleSave(#FormDataParam("file") MultipartFile file) {
Response response = new Response();
String fileName = null;
if (!file.isEmpty()) {
try {
fileName = file.getOriginalFilename();
byte[] bytes = file.getBytes();
BufferedOutputStream buffStream = new BufferedOutputStream(new FileOutputStream(new File("d:\\" + fileName)));
buffStream.write(bytes);
buffStream.close();
} catch (Exception e) {
}
} else {
}
return response;
}
when i hit this api then i got the error "415: Unsupported Media Type"
this means not supported header.I want to load file from ARC to controller.
and my console:
A message body reader for Java class org.springframework.web.multipart.MultipartFile, and Java type interface org.springframework.web.multipart.MultipartFile, and MIME media type multipart/form-data; boundary=----WebKitFormBoundaryP1d7Atv9FO9wU301 was not found.
The registered message body readers compatible with the MIME media type are:
/ ->
com.sun.jersey.core.impl.provider.entity.FormProvider
com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider
com.sun.jersey.core.impl.provider.entity.StringProvider
com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
com.sun.jersey.core.impl.provider.entity.FileProvider
com.sun.jersey.core.impl.provider.entity.InputStreamProvider
com.sun.jersey.core.impl.provider.entity.DataSourceProvider
i have add some maven dependency in pom.xml file.
My pom file:
<!-- multipart file dependency -->
<dependency>
<groupId>org.jvnet</groupId>
<artifactId>mimepull</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.jvnet.mimepull</groupId>
<artifactId>mimepull</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.17.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>1.17.1</version>
</dependency>
I am not sure about the code which you have written but this how i did it in spring-mvc
Use case: uploading Images
Add a bean definition in our web application's context configuration file
(DispatcherServlet-context.xml) for CommonsMultipartResolver,
as follows:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10240000"/>
Add dependency to pom.xml
enter Group Id as commons-fileupload, Artifact Id as commons-fileupload, Version as 1.2.2; select Scope as compile; and click on the OK button.
Similarly, add one more Group Id dependency as org.apache.commons, Artifact
Id as commons-io, Version as 1.3.2; select Scope as compile; click on the OK
button; and save the pom.xml file
Add a reference to
org.springframework.web.multipart.MultipartFile with the
corresponding setters and getters in the java class the defines the file as a property as follows:
#JsonIgnore
private MultipartFile productImage;
#XmlTransient
public MultipartFile getProductImage() {
return productImage;
}
public void setProductImage(MultipartFile productImage) {
this.productImage = productImage;
}
In the jsp where the file is to be uploaded use the following tag
Set the enctype attribute to multipart/form-data in the form tag as
follows in the jsp
Note: I am using spring form tag libraries
<form:form modelAttribute="newProduct" class="form-horizontal" enctype="multipart/form-data">
Add the following code to the controller
public String processAddNewProductForm(#ModelAttribute("newProduct") #Valid Product productToBeAdded, BindingResult result, HttpServletRequest request) {
if(result.hasErrors()) {
return "addProduct";
}
MultipartFile productImage = productToBeAdded.getProductImage();
String rootDirectory = request.getSession().getServletContext().getRealPath("/");
if (productImage!=null && !productImage.isEmpty()) {
try {
productImage.transferTo(new File(rootDirectory+"resources\\images\\"+productToBeAdded.getProductId() + ".png"));
} catch (Exception e) {
throw new RuntimeException("Product Image saving failed", e);
}
}
productService.addProduct(productToBeAdded);
return "redirect:/products";
}
****Prerequisites****
spring project has been setup correctly, wiring is done
few annotations and lines of code are specific to my project and may not be totally relevant

JAX RS, test ETags with JAX RS Client API, Jersey, Glassfish

I always get java.text.ParseException: End of header. exception.
#GET
public Response getTemplate4CustomSkin(#Context final Request request)
{
EntityTag eTag = new EntityTag("123456789");
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
if (responseBuilder == null)
{
System.out.printf("is null");
}
else
{
System.out.printf("is not null");
}
return Response.ok(data.inputStream, data.mimeType).tag(eTag).build();
}
}
#Test
public void testGetFileEtagIsNotChanged() throws UnsupportedEncodingException
{
Client client = ClientBuilder.newClient();
WebTarget target = client.target("someUrl");
EntityTag eTag = new EntityTag("123456789");
Response response = target.request().get();
//send request 2nd time with
response = target.request().header("If-None-Match", response.getEntityTag()).get();
//same result
//response = target.request().header("If-None-Match", response.getEntityTag().getValue()).get();
Assert.assertEquals(eTag, response.getEntityTag());
}
// the following code always throws an exception inside of org.glassfish.jersey.message.internal.HttpHeaderReaderImpl.java class:
private char getNextCharacter(boolean skipWhiteSpace) throws ParseException {
....
// this line of code always throws it:
if(this.index >= this.length) {
throw new ParseException(LocalizationMessages.HTTP_HEADER_END_OF_HEADER(), this.index);
} else {
return this.header.charAt(this.index);
}
}
I run tests via arquillian, dependency versions:
<!--Arquillian JUnit integration: -->
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<version>1.1.8.Final</version>
<scope>test</scope>
</dependency>
<!--glassfish-embedded:-->
<dependency>
<groupId>org.glassfish.main.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-glassfish-embedded-3.1</artifactId>
<version>1.0.0.CR4</version>
<scope>test</scope>
</dependency>
How can I resolve it? How can I send a request with "If-None-Match" header inlcuded and don't get an exception?
EDIT:
In order to avoid the error, I had to use wildfly embedded application server instead of glassfish.

no suitable HttpMessageConverter found for response type

Using spring, with this code :
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
for(HttpMessageConverter httpMessageConverter : messageConverters){
System.out.println(httpMessageConverter);
}
ResponseEntity<ProductList> productList = restTemplate.getForEntity(productDataUrl,ProductList.class);
I get
org.springframework.http.converter.ByteArrayHttpMessageConverter#34649ee4
org.springframework.http.converter.StringHttpMessageConverter#39fba59b
org.springframework.http.converter.ResourceHttpMessageConverter#383580da
org.springframework.http.converter.xml.SourceHttpMessageConverter#409e850a
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#673074aa
org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter#1e3b79d3
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#52bb1b26
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.mycopmany.ProductList] and content type [text/html;charset=UTF-8]
The a snippet of the pojo :
#XmlRootElement(name="TheProductList")
public class ProductList {
#XmlElement(required = true, name = "date")
private LocalDate importDate;
From a Spring point of view, none of the HttpMessageConverter instances registered with the RestTemplate can convert text/html content to a ProductList object. The method of interest is HttpMessageConverter#canRead(Class, MediaType). The implementation for all of the above returns false, including Jaxb2RootElementHttpMessageConverter.
Since no HttpMessageConverter can read your HTTP response, processing fails with an exception.
If you can control the server response, modify it to set the Content-type to application/xml, text/xml, or something matching application/*+xml.
If you don't control the server response, you'll need to write and register your own HttpMessageConverter (which can extend the Spring classes, see AbstractXmlHttpMessageConverter and its sub classes) that can read and convert text/html.
You could also simply tell your RestTemplate to accept all media types:
#Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
If you are using Spring Boot, you might want to make sure you have the Jackson dependency in your classpath. You can do this manually via:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
Or you can use the web starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
In addition to all the answers, if you happen to receive in response text/html while you've expected something else (i.e. application/json), it may suggest that an error occurred on the server side (say 404) and the error page was returned instead of your data.
So it happened in my case. Hope it will save somebody's time.
If you can't change server media-type response, you can extend GsonHttpMessageConverter to process additional support types
public class MyGsonHttpMessageConverter extends GsonHttpMessageConverter {
public MyGsonHttpMessageConverter() {
List<MediaType> types = Arrays.asList(
new MediaType("text", "html", DEFAULT_CHARSET),
new MediaType("application", "json", DEFAULT_CHARSET),
new MediaType("application", "*+json", DEFAULT_CHARSET)
);
super.setSupportedMediaTypes(types);
}
}
You can make up a class, RestTemplateXML, which extends RestTemplate. Then override doExecute(URI, HttpMethod, RequestCallback, ResponseExtractor<T>), and explicitly get response-headers and set content-type to application/xml.
Now Spring reads the headers and knows that it is `application/xml'. It is kind of a hack but it works.
public class RestTemplateXML extends RestTemplate {
#Override
protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor) throws RestClientException {
logger.info( RestTemplateXML.class.getSuperclass().getSimpleName() + ".doExecute() is overridden");
Assert.notNull(url, "'url' must not be null");
Assert.notNull(method, "'method' must not be null");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
// Set ContentType to XML
response.getHeaders().setContentType(MediaType.APPLICATION_XML);
if (!getErrorHandler().hasError(response)) {
logResponseStatus(method, url, response);
}
else {
handleResponseError(method, url, response);
}
if (responseExtractor != null) {
return responseExtractor.extractData(response);
}
else {
return null;
}
}
catch (IOException ex) {
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + url + "\":" + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
if (logger.isDebugEnabled()) {
try {
logger.debug(method.name() + " request for \"" + url + "\" resulted in " +
response.getRawStatusCode() + " (" + response.getStatusText() + ")");
}
catch (IOException e) {
// ignore
}
}
}
private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException {
if (logger.isWarnEnabled()) {
try {
logger.warn(method.name() + " request for \"" + url + "\" resulted in " +
response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
}
catch (IOException e) {
// ignore
}
}
getErrorHandler().handleError(response);
}
}
Try this:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
Or you can use
public void setSupportedMediaTypes(List supportedMediaTypes)
method which belongs to AbstractHttpMessageConverter<T>, to add some ContentTypes you like. This way can let the MappingJackson2HttpMessageConverter canRead() your response, and transform it to your desired Class, which on this case,is ProductList Class.
and I think this step should hooked up with the Spring Context initializing. for example, by using
implements ApplicationListener {
...
}
A refinement of Vadim Zin4uk's answer is just to use the existing GsonHttpMessageConverter class but invoke the setSupportedMediaTypes() setter.
For spring boot apps, this results into adding to following to your configuration classes:
#Bean
public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
List<MediaType> supportedMediaTypes = converter.getSupportedMediaTypes();
if (! supportedMediaTypes.contains(TEXT_PLAIN)) {
supportedMediaTypes = new ArrayList<>(supportedMediaTypes);
supportedMediaTypes.add(TEXT_PLAIN);
converter.setSupportedMediaTypes(supportedMediaTypes);
}
return converter;
}
This is not answering the problem but if anyone comes to this question when they stumble upon this exception of no suitable message converter found, here is my problem and solution.
In Spring 4.0.9, we were able to send this
JSONObject jsonCredential = new JSONObject();
jsonCredential.put(APPLICATION_CREDENTIALS, data);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity<String> res = restTemplate.exchange(myRestUrl), HttpMethod.POST,request, String.class);
In Spring 4.3.5 release, we starting seeing errors with the message that converter was not found.
The way Convertors work is that if you have it in your classpath, they get registered.
Jackson-asl was still in classpath but was not being recognized by spring. We replaced Jackson-asl with faster-xml jackson core.
Once we added I could see the converter being registered.
I also had the same error message :
"Could not extract response: no suitable HttpMessageConverter found for response type ..."
This occured when I was trying to get info from a link that did not return the object type I wanted to convert or when the link did not return anything. I handled it using a try catch block :
try {
status = restTemplate
.getForObject(statusResourceUrl, Status.class);
//TODO add new exceptions if necessary or simply use Exception
} catch (BeanCreationException | UnknownContentTypeException | HttpClientErrorException e) {
status.setStatus("DOWN");
System.out.println("exception " + e.getMessage());
}
I was also facing the same issue in last week. Tried the above solution which marked as accepted but didnt worked.
When this will come : While calling the external URL(REST call) and the response is the complex object.
What was wrong : was adding unnecessary the converter using the below code
org.springframework.web.client.restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
Solution: Just add the below dependency
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
Because spring boot add all the default message converters. No need to add any extra.
Not even any JaxB dependency. If present please delete and Try it will work
Danke!!

Multipart File Upload on Google Appengine using jersey-1.7

I wrote an application on Google Appengine with Jersey to handle simple file uploading. This works fine when it was on jersey 1.2. In the later versions (current 1.7) #FormDataParam is introduced to handle multipart/form inputs. I am using jersey-multipart and the mimepull dependency. It seems that the new way of doing it is creating temporary files in appengine which we all know is illegal...
Am I missing something or doing something wrong here since Jersey is now supposedly compatible with AppEngine?
#POST
#Path("upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public void upload(#FormDataParam("file") InputStream in) { .... }
The above will fail when called with these exceptions...
/upload
java.lang.SecurityException: Unable to create temporary file
at java.io.File.checkAndCreate(File.java:1778)
at java.io.File.createTempFile(File.java:1870)
at java.io.File.createTempFile(File.java:1907)
at org.jvnet.mimepull.MemoryData.createNext(MemoryData.java:87)
at org.jvnet.mimepull.Chunk.createNext(Chunk.java:59)
at org.jvnet.mimepull.DataHead.addBody(DataHead.java:82)
at org.jvnet.mimepull.MIMEPart.addBody(MIMEPart.java:192)
at org.jvnet.mimepull.MIMEMessage.makeProgress(MIMEMessage.java:235)
at org.jvnet.mimepull.MIMEMessage.parseAll(MIMEMessage.java:176)
at org.jvnet.mimepull.MIMEMessage.getAttachments(MIMEMessage.java:101)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readMultiPart(MultiPartReaderClientSide.java:177)
at com.sun.jersey.multipart.impl.MultiPartReaderServerSide.readMultiPart(MultiPartReaderServerSide.java:80)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:139)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:77)
at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:474)
at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:538)
Anyone have a clue? Is there a way to do thing while preventing mimepull from creating the temporary file?
For files beyond its default size, multipart will create a temporary file. To avoid this — creating a file is impossible on gae — you can create a jersey-multipart-config.properties file in the project's resources folder and add this line to it:
bufferThreshold = -1
Then, the code is the one you gave:
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response post(#FormDataParam("file") InputStream stream, #FormDataParam("file") FormDataContentDisposition disposition) throws IOException {
post(file, disposition.getFileName());
return Response.ok().build();
}
For the benefit of those struggling when using Eclipse with GPE (Google Plugin for Eclipse) I give this slightly modified solution derived from #yves' answer.
I have tested it with App Engine SDK 1.9.10 and Jersey 2.12. It will not work with App Engine SDK 1.9.6 -> 1.9.9 amongst others due to a different issue.
Under your \war\WEB-INF\classes folder create a new file called jersey-multipart-config.properties. Edit the file so it contains the line jersey.config.multipart.bufferThreshold = -1.
Note that the \classes folder is hidden in Eclipse so look for the folder in your operating system's file explorer (e.g. Windows Explorer).
Now, both when the multipart feature gets initialized (on Jersey servlet initialization) and when a file upload is done (on Jersey servlet post request) the temp file will not be created anymore and GAE won't complain.
It is very important to put the file jersey-multipart-config.properties under WEB-INF/classes inside the WAR.
Usually in a WAR file structure you put the config files (web.xml, appengine-web.xml) into WEB-INF/, but here you need to put into WEB-INF/classes.
Example Maven configuration:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<archiveClasses>true</archiveClasses>
<webResources>
<resource>
<directory>${basedir}/src/main/webapp/WEB-INF</directory>
<filtering>true</filtering>
<targetPath>WEB-INF</targetPath>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<targetPath>WEB-INF/classes</targetPath>
</resource>
</webResources>
</configuration>
</plugin>
And your project structure can look like:
Content of jersey-multipart-config.properties with Jersey 2.x:
jersey.config.multipart.bufferThreshold = -1
i've found solution to programmatically avoid to use temporary file creation (very useful for GAE implementation)
My solution consist of creating a new MultiPartReader Provider ... below my code
#Provider
#Consumes("multipart/*")
public class GaeMultiPartReader implements MessageBodyReader<MultiPart> {
final Log logger = org.apache.commons.logging.LogFactory.getLog(getClass());
private final Providers providers;
private final CloseableService closeableService;
private final MIMEConfig mimeConfig;
private String getFixedHeaderValue(Header h) {
String result = h.getValue();
if (h.getName().equals("Content-Disposition") && (result.indexOf("filename=") != -1)) {
try {
result = new String(result.getBytes(), "utf8");
} catch (UnsupportedEncodingException e) {
final String msg = "Can't convert header \"Content-Disposition\" to UTF8 format.";
logger.error(msg,e);
throw new RuntimeException(msg);
}
}
return result;
}
public GaeMultiPartReader(#Context Providers providers, #Context MultiPartConfig config,
#Context CloseableService closeableService) {
this.providers = providers;
if (config == null) {
final String msg = "The MultiPartConfig instance we expected is not present. "
+ "Have you registered the MultiPartConfigProvider class?";
logger.error( msg );
throw new IllegalArgumentException(msg);
}
this.closeableService = closeableService;
mimeConfig = new MIMEConfig();
//mimeConfig.setMemoryThreshold(config.getBufferThreshold());
mimeConfig.setMemoryThreshold(-1L); // GAE FIX
}
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return MultiPart.class.isAssignableFrom(type);
}
#Override
public MultiPart readFrom(Class<MultiPart> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> headers, InputStream stream) throws IOException, WebApplicationException {
try {
MIMEMessage mm = new MIMEMessage(stream, mediaType.getParameters().get("boundary"), mimeConfig);
boolean formData = false;
MultiPart multiPart = null;
if (MediaTypes.typeEquals(mediaType, MediaType.MULTIPART_FORM_DATA_TYPE)) {
multiPart = new FormDataMultiPart();
formData = true;
} else {
multiPart = new MultiPart();
}
multiPart.setProviders(providers);
if (!formData) {
multiPart.setMediaType(mediaType);
}
for (MIMEPart mp : mm.getAttachments()) {
BodyPart bodyPart = null;
if (formData) {
bodyPart = new FormDataBodyPart();
} else {
bodyPart = new BodyPart();
}
bodyPart.setProviders(providers);
for (Header h : mp.getAllHeaders()) {
bodyPart.getHeaders().add(h.getName(), getFixedHeaderValue(h));
}
try {
String contentType = bodyPart.getHeaders().getFirst("Content-Type");
if (contentType != null) {
bodyPart.setMediaType(MediaType.valueOf(contentType));
}
bodyPart.getContentDisposition();
} catch (IllegalArgumentException ex) {
logger.error( "readFrom error", ex );
throw new WebApplicationException(ex, 400);
}
bodyPart.setEntity(new BodyPartEntity(mp));
multiPart.getBodyParts().add(bodyPart);
}
if (closeableService != null) {
closeableService.add(multiPart);
}
return multiPart;
} catch (MIMEParsingException ex) {
logger.error( "readFrom error", ex );
throw new WebApplicationException(ex, 400);
}
}
}
We experienced a similar problem, Jetty wouldn't let us upload files more than 9194 bytes, (all of a sudden - one day), we realised afterwards that someone had taken our user access from /tmp, which corresponds to java.io.tmpdir on some linux versions, so Jetty couldn't store the uploaded file there, and we got a 400 error.

How do I read POST parameters for a RESTful service using Jersey?

I am not using JSON or anything like that. I have a simple form to upload a file and I want to read the parameters of the form. The code below is not working as expected. It will not show any parameters.
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Path("{appNum}/{docId}/file")
public Response uploadDocFile(
#PathParam("appNum") String appNum,
#PathParam("docId") String docId,
#Context HttpServletRequest req)
{
try {
log.info("POST Parameters:");
Enumeration e = req.getParameterNames();
while(e.hasMoreElements())
{
Object key = e.nextElement();
log.info("Key: " + key);
log.info("Val: " + req.getParameter(key.toString()));
}
} catch (Exception e) {
e.printStackTrace();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(new StatusResponse(e)).build();
}
return Response.ok().build();
}
FYI, You need to use #FormParam. Also make sure INPUT HTML types are using name= not id=.
I have the same problem. Using #FormParam annotation for individual parameters works, but reading them from HttpServletRequest injected through #Context doesn't. I also tried to get the request object/parameters through Guice using Provider<HttpServletRequest> and #RequestParameters<Map<String, String[]>>. In both cases there were no post parameters.
However, it is possible to get a map of parameters by adding a MultivaluedMap<String, String> parameter to resource method. Example:
#POST
public void doSomething(MultivaluedMap<String, String> formParams) {
//...
}
If you are using Jersey RESTful API in JAVA you can look for Parameter Annotations (#*Param)
Example:
Dependency:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.8</version>
</dependency>
Code:
package yourpack;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
#Path("/path_to_data")
public class DataResource {
#GET
#Path("/{param}")
public Response getMsg(#PathParam("param") String urlparam) {
int ok = 200;
String result = "Jersey Data resource: " + urlparam;
return Response.status(ok).entity(result ).build();
}
}
List of annotations: #MatrixParam, #HeaderParam, #CookieParam, #FormParam, #QueryParam, #PathParam
At some point of time Jersey ContainerServlet (or other Jersey object during request processing) calls request.getInputStream() or request.getReader() which set 'usingInputStream' or 'usingReader' to TRUE. This state prevents populating of parameters map inside the request object. Something like this:
parseParameters() {
if (usingInputStream || usingReader) {
return;
} else {
parametersMap.putAll({actual parameters parsing from stream})
}
}
Map getParametersMap() {
return parametersMap;
}
Try putting a break point at the very first entry point (beginning of Jersey ServletContainer.service() method) of your application and evaluate request.getParametersMap() call. You'll get your parameters.

Categories

Resources