I have a situation where one of the response headers Content-Disposition has to be removed. So I thought of writing a servlet filter to do this. But I realized that the HttpServletResponse has only a setHeader() method but no method to remove it.
How can I do this?
You can't delete headers afterwards by the standard Servlet API. Your best bet is to just prevent the header from being set. You can do this by creating a Filter which replaces the ServletResponse with a custom HttpServletResponseWrapper implementation which skips the setHeader()'s job whenever the header name is Content-Disposition.
Basically:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
public void setHeader(String name, String value) {
if (!name.equalsIgnoreCase("Content-Disposition")) {
super.setHeader(name, value);
}
}
});
}
Just map that filter on the URL-pattern of interest to get it to run.
This may not be Servlet API compliant, but setting the value to null works on GlassFish 4 and probably on Tomcat too as that is what is underneath GlassFish.
We really need to update the Servlet API specification to either add a method to allow removing headers or to officially support using setHeader with a null value.
An example where this is important is if you use a security constraint (SSL/TLS) on your web application then static resource caching is complicated by the fact that the container will automatically add headers to prevent caching (you can try to disable with disableProxyCaching and securePagesWithPragma on Tomcat/GlassFish). I've already got a servlet filter for cache control that works great for non-secure content so I would like to keep cache control all in one place and simply set Prama and Cache-Control to null to clear any container added headers.
As the other responses. There is no way to remove a header after being set, at least not standard (glassfish lets clear a header setting it's value to null). So at the end of the day you would have two choices:
Reset the response with response.reset() - This effectively removes ALL headers AND ALSO ANY BUFFERED DATA, depending on you case can be a good alternative (in my case was after authentication validation errors). If the response is already committed you'll get an IllegalStateException.
Set header to empty string, clearly this doesn't remove the header. But the http specification only has some definitions for and empty value in the Accept-Encoding, TE (transfer encoding) and HOST headers, so depending in your needs you can control that in your application layer.
This does not work for me using Spring 4. I'm trying to strip out the Expires response header. For every page. Like so:
public class CachingFilter implements Filter {
private final Log logger = LogFactory.getLog(getClass());
public CachingFilter() {}
public void destroy() {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.debug("doFilter()");
chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
public void setHeader(String name, String value) {
logger.debug("setHeader(" + name + ","+value+")");
if (!name.equalsIgnoreCase("Expires")) {
super.setHeader(name, value);
}
}
});
}
public void init(FilterConfig fConfig) throws ServletException {}
}
And here is how I add the filter:
public class AppConfig implements WebApplicationInitializer {
private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
private static final String DISPATCHER_SERVLET_MAPPING = "/";
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(AppContext.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
FilterRegistration.Dynamic noCache = servletContext.addFilter("noCacheFilter", new CachingFilter());
noCache.addMappingForUrlPatterns(dispatcherTypes, true, "/*");
servletContext.addListener(new ContextLoaderListener(rootContext));
}
}
setHeader() being called for Expires and Cache-Control, but I can't override the Expires filter value, or the Cache-Control value. I can add to the Cache-Control value. It turns into an array of values if I call setHeader on Cache-Control. But I need to delete the header.
Related
The context
I am currently working on an educational project. This implies two Spring Boot REST servers. One is an actual server, which does some processing.
The one I'm interested in is the other. It is a proxy which will redirect all calls to the first one. So that when I call http://localhost:8080/foo, my proxy server will in turn call http://localhost:8090/foo. And if the first server returns A, the proxy will return {"proxied": A, "someInformationAboutThisCall": B}.
I managed to get to this point with some probably inelegant but functioning code of which I give an excerpt below. The key here is that I use #RequestMapping("**") to achieve this. The next step is to design an interface that will make my additional information immediately legible, which is basically the point of this project. If I remove all #RequestMapping("**"), it works just fine.
The question
Now my problem is the following: having used #RequestMapping("**"), I cannot serve static content (the calls get redirect to the other REST server, which does not serve static content). How could I configure Spring Boot/Spring MVC to ignore resources available as static content when mapping the requests, or make the PathResourceResolver prioritary over my controller?` Or should I serve my static content from yet another JVM/server?
Thanks in advance for your help!
Edit of interest: while doing some tests, I discovered that the static content is served, with some restrictions, if I use #RequestMapping("*").
/index.html generates an error page (as does more generally any static content directly in public)
/itf/index.html works (as does more generally any file in public/itf or any other subdirectory of public)
/itf does not work: Spring Boot seems unaware of an index file in it. I must specify a full URI, down to the specific file I want to display.
This however does not work at all with #RequestMapping("**"), which I need.
The tentatives
I tried using a WebMvcConfigurerAdapter with an HandlerInterceptorAdapter (found on SO, SO again and many other places on the Internet), but could not start my project anymore because Spring boot then does not find the InterceptorRegistry bean (has there been recent changes in Spring Boot? I'm using the version 1.5.3.RELEASE).
I also tried some anti-matching but not only does it not work, it also feels very very dirty (and this whole project is probably not optimal, so that's saying a lot).
The code samples for the curious
My "proxy" controller
Note: you can suggest better ways to realize this in comments. Please keep in mind that, though I'm always open to enhancement suggestions, this was not my question.
#RestController
public class ProxyController {
#Value("${monitored.url.base}") // "http://localhost:8090"
private String redirectBase;
#RequestMapping(value = "**", method = {RequestMethod.POST, RequestMethod.PUT})
public ProxiedResponse proxifyRequestsWithBody(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
return proxifyRequest(request, headers, body);
}
#RequestMapping(value = "**")
public ProxiedResponse proxifyRequestsWithoutBody(HttpServletRequest request, #RequestHeader HttpHeaders headers) throws URISyntaxException {
return proxifyRequest(request, headers, null);
}
private ProxiedResponse proxifyRequest(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
final RequestEntity<Object> requestEntity = convertToRequestEntity(request, headers, body);
// call remote service
final ResponseEntity<Object> proxied = restTemplate.exchange(requestEntity, Object.class);
// Return service result + monitoring information
final ProxiedResponse response = new ProxiedResponse();
response.setProxied(proxied.getBody());
// set additional information
return response;
}
// Won't work properly for POST yet
private <T> RequestEntity<T> convertToRequestEntity(HttpServletRequest request, HttpHeaders headers, T body) throws URISyntaxException {
// Build proxied URL
final StringBuilder redirectUrl = new StringBuilder(redirectBase).append(request.getRequestURI());
final String queryString = request.getQueryString();
if (queryString != null) {
redirectUrl.append("?").append(queryString);
}
// TODO enhancement: transmit headers and request body to make this a real proxy
final HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod());
return new RequestEntity<>(body, headers, httpMethod, new URI(redirectUrl.toString()));
}
}
My dirty attempt at excluding static resources URLs
#Configuration // adding #EnableWebMvc did not solve the problem
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
private static class StaticResourcesHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String requestURI = request.getRequestURI();
if (requestURI == null || "/".equals(requestURI) || "/index.html".equals(requestURI) || requestURI.startsWith("/assets")) {
return super.preHandle(request, response, null);
}
return super.preHandle(request, response, handler);
}
}
#Autowired
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new StaticResourcesHandlerInterceptor()).addPathPatterns("/**");
}
}
You can split the path into a wild-card, and a named path variable which must match a negative lookahead regular expression.
#RequestMapping("/{variable:(?!static).*}/**")
You can then use #PathVariable String variable as an argument of your controller method to obtain the value of variable if you need to pass it.
(Would rather have written a comment but I have insufficient reputation)
Try to add the #EnableWebMvc annotation to your configuration:
#Configuration
#EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
...
}
Since Servlet 3.0, HttpServletResponse#getHeaderNames() and HttpServletResponse#getHeaders() has been available. However, I'm using an older spec, specifically Servlet 2.4.
Having looked at the resource, How can I get the HTTP status code out of a ServletResponse in a ServletFilter?, I got an idea of how to write a wrapper. If I understand it right, I have to use setHeader() to facilitate the creation of getHeaderNames() and getHeaders(). I think I have a solid footing on how to store the headers to simulate the usage of these missing methods.
The problem is the filter which leverages this wrapper does not seem to be calling setHeader() automatically. I don't get it. I presume sincegetStatus() is working properly, I'm expecting setHeader() to behave in the same fashion. Specifically, I'm looking to print out all the response headers, after calling chain.doFilter(). I'm not sure what I'm doing wrong here. Maybe there is something wrong with how I'm storing header name-value pairs.
I would appreciate any help. Thank you.
public class ServletResponseWrapper extends HttpServletResponseWrapper {
private int httpStatus = SC_OK;
private HashMap<String, String> hashMapHeaders = new HashMap<String, String>();
public ServletResponseWrapper(HttpServletResponse response) {
super(response);
}
#Override
public void sendError(int sc) throws IOException {
httpStatus = sc;
super.sendError(sc);
}
#Override
public void sendError(int sc, String msg) throws IOException {
httpStatus = sc;
super.sendError(sc, msg);
}
#Override
public void setStatus(int sc) {
httpStatus = sc;
super.setStatus(sc);
}
public int getStatus() {
return httpStatus;
}
#Override
public void sendRedirect(String location) throws IOException {
httpStatus = SC_MOVED_TEMPORARILY;
super.sendRedirect(location);
}
#Override
public void setHeader(String name, String value) {
hashMapHeaders.put(name, value);
super.setHeader(name, value);
}
public String getHeader(String name) {
return hashMapHeaders.get(name);
}
public Enumeration<String> getHeaderNames() {
Enumeration<String> enumerationHeaderNames = Collections.enumeration(hashMapHeaders.keySet());
return enumerationHeaderNames;
}
}
public class ServletResponseWrapperFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletResponseWrapper servletResponseWrapper = new ServletResponseWrapper( (HttpServletResponse) response );
chain.doFilter( request, servletResponseWrapper );
// Process response
// This works, even though I never explicitly call the setStatus() method
int status = response.getStatus();
// This returns NULL because no header values get set; I presume setHeader() gets called implicitly
Enumeration<String> headerNames = servletResponseWrapper.getHeaderNames();
}
public void init(FilterConfig config) throws ServletException {
//empty
}
public void destroy() {
// empty
}
}
web.xml file
<display-name>Tomcat App</display-name>
<filter>
<filter-name>ResponseHeadersFilter</filter-name>
<filter-class>com.company.filters.ResponseHeadersFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ResponseHeadersFilter</filter-name>
<url-pattern>/testfilter.jsp</url-pattern>
</filter-mapping>
I took the vendor's servlet out of the equation. The filter now fires on an empty JSP file. Tomcat is also hooked to a front-end web server, IIS. I disabled IIS. Now, I'm accessing the website directly over Tomcat, via port 8080. Despite all this, I dot see any response headers.
Using Fiddler, the response headers I see are few but existing, namely:
(Cache) Date
(Entity) Content- Length, Content-Type
(Miscellaneous) Server
And status response, i.e. HTTP/1.1 200 OK
I can get by without getting response headers in the filter. But the big question I have is this is a bug with Servlet version 2.4 or is there some kind of OS Server and/or Tomcat configuration change I need to enable? Unless there's some Tomcat configuration, I'm led to believe this is likely a bug. Perhaps a clean install using the default configuration of the Tomcat version I'm using, 5.5.28, would resolve the problem, but I cannot attempt that at this time.
I am new to Servlets. In the book i am reading now it is written, that we need wrappers, because it is late to do anything with response after finishing chain.doFilter() method as response is sent already.
I wrote the following Servlet and Filter:
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
PrintWriter writer = response.getWriter();
writer.println("In Servlet");
}
}
public class MyFilter implements Filter{
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException{
PrintWriter writer = response.getWriter();
chain.doFilter(request, response);
writer.println("After chain");
}
}
And i see both strings in the browser.
My question is: Why do we need wrappers? I still can write to response even after chain.doFilter and i still see result?
Is it because response is sent in two pieces(first in the end of chain.doFilter and second in the end of Filter.doFilter)? So if i had to compress response it would work incorrectly(because first uncompressed part would be sent)?
The book is talking about response headers.
You misunderstood it as response body.
Here are some real world use cases of response wrappers so you can see why we may need them:
How to add response headers based on Content-type; getting Content-type before the response is committed
How do delete a HTTP response header?
How to read and copy the HTTP servlet response output stream content for logging
How to insert JSF page rendering time and response size into the page itself, at least partially?
How to configure Tomcat to not encode the session id into the URL when HttpServletResponse.encodeURL() is invoked
For more examples, see this search.
I am trying to implement a filter that uses MockHttpServletRequest to add a header to the request object. I want to use that header for preauthentication. This is the filter class..
public class MockAuthFilter implements Filter{
private FilterConfig filterConfig = null;
private static String loggedInUserName = "myId";
private static String httpRequestHeaderName = "SM_ID";
private Logger logger = Logger.getLogger(MockAuthFilter.class);
#Override
public void destroy() {
this.filterConfig = null;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if(this.filterConfig.getServletContext() == null){}
HttpServletRequest httpRequest = (HttpServletRequest) request;
MockHttpServletRequest mRequest = new MockHttpServletRequest(this.filterConfig.getServletContext());
if(mRequest.getHeader(httpRequestHeaderName)==null ||
!mRequest.getHeader(httpRequestHeaderName).equalsIgnoreCase(loggedInUserName))
mRequest.addHeader(httpRequestHeaderName, loggedInUserName);
mRequest.setMethod("GET");
mRequest.setRequestURI(httpRequest.getRequestURL().toString());
logger.debug("**********************exiting doFilter() method*****************");
chain.doFilter(mRequest, response);
}
#Override
public void init(FilterConfig config) throws ServletException {
this.filterConfig = config;
}
}
but there is not url populated when the request reaches the next filter in the filter chain of Spring Security. I see following lines in the log file..
[2011-10-13 16:52:35,114] [DEBUG] [http-8080-1] [com.app.filter.MockAuthFilter:doFilter:55] - **********************exiting doFilter() method*****************
[2011-10-13 16:52:35,114] [DEBUG] [http-8080-1] [com.app.filter.MockAuthFilter:doFilter:55] - **********************exiting doFilter() method*****************
[2011-10-13 16:52:35,114] [DEBUG] [http-8080-1] [org.springframework.security.web.util.AntPathRequestMatcher:matches:103] - Checking match of request : ''; against '/static/**'
[2011-10-13 16:52:35,114] [DEBUG] [http-8080-1][org.springframework.security.web.util.AntPathRequestMatcher:matches:103] - Checking match of request : ''; against '/static/**'
As you can see there was no url in the request object passed onto AntPathrequestMatcher's matches method..
I have checked mockrequest object right before chain.doFilter() method and it contains the url value in its requestURI field. if URI and URL aren't the same thing here, what changes should I make here so that url is maintained in the request object..
Don't use MockHttpServletRequest like that. It's for testing, not for production code. The appropriate way to wrap a request and/or response in a filter to modify its behavior later on is with HttpServletRequestWrapper and HttpServletResponseWrapper.
Why are you even trying to wrap or remove the original request if all you want is to add a header? Just use addHeader() on the incoming request.
Trying to change the request method and request URI in the filter as you're doing may have unexpected consequences and will almost certainly not do what you probably expect it to do. By the time the request hits your filter, the filter chain has already been built based on the original state of the request. Changing it now won't affect where it ends up going.
I am just beginning with Servlets and managed to have some servlets that act as individual URLs for populating a database for some dummy testing. Something of the form:
public class Populate_ServletName extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain");
//Insert records
//Print confirmation
}
}
I have about 6 such servlets which I want to execute in a sequence. I was thinking of using setLocation to set the next page to be redirected but was not sure if this is the right approach because the redirects should happen after the records have been inserted. Specifically, I am looking for something like this:
public class Populate_ALL extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain");
//Call Populate_1
//Call Populate_2
//Call Populate_3
//...
}
}
Any suggestions?
Use RequestDispatcher#include() on an URL matching the url-pattern of the Servlet.
public class Populate_ALL extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
request.getRequestDispatcher("/populateServlet1").include(request, response);
request.getRequestDispatcher("/populateServlet2").include(request, response);
request.getRequestDispatcher("/populateServlet3").include(request, response);
//...
}
}
Note: if those servlets cannot be used independently, then this is the wrong approach and you should be using standalone Java classes for this which does not extend HttpServlet. In your specific case, I think the Builder Pattern may be of interest.
The RequestDispatcher#forward() is not suitable here since it throws IllegalStateException when the response headers are already committed. This will be undoubtely the case when you pass the request/response through multiple servlets which each writes to the response.
The HttpServletResponse#sendRedirect() is absolutely not suitable here since it implicitly creates a brand new request and response, hereby trashing the original ones.
See also:
How do I call a second JSP servlet while in the first JSP servlet?
RequestDispatcher.forward() vs HttpServletResponse.sendRedirect()
communication between remote servlets
It looks like what you may need is a service that each of the servlets can use to perform some work. Then the servlets are not depending one and another, but rather all using the service.
However, here is an explanation of forwarding or redirecting requests.