Struts 2 discard cache header - caching

I have strange discarding behavior of struts2 while setting cache option for my image.
I'm trying to put image from db to be cached on client side
To render image I use ( http://struts.apache.org/2.x/docs/how-can-we-display-dynamic-or-static-images-that-can-be-provided-as-an-array-of-bytes.html ) where special result type render as follow:
public void execute(ActionInvocation invocation) throws Exception {
...//some preparation
HttpServletResponse response = ServletActionContext.getResponse();
HttpServletRequest request = ServletActionContext.getRequest();
ServletOutputStream os = response.getOutputStream();
try
{
byte[] imageBytes = action.getImage();
response.setContentType("image/gif");
response.setContentLength(imageBytes.length);
//I want cache up to 10 min
Date future = new Date(((new Date()).getTime() + 1000 * 10*60l));
;
response.addDateHeader("Expires", future.getTime());
response.setHeader("Cache-Control", "max-age=" + 10*60 + "");
response.addHeader("cache-Control", "public");
response.setHeader("ETag", request.getRequestURI());
os.write(imageBytes);
}
catch(Exception e)
{
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
os.flush();
os.close();
}
But when image is embedded to page it is always reloaded (Firebug shows code 200), and neither Expires, nor max-age are presented in header
Host localhost:9090
Accept image/png,image/*;q=0.8,*/*;q=0.5
Accept-Language en-us,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
Connection keep-alive
Referer http://localhost:9090/web/result?matchId=1
Cookie JSESSIONID=4156BEED69CAB0B84D950932AB9EA1AC;
If-None-Match /web/_srv/teamcolor
Cache-Control max-age=0
I have no idea why it is dissapered, may be problem in url? It is forms with parameter:
http://localhost:9090/web/_srv/teamcolor?loginId=3

At last I've discovered what wrong with my code, it is rather strange because it is partially works (image is displayed).
The culprit is following line:
HttpServletResponse response = ServletActionContext.getResponse();
It must be replaced with following:
HttpServletResponse response = (HttpServletResponse)
invocation.getInvocationContext().get(StrutsStatics.HTTP_RESPONSE);
It is looks like kind of magic, but obviously both response shares the same output stream but not the container of header declarations.

Not sure if this would work any better, but you could try. Create a custom interceptor that modifies the response headers. Something like this (note, I haven't tested this):
package com.yourpackage.interceptor;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.StrutsStatics;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
public class ResponseHeadersInterceptor extends AbstractInterceptor {
#Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext context = invocation.getInvocationContext();
HttpServletResponse response = (HttpServletResponse)context.get(StrutsStatics.HTTP_RESPONSE);
HttpServletRequest request = (HttpServletRequest)context.get(StrutsStatics.HTTP_REQUEST);
if (response!=null) {
Date future = new Date(((new Date()).getTime() + 1000 * 10*60l));
response.addDateHeader("Expires", future.getTime());
response.setHeader("Cache-Control", "max-age=" + 10*60 + "");
response.addHeader("cache-Control", "public");
if (request!=null)
response.setHeader("ETag", request.getRequestURI());
}
return invocation.invoke();
}
}
Then in your struts.xml, define the interceptor and a new interceptor stack:
<interceptors>
<interceptor name="responseHeaders" class="com.yourpackage.interceptor.ResponseHeadersInterceptor" />
<interceptor-stack name="extendedStack">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="responseHeaders" />
</interceptor-stack>
</interceptors>
Then modify your action definition to use the extendedStack.

Related

Add Basic Authorization HTTP Headers to SOAP Request with Spring-WS

I have been trying to consume a SOAP service using a Spring application, but I've been absolutely stuck for a while. I expect the solution will be very simple and I'm hoping to be pointed in the right direction.
I am able to successfully make requests via SoapUI. I simply loaded the .WSDL file, selected the method to use, and added the username and password with basic authorization from the 'Auth' menu in SoapUI.
In the Spring application, I've gotten to the point of getting a 401 error, so I believe it's almost there. I referenced these two nice examples below to first add the username and password, and secondly to log the HTTP headers to verify that they are populated correctly.
https://codenotfound.com/spring-ws-log-client-server-http-headers.html
Setting a custom HTTP header dynamically with Spring-WS client
However, neither setting the 'usernamePasswordCredentials', nor setting the connection's request header seems to have any effect. I've also confirmed that the XML body is correct by testing the logged output in SoapUI. So I believe it's just an authorization issue.
Bean/Configuration Class:
#Bean
public Jaxb2Marshaller marshaller() {
System.out.println("BEAN CREATED: MARSHALLER...");
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("..."); // omitted for example
return marshaller;
}
#Bean
public UsernamePasswordCredentials usernamePasswordCredentials() {
return new UsernamePasswordCredentials("testu", "test");
}
#Bean
#DependsOn({"usernamePasswordCredentials"})
public HttpComponentsMessageSender httpComponentsMessageSender(UsernamePasswordCredentials usernamePasswordCredentials) {
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setCredentials(usernamePasswordCredentials);
return httpComponentsMessageSender;
}
#Bean
#DependsOn({"marshaller"})
public TicketDetailsClient ticketDetailsClient(Jaxb2Marshaller marshaller) {
System.out.println("BEAN CREATED: TICKETDETAILSCLIENT...");
TicketDetailsClient ticketDetailsClient = new TicketDetailsClient();
ticketDetailsClient.setDefaultUri("..."); // omitted for example
ticketDetailsClient.setMarshaller(marshaller);
ticketDetailsClient.setUnmarshaller(marshaller);
return ticketDetailsClient;
}
Bean Method:
public GetTicketDetailsResponse getTicketDetails(long definitionId, long itemId) {
ObjectFactory of = new ObjectFactory();
this.template.setInterceptors(new ClientInterceptor[] {new LogHttpHeaderClientInterceptor()});
GetItemDetailsRequest itemDetailsRequest = new GetItemDetailsRequest();
itemDetailsRequest.setItemDefinitionId(definitionId);
itemDetailsRequest.setItemId(itemId);
GetTicketDetails getTicketDetails = new GetTicketDetails();
getTicketDetails.setGetItemDetailsRequest(itemDetailsRequest);
JAXBElement<GetTicketDetails> elGetTicketDetails = of.createGetTicketDetails(getTicketDetails);
System.out.println("ABOUT TO MARSHALSENDANDRECEIVE...");
GetTicketDetailsResponse ticketDetailsResponse = (GetTicketDetailsResponse) this.template.marshalSendAndReceive(elGetTicketDetails);
return ticketDetailsResponse;
}
Interceptor:
#Override
public boolean handleRequest(MessageContext arg0) throws WebServiceClientException {
// TODO Auto-generated method stub
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection =(HttpComponentsConnection) context.getConnection();
try {
connection.addRequestHeader("username", "testu");
connection.addRequestHeader("password", "test");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
HttpLoggingUtils.logMessage("Client Request Message", arg0.getRequest());
return true;
}
Result Snippet (This is where I expected to see the username/password headers. Since they are missing, I'm guessing this is the issue):
ABOUT TO MARSHALSENDANDRECEIVE...
2021-08-09 13:46:18.891 INFO 23112 --- [ main] com.fp.fpcustomization.HttpLoggingUtils :
----------------------------
Client Request Message
----------------------------
Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
SOAPAction: ""
Content-Type: text/xml; charset=utf-8
Content-Length: 378
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns3:getTicketDetails xmlns:ns3="http://externalapi.business.footprints.numarasoftware.com/"><getItemDetailsRequest><_itemDefinitionId>76894</_itemDefinitionId><_itemId>30201</_itemId></getItemDetailsRequest></ns3:getTicketDetails></SOAP-ENV:Body></SOAP-ENV:Envelope>
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.151 s <<< FAILURE! - in com.fp.fpcustomization.FpcustomizationApplicationTests
[ERROR] RequestTest Time elapsed: 0.51 s <<< ERROR!
org.springframework.ws.client.WebServiceTransportException: [401]
at com.fp.fpcustomization.FpcustomizationApplicationTests.RequestTest(FpcustomizationApplicationTests.java:29)
Following up with the solution that worked for me. This time around, I looked at going about this via the callback function. This led me to the question asked here:
Add SoapHeader to org.springframework.ws.WebServiceMessage
The second answer by Pranav Kumar is what worked for me. So I simply added the callback function to the 'marshalSendAndReceive' function call in the 'GetTicketDetailsResponse getTicketDetails(long definitionId, long itemId)' method. This way, I was able to add the Authorization header that got me past the 401 error that I was getting before.
Object theResponseObject = this.template.marshalSendAndReceive((elGetTicketDetails), new WebServiceMessageCallback(){
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
MimeHeaders mimeHeader = soapMessage.getSaajMessage().getMimeHeaders();
mimeHeader.setHeader("Authorization", "Basic ...==");
}
});
Following this addition, the Authorization header was showing up in the request and the response was successfully returned.

Redirect almost all requests to index.html

I'm creating a web application for which I'm using Vue for the frontend and Spring Boot for the backend. Spring Boot serves index.html at / and /index.html, but I want it to be served at other URL's too, for example /account, which in turn will be detected by Vue's Router and will show the proper page.
Additionally, I have some other URL's I don't want to serve index.html. All of them start with /api, meaning that's the place where the Vue app sends requests.
Any help will be appreciated. Thanks.
What you want to do is called an SPA (single page application). In order to achive this you need to do two things:
Tell vue-router to use HTML5 history push: https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations
Tell SpringBoot to serve the index.html when it cannot find a relevant route. Here is a good guide on how to do it using a handler for NoHandlerFoundException: https://medium.com/#kshep92/single-page-applications-with-spring-boot-b64d8d37015d
I have to warn you: when you configure history mode in step 1., click something, it will look like your SPA is already working (no # sign). Beware that this is an illusion. Vue-router tells the browser how the url should look like, but when you refresh the page, the server will return 404. You have to configure step 2 as well.
Because in my application I do not have only VUE in the user interface, redirect all errors to the VUE index.html as is proposed before is not acceptable in my scenario.
Finally, I have solved in another manner using filters ( basically the idea is to intercept all URL that are not css, js, images, etc... used in my VUE UI and take control of the response). In my case the VUE URL starts with "/context/kmobile/":
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
#Component
public class Html5PathFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(Html5PathFilter.class);
// Capture the content of a file from /webapps/kmobile/index.html
// Inspired by https://stackoverflow.com/questions/30431025/spring-how-to-access-contents-of-webapp-resources-in-service-layer
#Value("/kmobile/index.html")
private Resource indexResource;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String path = request.getServletPath();
if (!path.endsWith(".css") && !path.endsWith(".js") && !path.endsWith(".ico") && !path.endsWith(".html") &&
!path.endsWith("/kmobile/")) {
// log.info("YES, do redirect ->" + path);
// Code warning, initially were using redirect, that's a bad practice because from browser get the index.html url what never should be used directly there
// response.sendRedirect(request.getContextPath() + "/kmobile/index.html");
// Disable browser cache
response.setHeader("Expires", "Sat, 6 May 1971 12:00:00 GMT");
response.setHeader("Cache-Control", "must-revalidate");
response.addHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Pragma", "no-cache");
InputStream is = indexResource.getInputStream();
// Set MIME type
response.setContentType("text/html");
// Content leght
response.setContentLength(is.available());
try (ServletOutputStream out = response.getOutputStream()) {
IOUtils.copy(is, out);
out.flush();
}
return;
} else {
// log.info("NO, do redirect ->" + path);
}
} catch (Exception e) {
log.debug("Error: {}", e.getMessage(), e);
}
//log.info("--> {}", request.getServletPath());
filterChain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getServletPath();
boolean valid = path.startsWith("/kmobile");
if (valid) {
log.info("path: {} => {}", path, valid);
}
return !valid;
}
}

two-legged oauth2 : how to call google drive rest API without specific API library

I have created an app in the Google Developer's Console, then created OAuth2 credentials. I have a client_id and client_secret. Now, I want to use these to obtain an access token for two-legged calls into the Google Drive API. I am using Google's oauth2 client in java:
import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.TokenResponse;
...
public void oauth2Test() {
String clientId = "...";
String clientSecret = "...";
ClientCredentialsTokenRequest request = new ClientCredentialsTokenRequest(
new NetHttpTransport(),
new JacksonFactory(),
new GenericUrl("https://accounts.google.com/o/oauth2/auth"));
request.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret));
TokenResponse response;
try {
response = request.execute();
} catch (Exception ex) {
ex.printStackTrace();
}
}
However, I get a "400 Bad Request" with message
"Required parameter is missing: response_type".
What is the correct way to obtain an access token in the two-legged request model? Note: I only have the client_id and client_secret, I do not have the full API token.
EDIT: My original question was imprecise. While I prefer to start only with client_id and client_secret, that is not necessary. It is OK to use google-specific APIs to obtain access tokens and it is OK to use GoogleCredential. What is necessary is that I am able to use whatever access token(s) are obtained from the authorization process in a generic REST call. In other words, given google app credentials, which can be {client_id,client_secret}, or a google service account key in either JSON or P12 format, how do I obtain access token(s) and how are they used in the REST API call -- do I set the Authorization header or something else?
The first answer points out that client_credential isn't supported, which I've verified. But I still need a path to get the bearer token, so that I can use it in REST calls without specific Google client API libraries. So I started with code that works, but uses the Google libraries. It requires a JSON credential file.
InputStream is = getClass().getResourceAsStream("JSONCredFile");
GoogleCredential credential = GoogleCredential.fromStream(is).createScoped(scopes);
Drive service = new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName("My app")
.build();
FileList result = service.files().list().setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
By hooking up an SSLSocket proxy to the credential (details omitted), I was able to trace the outbound communication:
POST /token HTTP/1.1
Accept-Encoding: gzip
User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: oauth2.googleapis.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 771
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=<lots of encoded stuff>
The reply is a gzip-encoded bearer token, which is used in the API call:
GET /drive/v3/files?fields=nextPageToken,%20files(id,%20name)&pageSize=10 HTTP/1.1
Accept-Encoding: gzip
Authorization: Bearer ya29.c.Eln_BSgrx0afa85mdMstW5jzEvM5dotWpctSXl-DE1jeO2mmu1h0FErr_EZO05YnC-B1yz30IBwOyFXoWr_wwKxlZk08R6eZldNU-EAfrQ1yNftymn_Qqc_pfg
Clearly this is the JWT profile of oauth2. But now what? Somehow I need to get the bearer token without actually making the API call through the specific library. The Google OAuth2 libraries don't seem to support this request type, at least I don't see a "JWT" flavor of TokenRequest. I can cook up the OAuth2 call directly, or create a subclass of TokenRequest that supports JWT?
Any better ideas?
Google doesn't support grant_type=client_credentials which is how you'd do 2LO with an OAuth client ID and secret.
You can use service accounts to do 2LO: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
OK, I finally figured out how to make the JWT, send the OAuth2 request, and extract the access token. For easier integration with the google OAuth2 client, I subclassed TokenRequest:
import com.google.api.client.auth.oauth2.TokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collection;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
/**
* #author jlilley
*/
public class JWTTokenRequest extends TokenRequest {
private String serviceKeyJson;
private boolean doRsa = true;
public JWTTokenRequest(HttpTransport transport, JsonFactory jsonFactory, GenericUrl tokenServerUrl) {
super(transport, jsonFactory, tokenServerUrl, "urn:ietf:params:oauth:grant-type:jwt-bearer");
}
#Override
public JWTTokenRequest setTokenServerUrl(GenericUrl tokenServerUrl) {
return (JWTTokenRequest)super.setTokenServerUrl(tokenServerUrl);
}
public JWTTokenRequest setServiceKey(String json) throws Exception {
this.serviceKeyJson = json;
return this;
}
public JWTTokenRequest setServiceKey(InputStream is) throws Exception {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(is))) {
return setServiceKey(buffer.lines().collect(Collectors.joining("\n")));
}
}
#Override
public JWTTokenRequest setScopes(Collection<String> scopes) {
return (JWTTokenRequest) super.setScopes(scopes);
}
#Override
public JWTTokenRequest set(String fieldName, Object value) {
return (JWTTokenRequest) super.set(fieldName, value);
}
/**
* Create a JWT for the given project id, signed with the given RSA key.
*/
private String signJwtRsa(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Create a JWT for the given project id, signed with the given ES key.
*/
private String signJwtEs(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
return jwtBuilder.signWith(SignatureAlgorithm.ES256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Finalize the JWT and set it in the assertion property of the web service call
* #throws java.io.IOException
*/
void makeAssertion() {
JsonParser parser = new JsonParser();
JsonObject jsonDoc = (JsonObject) parser.parse(serviceKeyJson);
String pkStr = jsonDoc.get("private_key").getAsString()
.replace("\n", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
byte[] pkBytes = Base64.getDecoder().decode(pkStr);
DateTime now = new DateTime();
JwtBuilder jwtBuilder = Jwts.builder()
.setIssuedAt(now.toDate())
.setExpiration(now.plusMinutes(60).toDate())
.setAudience(getTokenServerUrl().toString())
.setIssuer(jsonDoc.get("client_email").getAsString());
if (getScopes() != null) {
jwtBuilder = jwtBuilder.claim("scope", getScopes());
}
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkBytes);
String pkId = jsonDoc.get("private_key_id").getAsString();
jwtBuilder.setHeaderParam("kid", pkId);
jwtBuilder.setHeaderParam("typ", "JWT");
set("assertion", doRsa ? signJwtRsa(jwtBuilder, spec) : signJwtEs(jwtBuilder, spec));
}
/**
* Finalize the JWT, set it in the assertion property of the web service call, and make the token request.
*/
#Override
public TokenResponse execute() throws IOException {
makeAssertion();
return super.execute();
}
}
Give this, I can set up the token request from the service account JSON key file, execute() it, and fetch the resulting access token. Note that token renewal responsibility is up to the caller.

java.lang.NullPointerException at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.unquoteMediaTypeParameters

I have a rest class which upload an image to a folder. By the time I try to test it using postman, my rest class does not fire.
Here is my rest class -
package uploadRest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;
#Path("/file")
public class UploadFileService {
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(
#FormDataParam("file") InputStream uploadedInputStream,
#FormDataParam("file") FormDataContentDisposition fileDetail) {
try {
System.out.println("Hi ");
String uploadedFileLocation = "d://uploaded/" + fileDetail.getFileName();
// save it
writeToFile(uploadedInputStream, uploadedFileLocation);
String output = "File uploaded to : " + uploadedFileLocation;
return Response.status(200).entity(output).build();
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
// save uploaded file to new location
private void writeToFile(InputStream uploadedInputStream,
String uploadedFileLocation) {
try {
OutputStream out = new FileOutputStream(new File(
uploadedFileLocation));
int read = 0;
byte[] bytes = new byte[1024];
out = new FileOutputStream(new File(uploadedFileLocation));
while ((read = uploadedInputStream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I try to test it via postman but it doesn't print "Hi" which is in System.out.println()
Here is what is printed in console: -
cache-control: no-cache
Postman-Token: 35942cde-5bcb-487d-896e-b772e9430e55
Content-Type: multipart/form-data
User-Agent: PostmanRuntime/6.1.6
Accept: */*
accept-encoding: gzip, deflate
Connection: keep-alive
Transfer-Encoding: chunked
]] Root cause of ServletException.
java.lang.NullPointerException
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.unquoteMediaTypeParameters(MultiPartReaderClientSide.java:245)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readMultiPart(MultiPartReaderClientSide.java:172)
at com.sun.jersey.multipart.impl.MultiPartReaderServerSide.readMultiPart(MultiPartReaderServerSide.java:80)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:158)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:85)
Truncated. see log file for complete stacktrace
>
This is how I am sending request using postman -
problem was solved by sending data as form-data instead of binary. I changed the method of sending data in postman body from binary to form-data and the problem was solved.
Thanks #Vikas Sachdeva because of sharing helpful link :)
Tool for sending multipart/form-data request

Consuming MULTIPART_FORM_DATA and application/x-www-form-urlencoded Media Types in a method of jersey servlet

I have a method in jersey servlet which consumes both MULTIPART_FORM_DATA and application/x-www-form-urlencoded Media Types, In my request I am sending some parameters along with a file in the file input stream.
Here is my method
#POST
#Path("/upload")
#Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_FORM_URLENCODED})
#Produces(MediaType.TEXT_PLAIN)
public String uploadFile(MultivaluedMap<String,String> requestParamsPost,#FormDataParam("file") InputStream fis,
#FormDataParam("file") FormDataContentDisposition fdcd){
//some code goes here
}
But my problem is when I start my server after making the mapping of the servlet in web.xml, I get some severe exceptions
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 0
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 1
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 2
Is it somehow possible to consume two Media Types in one method at single endpoint?
Sending a file Parameter is necessary in every request?
The reason for the error is the MultivaluedMap parameter. Jersey doesn't know what to do with it. You can only have one entity type per method. In your method you are trying to accept two different body types in the request. You can't do that. I don't even know how you plan on sending that from the client.
The application/x-www-form-urlencoded data needs to be part of the multipart body. So you can do
#Consumes({MediaType.MULTIPART_FORM_DATA})
public String uploadFile(#FormDataParam("form-data") MultivaluedMap<String,String> form,
#FormDataParam("file") InputStream fis,
#FormDataParam("file") FormDataContentDisposition fdcd){
That would work. The only thing is, you need to make sure the client set the Content-Type of the form-data part to application/x-www-form-urlencoded. If they don't, then the default Content-Type for that part will be text/plain and Jersey will not be able to parse it to a MultivaluedMap.
What you can do instead is just use FormDataBodyPart as a method parameter, then explicitly set the media type. Then you can extract it to a MultivaluedMap. This way the client doesn't need to be expected to set the Content-Type for that part. Some clients don't even allow for setting individual part types.
Here's an example using Jersey Test Framework
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
public class MultipartTest extends JerseyTest {
#Path("test")
public static class MultiPartResource {
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response post(#FormDataParam("form-data") FormDataBodyPart bodyPart,
#FormDataParam("data") String data) {
bodyPart.setMediaType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
MultivaluedMap<String, String> formData = bodyPart.getEntityAs(MultivaluedMap.class);
StringBuilder sb = new StringBuilder();
sb.append(data).append(";").append(formData.getFirst("key"));
return Response.ok(sb.toString()).build();
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(MultiPartResource.class)
.register(MultiPartFeature.class)
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
#Override
public void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
}
#Test
public void doit() {
FormDataMultiPart multiPart = new FormDataMultiPart();
multiPart.field("data", "hello");
multiPart.field("form-data", "key=world");
final Response response = target("test")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertEquals("hello;world", response.readEntity(String.class));
}
}
If you look at the logging, you will see the request as
--Boundary_1_323823279_1458137333706
Content-Type: text/plain
Content-Disposition: form-data; name="data"
hello
--Boundary_1_323823279_1458137333706
Content-Type: text/plain
Content-Disposition: form-data; name="form-data"
key=world
--Boundary_1_323823279_1458137333706--
You can see the Content-Type for the form-data body part is text/plain, which is the default, but in the server side, we explicitly set it before Jersey parses it
public Response post(#FormDataParam("form-data") FormDataBodyPart bodyPart,
#FormDataParam("data") String data) {
bodyPart.setMediaType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
MultivaluedMap<String, String> formData = bodyPart.getEntityAs(MultivaluedMap.class);

Resources