Unsuccessful content negotation with excel file - java

I have a Spring Boot application with an already working endpoint that produces an xlsx file.
Now, I want to implement content negotation on this endpoint but I always receive 406 Not Acceptable.
{
"timestamp": "2021-03-09T18:44:56.997+0000",
"status": 406,
"error": "Not Acceptable",
"message": "Could not find acceptable representation",
"path": "/students/excel"
}
And I am using URL parameters and I am calling it like that
localhost:8080/students/excel/excel?format=xlsx
The implementation
Endpoint
#PostMapping(path = "/excel", produces = {"application/vnd.ms-excel"})
public byte[] generateExcel(HttpServletResponse response, final #RequestBody #NonNull Criteria criteria) {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=Students.xlsx");
return studentService.generateExcelReport(response, criteria);
}
The method that is finalizing the excel file.
public static byte[] write(HttpServletResponse response, final #NonNull XSSFWorkbook workbook) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
os.writeTo(response.getOutputStream());
workbook.write(os);
workbook.close();
return os.toByteArray();
} catch (final IOException ex) {
throw new RuntimeException("Error generating excel", ex);
}
}
And the relevant method on WebConfiguration that implements WebMvcConfigurer
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("format").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xlsx", MediaType.MULTIPART_FORM_DATA);
}
I tried a lot of combinations with MediaType and on WebConfiguration and the attribute of produces like
application/csv to check if there is a possibility that it could work due to formating of the excel file and others. But I couldn't overcome this status. When setting it to application/json and text/plain it works but it's not the wanted functionality or the correct one.
When I am not using content negotiation, the generation of the excel works as I mentioned.
Edit:
Based on suggestions, I changed the content type to application/vnd.openxmlformats-officedocument.spreadsheetml.sheet and changed the header of both Accept and Content-Type on Postman and still receive 406.
This is how the request on Postman looks
I also debugged the application and it doesn't enter the method of the endpoint, it seems to fail instantly because of the produces value.
Ι want to add that this is a POST request that accepts a JSON. So, using any other content-type on Postman will break it.
Update
It works by using the accept header instead of parameters and changing WebConfigurer method. But, I wanted to to use URL parameters and to understand why they don't work.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(false).
ignoreAcceptHeader(false).
useJaf(false);
}

I found a solution in order to make it work with URL parameters, as it was my first intention.
I added a new Media Type of vnd.ms-excel on WebConfiguration as following.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("format").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xlsx", new MediaType("application","vnd.ms-excel"));
}
On Accept header of the request I added the value application/vnd.ms-excel.
Finally, by calling the excel endpoint now with the needed format it generates the excel file properly.
localhost:8080/students/excel/excel?format=xlsx

Related

Browsers do not automatically display an inline file returned from a back-end java HTTP response

I created a pdf on the fly with pdfbox, did corresponding tests and it is well formatted, I can save it, send it by email - and everything is working as expected.
Now that same pdf(without saving it), I returns it as a client hit a button; the respective request/response succeeds but the browser (any) do not display it.
Some context:
angularJS 1.6 on the front-end
jersey 1.9 as a rest api
HTTP POST method
No errors
It just stays on the current page
My code
final ByteArrayOutputStream pdfStream = (ByteArrayOutputStream) generatePricePDF((Price) services.includeClient(price), null);
StreamingOutput streamingOutput = new StreamingOutput() {
#Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
pdfStream.writeTo(outputStream);
pdfStream.flush();
pdfStream.close();
}
};
return Response.ok(streamingOutput, MediaType.valueOf("application/pdf"))
.header("Content-Length", pdfStream.toString().length())
.header("Content-Disposition", "inline; filename=price").build();
}
As I mentioned it above, I can copy the encoded response:
JVBERi0xLjQKJfbk/N8KMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovVmVyc2lvbiAvMS40Ci9QYWdlcyAyIDAgUgo+PgplbmRvYmoKMiAwIG9iago8PAovVHlwZSAvUGFnZXMKL0tpZHMgWzMgMCBSXQovQ291bnQgMQo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvUGFnZQovTWVkaWFCb3ggWzAuMCAwLjAgNjEyLjAgNzkyLjBdCi9QYXJlbnQgMiAwIFIKL0NvbnRlbnRzIDQgMCBSCi9SZXNvdXJjZXMgNSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL0xlbmd0aCA0MzEKL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtDQp4nO1Xy27bMBC86yv2mByUcvmUiiBA7Ui3nqofIFQ6UKFaqCQDSb++I1J20cfFQG4W4MNydrg7JAcLa9dkH2omltQcMsOCnET4Nbszpryn5luWq9JQbhK4H1qft0PvY0oZQSLi9djRZz+SkMTFRyHwo/2XhqTgYq0Cbs5FZH96OXU92Ds/j2FKpZxcS2nFVjPnLuJVk+2azBgyLqWZTUxok/iP2jjltGUpLCIpnLFVXOllbVHYlk5YZcta2Qq4A8LgIA92mZhWgi0r7MMeRECtrcF8tuyWPEQtVVGJl/1Pi4blREmEY7RFObRZiXZfPaOJiXFl6yivQN6c8yivkuil6CIwIpB43ZH+kvI+50N05ZH+lHHXDxOFPrTzOIRjGw6+H15Gf+imefUOHlBdnvRBiIgau7qAlZQX9LcJ5GaCmzeBvkyCYjPBLZgAo/9fE2yT4LZM8L9JoLb/BJsJ1DYJNhPIbRJsJpA3MwmeznfEV90RS+zQKn3Jd+E4DzSFbqLvXU9tWrcnPyLw9Abk2A704xTmn74PeK9X387D9LB+rzOdrxvSNDqdG/0CJBiXHA0KZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8Ci9Gb250IDYgMCBSCj4+CmVuZG9iago2IDAgb2JqCjw8Ci9GMSA3IDAgUgo+PgplbmRvYmoKNyAwIG9iago8PAovVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL0Jhc2VGb250IC9UaW1lcy1Sb21hbgovRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZwo+PgplbmRvYmoKeHJlZgowIDgKMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDAwMDE1IDAwMDAwIG4NCjAwMDAwMDAwNzggMDAwMDAgbg0KMDAwMDAwMDEzNSAwMDAwMCBuDQowMDAwMDAwMjQ3IDAwMDAwIG4NCjAwMDAwMDA3NTIgMDAwMDAgbg0KMDAwMDAwMDc4NSAwMDAwMCBuDQowMDAwMDAwODE2IDAwMDAwIG4NCnRyYWlsZXIKPDwKL1Jvb3QgMSAwIFIKL0lEIFs8RkI2MUI4MDM5Q0RFQzU0OTkwNDFFMENBMDc0RTNDREU+IDxGQjYxQjgwMzlDREVDNTQ5OTA0MUUwQ0EwNzRFM0NERT5dCi9TaXplIDgKPj4Kc3RhcnQ=
and I can convert it (using an online converted), So there it is my pdf.
I have reviewed Content-Disposition
and it seems that the header are ok.
I was not managing the response accordingly, this is the code added in the AngularJs 1.x Controller:
$http.post('rest/processprice', $scope.price, {responseType: "blob"})
.then(function(response) {
var file = new Blob([response.data], {type: "application/pdf"});
var fileURL = URL.createObjectURL(file);
$window.open(fileURL, '_blank');
});
Managing the response this way, the pdf file opens up automatically.

Mapping incoming JSON to a class in spring boot

I'm struggling to understand why I'm getting the following error when I call my spring boot end point
{
"timestamp": 1489573322678,
"status": 406,
"error": "Not Acceptable",
"exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
"message": "Could not find acceptable representation",
"path": "/quotes"
}
This is the request that I'm sending to the server
POST /quotes HTTP/1.1
Host: localhost:8080
tamid: 5
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 94370a3f-6165-106f-f27f-44a44093e0d5
{
"test": "works"
}
I would like the incoming JSON request body to map to a java class I have defined. Here is the class.
#Embedded
public class QuoteVersion {
private String test;
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
public void validate() {
}
}
I'm using the #Embedded annotation for use with a mongodb mapping library that I'm hoping is unrelated to the issue I'm facing
Here is the controller method
#RequestMapping(
path = "/quotes",
method = RequestMethod.POST,
headers = "Accept=application/json",
produces = "application/json"
)
public #ResponseBody QuoteStatus create (#RequestHeader(value = "tamid") String tamId,
#RequestBody QuoteVersion firstQuoteVersion) {
// final QuoteVersion firstQuoteVersion = this.quoteFactory.createQuoteVersion(incomingQuote);
final User currentUser = User.getFromTamId(tamId);
currentUser.can(Permissions.CREATE_QUOTE);
firstQuoteVersion.validate();
final Quote newQuote = new Quote();
newQuote.addVersion(firstQuoteVersion);
this.dataRepository.save(newQuote);
QuoteStatus qs = new QuoteStatus(newQuote);
return qs;
}
I'm guessing that Spring Boot for some reason does not understand how to map the incoming payload to the class I have defined but I have no idea how to fix the issue. Thanks in advance for any help you may have to offer.
Spring clearly indicates this problem:
HttpMediaTypeNotAcceptableException
This means that in your content-type header you provided the wrong information or made a syntactical mistake. Try putting there something like application/json.
Also
Make sure the other end will accept it. You currently only accepting requests with an accept header with value application/json. I don't think that is what you want.
So either remove that requirement or add this header to the request.

How to serve already gzipped content in JAX-RS?

I'm developing a small JAX-RS application with Resteasy. I wanted the application to serve some static content for Javascript and CSS files, etc. and I would like to take advantage of the already gzipped version of the resources packaged in the jars of webjars.org. Thus, I need to handle the Accept-Encoding header and check if the .gz is there (or not).
So far, what I have is:
#Path("res/{path:.*}")
#GET
public Response webjars(#PathParam("path") String path, #HeaderParam("Accept-Encoding") String acceptEncoding) {
// Guesses MIME type from the path extension elsewhere.
String mime = mimes.getContentType(path);
if (acceptEncoding.contains("gzip")) {
InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path + ".gz");
if (is != null)
return Response.ok().type(mime).encoding("gzip").entity(is).build();
}
InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path);
if (is != null)
return Response.ok().type(mime).entity(is).build();
return Response.status(Status.NOT_FOUND).build();
}
But it doesn't work. The content served is totally broken. So far, I've found that a component that compresses the stream again: org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor because I manually filled the Content-Encoding header (using the ResponseBuilder.encoding method).
This looks like a bug to me because, apparently, there's no way to share an already gzipped stream. However, Is this achievable using JAX-RS? Is this a Resteasy bug?
I can think of a variety of ways to achieve the same thing externally to Resteasy, like mapping the webjars.org servlet (I'm not in a Servlet API 3.0 environment, so I have no META-INF/resources/ automatic classpath mapping). Nevertheless, my questions still prevail. It applies to several other scenarios.
Update:
For the record I have filled the issue RESTEASY-1170.
Here's an example implementation of my above comment.
The point I'm getting at is that if you don't want it to be handle by the current interceptor, don't set the header, create an Interceptor that will be name binded, with your own annotation, and set the priority to one lower than the one you want to avoid, then set the header in your Interceptor...
#AlreadyGzipped
#NameBinding
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface AlreadyGzipped {}
WriterInterceptor. Notice the #Priority. The GZIPEncodingInterceptor uses Priorities.ENTITY_CODER
#Provider
#AlreadyGzipped
#Priority(Priorities.ENTITY_CODER + 1000)
public class AlreadyGzippedWriterInterceptor implements WriterInterceptor {
#Context HttpHeaders headers;
#Override
public void aroundWriteTo(WriterInterceptorContext wic) throws IOException,
WebApplicationException {
String header = headers.getHeaderString("Accept-Encoding");
if (null != header && header.equalsIgnoreCase("gzip")) {
wic.getHeaders().putSingle("Content-Encoding", "gzip");
}
wic.proceed();
}
}
Test resource
#Path("resource")
public class AlreadyGzippedResoure {
#GET
#AlreadyGzipped
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getAlreadGzipped() throws Exception {
InputStream is = getClass().getResourceAsStream("/stackoverflow.png.gz");
return Response.ok(is).build();
}
}
Test
public class Main {
public static void main(String[] args) throws Exception {
Client client = ClientBuilder.newClient();
String url = "http://localhost:8080/api/resource";
Response response = client.target(url).request().acceptEncoding("gzip").get();
Image image = ImageIO.read(response.readEntity(InputStream.class));
JOptionPane.showMessageDialog(null,new JLabel(new ImageIcon(image)));
}
}
Result

spring requestmapping http error 406 on file extension

I have created this REST mapping so that it can accept filenames at the end of the URI ...
#RequestMapping(value="/effectrequest/{name}/{imagename:[a-zA-Z0-9%\\.]*}",
headers="Accept=*/*", method=RequestMethod.GET,
produces = "application/json")
public #ResponseBody EffectRequest effectRequest(
#PathVariable("name") String name,
#PathVariable("imagename") String imageName)
{
return new EffectRequest(2, "result");
}
Which returns JSON content using MappingJackson2HttpMessageConverter. I make a test jQuery AJAX call to this mapping with ...
var effectName = 'Blur';
var imageName = 'Blah.jpg';
var requestUri = '/effectrequest/' + effectName + '/' + imageName;
alert(requestUri);
$(document).ready(function() {
$.ajax({
url: /*[+ [[${hostname}]] + requestUri +]*/
}).then(function(data) {
$('.effect').append(data.id);
$('.image').append(data.content);
});
});
This generates a URI of http://localhost/effectrequest/Blur/Blah.jpg and in a debugging session the filename is received correctly in the effectRequest() method above. However, the client or jQuery AJAX call receives a HTTP 406 error (Not Acceptable) from the server even with the produces = "application/json" in the RequestMapping.
After much debugging later, I have this narrowed down - when I modify the test javascript code to generate a URI of http://localhost/effectrequest/Blur/Blah.json it works. So either Tomcat or MappingJackson2HttpMessageConverter is causing the HTTP 406 error by looking at the filename extension at the end of the URI and deciding that the JSON content I'm sending back is not a good match.
Is there anyway to override this behaviour without having to encode the . (dot) in the filename?
By default, Spring MVC prefers to use the request's path when it's trying to figure out the media type for a response to a request. This is described in the javadoc for ContentNegotiationConfigurer.favorPathExtension():
Indicate whether the extension of the request path should be used to determine the requested media type with the highest priority.
By default this value is set to true in which case a request for /hotels.pdf will be interpreted as a request for "application/pdf" regardless of the Accept header.
In your case this means that the request for /effectrequest/Blur/Blah.jpg is being interpreted as a request for image/jpeg which leaves MappingJackson2HttpMessageConveter trying to write an image/jpeg response which it is unable to do.
You can easily change this configuration using ContentNegotiationConfigurer accessed by extending WebMvcConfigurerAdapter. For example:
#SpringBootApplication
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}

File upload along with other object in Jersey restful web service

I want to create an employee information in the system by uploading an image along with employee data. I am able to do it with different rest calls using jersey. But I want to achieve in one rest call.
I provide below the structure. Please help me how to do in this regard.
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
Employee emp) {
//..... business login
}
Whenever I am trying to do, I get error in Chrome postman. The simple structure of my Employee json is given below.
{
"Name": "John",
"Age": 23,
"Email": "john#gmail.com",
"Adrs": {
"DoorNo": "12-A",
"Street": "Street-11",
"City": "Bangalore",
"Country": "Karnataka"
}
}
However I can do it by making two different call, but I want to achieve in one rest call so that I can receive the file as well as the actual data of the employee.
Request you to help in this regard.
You can't have two Content-Types (well technically that's what we're doing below, but they are separated with each part of the multipart, but the main type is multipart). That's basically what you are expecting with your method. You are expecting mutlipart and json together as the main media type. The Employee data needs to be part of the multipart. So you can add a #FormDataParam("emp") for the Employee.
#FormDataParam("emp") Employee emp) { ...
Here's the class I used for testing
#Path("/multipart")
public class MultipartResource {
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("emp") Employee emp) throws Exception{
Image img = ImageIO.read(fileInputStream);
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
System.out.println(cdh.getName());
System.out.println(emp);
return Response.ok("Cool Tools!").build();
}
}
First I just tested with the client API to make sure it works
#Test
public void testGetIt() throws Exception {
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
WebTarget t = client.target(Main.BASE_URI).path("multipart").path("upload2");
FileDataBodyPart filePart = new FileDataBodyPart("file",
new File("stackoverflow.png"));
// UPDATE: just tested again, and the below code is not needed.
// It's redundant. Using the FileDataBodyPart already sets the
// Content-Disposition information
filePart.setContentDisposition(
FormDataContentDisposition.name("file")
.fileName("stackoverflow.png").build());
String empPartJson
= "{"
+ " \"id\": 1234,"
+ " \"name\": \"Peeskillet\""
+ "}";
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
Response response = t.request().post(
Entity.entity(multipartEntity, multipartEntity.getMediaType()));
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
response.close();
}
I just created a simple Employee class with an id and name field for testing. This works perfectly fine. It shows the image, prints the content disposition, and prints the Employee object.
I'm not too familiar with Postman, so I saved that testing for last :-)
It appears to work fine also, as you can see the response "Cool Tools". But if we look at the printed Employee data, we'll see that it's null. Which is weird because with the client API it worked fine.
If we look at the Preview window, we'll see the problem
There's no Content-Type header for the emp body part. You can see in the client API I explicitly set it
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
So I guess this is really only part of a full answer. Like I said, I am not familiar with Postman So I don't know how to set Content-Types for individual body parts. The image/png for the image was automatically set for me for the image part (I guess it was just determined by the file extension). If you can figure this out, then the problem should be solved. Please, if you find out how to do this, post it as an answer.
See UPDATE below for solution
And just for completeness...
See here for more about MultiPart with Jersey.
Basic configurations:
Dependency:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey2.version}</version>
</dependency>
Client config:
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
Server config:
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.multipart")
.register(MultiPartFeature.class);
If you're having problems with the server configuration, one of the following posts might help
What exactly is the ResourceConfig class in Jersey 2?
152 MULTIPART_FORM_DATA: No injection source found for a parameter of type public javax.ws.rs.core.Response
UPDATE
So as you can see from the Postman client, some clients are unable to set individual parts' Content-Type, this includes the browser, in regards to it's default capabilities when using FormData (js).
We can't expect the client to find away around this, so what we can do, is when receiving the data, explicitly set the Content-Type before deserializing. For example
#POST
#Path("upload2")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(#FormDataParam("emp") FormDataBodyPart jsonPart,
#FormDataParam("file") FormDataBodyPart bodyPart) {
jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Employee emp = jsonPart.getValueAs(Employee.class);
}
It's a little extra work to get the POJO, but it is a better solution than forcing the client to try and find it's own solution.
Another option is to use a String parameter and use whatever JSON library you use to deserialze the String to the POJO (like Jackson ObjectMapper). With the previous option, we just let Jersey handle the deserialization, and it will use the same JSON library it uses for all the other JSON endpoints (which might be preferred).
Asides
There is a conversation in these comments that you may be interested in if you are using a different Connector than the default HttpUrlConnection.
You can access the Image File and data from a form using MULTIPART FORM DATA By using the below code.
#POST
#Path("/UpdateProfile")
#Consumes(value={MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
#Produces(value={MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response updateProfile(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
#FormDataParam("ProfileInfo") String ProfileInfo,
#FormDataParam("registrationId") String registrationId) {
String filePath= "/filepath/"+contentDispositionHeader.getFileName();
OutputStream outputStream = null;
try {
int read = 0;
byte[] bytes = new byte[1024];
outputStream = new FileOutputStream(new File(filePath));
while ((read = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch(Exception ex) {}
}
}
}
When I tried #PaulSamsotha's solution with Jersey client 2.21.1, there was 400 error. It worked when I added following in my client code:
MediaType contentType = MediaType.MULTIPART_FORM_DATA_TYPE;
contentType = Boundary.addBoundary(contentType);
Response response = t.request()
.post(Entity.entity(multipartEntity, contentType));
instead of hardcoded MediaType.MULTIPART_FORM_DATA in POST request call.
The reason this is needed is because when you use a different Connector (like Apache) for the Jersey Client, it is unable to alter outbound headers, which is required to add a boundary to the Content-Type. This limitation is explained in the Jersey Client docs. So if you want to use a different Connector, then you need to manually create the boundary.
Your ApplicationConfig should register the MultiPartFeature.class from the glassfish.jersey.media.. so as to enable file upload
#javax.ws.rs.ApplicationPath(ResourcePath.API_ROOT)
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
//register the necessary headers files needed from client
register(CORSConfigurationFilter.class);
//The jackson feature and provider is used for object serialization
//between client and server objects in to a json
register(JacksonFeature.class);
register(JacksonProvider.class);
//Glassfish multipart file uploader feature
register(MultiPartFeature.class);
//inject and registered all resources class using the package
//not to be tempered with
packages("com.flexisaf.safhrms.client.resources");
register(RESTRequestFilter.class);
}
I used file upload example from,
http://www.mkyong.com/webservices/jax-rs/file-upload-example-in-jersey/
in my resource class i have below method
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response attachupload(#FormDataParam("file") byte[] is,
#FormDataParam("file") FormDataContentDisposition fileDetail,
#FormDataParam("fileName") String flename){
attachService.saveAttachment(flename,is);
}
in my attachService.java i have below method
public void saveAttachment(String flename, byte[] is) {
// TODO Auto-generated method stub
attachmentDao.saveAttachment(flename,is);
}
in Dao i have
attach.setData(is);
attach.setFileName(flename);
in my HBM mapping is like
<property name="data" type="binary" >
<column name="data" />
</property>
This working for all type of files like .PDF,.TXT, .PNG etc.,
The request type is multipart/form-data and what you are sending is essentially form fields that go out as bytes with content boundaries separating different form fields.To send an object representation as form field (string), you can send a serialized form from the client that you can then deserialize on the server.
After all no programming environment object is actually ever traveling on the wire. The programming environment on both side are just doing automatic serialization and deserialization that you can also do. That is the cleanest and programming environment quirks free way to do it.
As an example, here is a javascript client posting to a Jersey example service,
submitFile(){
let data = new FormData();
let account = {
"name": "test account",
"location": "Bangalore"
}
data.append('file', this.file);
data.append("accountKey", "44c85e59-afed-4fb2-884d-b3d85b051c44");
data.append("device", "test001");
data.append("account", JSON.stringify(account));
let url = "http://localhost:9090/sensordb/test/file/multipart/upload";
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
axios.post(url, data, config).then(function(data){
console.log('SUCCESS!!');
console.log(data.data);
}).catch(function(){
console.log('FAILURE!!');
});
},
Here the client is sending a file, 2 form fields (strings) and an account object that has been stringified for transport. here is how the form fields look on the wire,
On the server, you can just deserialize the form fields the way you see fit. To finish this trivial example,
#POST
#Path("/file/multipart/upload")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadMultiPart(#Context ContainerRequestContext requestContext,
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("accountKey") String accountKey,
#FormDataParam("account") String json) {
System.out.println(cdh.getFileName());
System.out.println(cdh.getName());
System.out.println(accountKey);
try {
Account account = Account.deserialize(json);
System.out.println(account.getLocation());
System.out.println(account.getName());
} catch (Exception e) {
e.printStackTrace();
}
return Response.ok().build();
}

Categories