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

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

Related

Standalone SpringBoot app with OAuth2 authentication

I am working on creating an app using springboot which would consume an API which has OAuth2 authentication. Post successful getting the Bearer code I would be calling another API which would actually give me data for further processing. I have custom OAuth url, authorization code, username, password, secret key, api key. When I searched on internet, none of the example were usign all of these[only secret key, authorization code and api key was getting used.]. Do I need to use username and password as well?
I tried below code [and few other things]. But not able to get through this.
<code>
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.support.BasicAuthorizationInterceptor;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Component
public class ApiConsumer {
#Autowired
private RestTemplate template;
#Value("${oauth.api}")
String url;
#Value("${oauth.oAuth.url}")
String oAuthUrl;
#Value("${oauth.user}")
String username;
#Value("${oauth.password}")
String password;
#Value("${oauth.apikey}")
String apiKey;
#Value("${oauth.secretkey}")
String apiSecret;
public String postData() {
log.info("Call API");
try {
String response = consumeApi();
if (response.equals("200")) {
log.info("posting data to another api");
// CALL another API HERE for actual data with bearer code
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private String consumeApi() throws Exception {
String authorizationHeader = "Basic "
+ DatatypeConverter.printBase64Binary((apiKey + ":" + apiSecret).getBytes());
// setting up the HTTP Basic Authentication header value
HttpHeaders requestHeaders = new HttpHeaders();
// set up HTTP Basic Authentication Header
requestHeaders.add("Authorization", authorizationHeader);
requestHeaders.add("Accept", MediaType.APPLICATION_FORM_URLENCODED_VALUE);
requestHeaders.add("response_type", "code");
// request entity is created with request headers
HttpEntity<String> request = new HttpEntity<String>(requestHeaders);
template.getInterceptors().add(new BasicAuthorizationInterceptor(username, password));
ResponseEntity<String> result = null;
try {
result = template.exchange(oAuthUrl, HttpMethod.POST, request, String.class);
log.info( result.getBody());
if (result.getStatusCode() == HttpStatus.OK) {
transformData(result.getBody());
}
if (result.getStatusCode() != HttpStatus.REQUEST_TIMEOUT) {
throw new Exception("Api taking too long to respond! ");
}
}
catch (Exception e) {
log.error("Api taking too long to respond!");
}
return "";
}
private void transformData(String body) throws JsonMappingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
List<HeapEntity> heapEntityList = Arrays.asList(mapper.readValue(body, HeapEntity[].class));
if (heapEntityList != null && heapEntityList.size() > 0) {
heapEntityList.forEach(i -> i.getPhoneNumber().replaceAll("-", ""));
}
log.debug("Size of list is :: " + heapEntityList.size());
heapEntityList.add(null);
}
}
</code>
Unfortunately, I cannot give a direct answer to your question, because it is not clear from it which grant type you are trying to use, and this will determine the answer to the question whether you need to use a username and password or not.
I advise you to familiarize yourself with the Section 4 of RFC 6749, in which you will find information on all grant types supported by the standard, and the request parameters they require.
Examples for the Password grant type:
If you need to use the RestTemplate, you can do something like this:
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/x-www-form-urlencoded");
headers.set("Authorization", "Basic " + Base64.getUrlEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()));
String body = String.format("grant_type=password&username=%s&password=%s", username, password);
String json = restTemplate.postForObject(tokenUrl, new HttpEntity<>(body, headers), String.class);
Note that the response is a json object containing a token, not the token itself.
Or you can simply use the more appropriate for your purpose OAuth2RestTemplate:
#Bean
public OAuth2RestTemplate oAuth2RestTemplate() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setClientAuthenticationScheme(AuthenticationScheme.form);
resource.setAccessTokenUri("tokenUrl");
resource.setClientId("clientId");
resource.setClientSecret("clientSecret");
resource.setUsername("username");
resource.setPassword("password");
return new OAuth2RestTemplate(resource);
}
Do not forget to add #EnableOAuth2Client to one of your configuration classes.

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.

AJAX HttpRequest doesn't recieve updated data from servlet

Servlet:
package world.hello;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import world.hello.MyMainClass;
public class TestServlet extends HttpServlet{
private static final int BYTES_DOWNLOAD = 1024;
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/plain");
OutputStream os = response.getOutputStream();
os.write(("hello world"+Double.toString(Math.random())).getBytes());
os.flush();
os.close();
}
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
doGet(request, response);
}
}
HTML:
<html>
<body>
<script>
function myAjax()
{
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("GET","RunQuery", false);
xmlhttp.send();
document.getElementById("myText").innerHTML=xmlhttp.responseText + " " + xmlhttp.readyState.toString() + " " + xmlhttp.status.toString() ;
document.getElementById("myText2").innerHTML=Math.random();
}
</script>
<button id = "myButton" onclick = "myAjax()">click me</button>
<div id = "myText"></div>
<div id = "myText2"></div>
</body>
</html>
If I access the servlet directly at http://localhost:9070/test_web_project_1/RunQuery
Each time I refresh it, I get a different random float displayed.
When I run the HTML at http://localhost:9070/test_web_project_1/myxjax.html, The second float changes, the first is fixed.
What is causing this, and how do i resolve it?
Nevermind what I said before...your code is synchronous because you set async to false. Your issue is just browser caching. Your ajax request is being cached. You can trick the browser to not load the cache by adding a parameter with the date/time to the request like:
var d = new Date();
xmlhttp.open("GET","RunQuery?ts="+d.getTime(), false);
That just makes the browser see each request as unique; there's no need to do anything with that param on the server side.
Or, you could add no-cache headers in the servlet being called by the Ajax. You can also do both to be extra cautious.
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma","no-cache"); //HTTP 1.0
response.setDateHeader ("Expires", 0);

Struts 2 discard cache header

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.

Resources