I am trying to log (just to console write now for simplicity sake) the final rendered HTML that will be returned by the HttpServletResponse. (i.e. the body) To this end, I am using the HandlerInterceptorAdapter from Spring MVC like so:
public class VxmlResponseInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(response.toString());
}
}
This works as expected and I see the HTTP response headers in the console. My question is if there is a relatively simple way to log the entire response body (i.e. final rendered HTML) to the console without having to resort to doing jumping jacks with PrintWriters, OutputStream's and the like.
Thanks in advance.
This would be better done using a Servlet Filter rather than a Spring HandlerInterceptor, for the reason that a Filter is allowed to substitute the request and/or response objects, and you could use this mechanism to substitute the response with a wrapper which logs the response output.
This would involve writing a subclass of HttpServletResponseWrapper, overriding getOutputStream (and possibly also getWriter()). These methods would return OutputStream/PrintWriter implementations that siphon off the response stream into a log, in addition to sending to its original destination. An easy way to do this is using TeeOutputStream from Apache Commons IO, but it's not hard to implement yourself.
Here's an example of the sort of thing you could do, making use of Spring's GenericFilterBean and DelegatingServletResponseStream, as well as TeeOutputStream, to make things easier:
public class ResponseLoggingFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse responseWrapper = loggingResponseWrapper((HttpServletResponse) response);
filterChain.doFilter(request, responseWrapper);
}
private HttpServletResponse loggingResponseWrapper(HttpServletResponse response) {
return new HttpServletResponseWrapper(response) {
#Override
public ServletOutputStream getOutputStream() throws IOException {
return new DelegatingServletOutputStream(
new TeeOutputStream(super.getOutputStream(), loggingOutputStream())
);
}
};
}
private OutputStream loggingOutputStream() {
return System.out;
}
}
This logs everything to STDOUT. If you want to log to a file, it'll get a big more complex, what with making sure the streams get closed and so on, but the principle remains the same.
If you're using (or considering) logback as your logging framework, there is a nice servlet filter already available that does exactly that. Checkout the TeeFilter chapter in the documentation.
I've been looking for a way to log full HTTP Request/Response for a while and discovered it has been solved for me in the Tomcat 7 RequestDumperFilter. It works as advertised from a Tomcat 7 container. If you want to use it in Jetty, the class works fine stand-alone or, as I did, copied and adapted to the specific needs of my environment.
I made a small library spring-mvc-logger available via maven central.
Add to pom.xml:
<dependency>
<groupId>com.github.isrsal</groupId>
<artifactId>spring-mvc-logger</artifactId>
<version>0.2</version>
</dependency>
Add to web.xml:
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.github.isrsal.logging.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Add to log4j.xml:
<logger name="com.github.isrsal.logging.LoggingFilter">
<level value="DEBUG"/>
</logger>
the code pasted below works with my tests and can be downloaded from my github project, sharing after applying a solution based on that on a production project
#Configuration
public class LoggingFilter extends GenericFilterBean {
/**
* It's important that you actually register your filter this way rather then just annotating it
* as #Component as you need to be able to set for which "DispatcherType"s to enable the filter
* (see point *1*)
*
* #return
*/
#Bean
public FilterRegistrationBean<LoggingFilter> initFilter() {
FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoggingFilter());
// *1* make sure you sett all dispatcher types if you want the filter to log upon
registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
// *2* this should put your filter above any other filter
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ContentCachingRequestWrapper wreq =
new ContentCachingRequestWrapper(
(HttpServletRequest) request);
ContentCachingResponseWrapper wres =
new ContentCachingResponseWrapper(
(HttpServletResponse) response);
try {
// let it be ...
chain.doFilter(wreq, wres);
// makes sure that the input is read (e.g. in 404 it may not be)
while (wreq.getInputStream().read() >= 0);
System.out.printf("=== REQUEST%n%s%n=== end request%n",
new String(wreq.getContentAsByteArray()));
// Do whatever logging you wish here, in this case I'm writing request
// and response to system out which is probably not what you wish to do
System.out.printf("=== RESPONSE%n%s%n=== end response%n",
new String(wres.getContentAsByteArray()));
// this is specific of the "ContentCachingResponseWrapper" we are relying on,
// make sure you call it after you read the content from the response
wres.copyBodyToResponse();
// One more point, in case of redirect this will be called twice! beware to handle that
// somewhat
} catch (Throwable t) {
// Do whatever logging you whish here, too
// here you should also be logging the error!!!
throw t;
}
}
}
Related
in our application we are creating request filters which should not be hit for some urls. We want be able to exclude urls like spring do witch url patterns, e.g.:
// register filter in bean
FilterRegistrationBean filterBeanRegistration = new FilterRegistrationBean();
filterBeanRegistration.setFilter(myFilter());
filterBeanRegistration.addInitParameter("excluded", "*/foo/**, */bar/**");
...
and new filter doesn't hit urls like domain:8080/aaa/foo/xxxx, domain:8080/bbb/bar/xxxx .
Can you tell me how to do that with spring classes or another simple way? Thank you in advice.
EDIT:
There is FilterBeanRegistration#addUrlPatterns(String... urls) method where I can specify urls, but there is no any format which tells which url should hit. For our purposes is better exclude some urls.
You can use org.springframework.web.filter.OncePerRequestFilter. It gets executed once every incoming request. You can override shouldNotFilter method to exclude the URLs you don't want the filter to run for. Example code:
public class MyFilter extends OncePerRequestFilter {
private static final String[] excludedEndpoints = new String[] {"*/foo/**, */bar/**"};
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return Arrays.stream(excludedEndpoints)
.anyMatch(e -> new AntPathMatcher().match(e, request.getServletPath()));
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Filtering logic goes here. call below line on successful authentication.
filterChain.doFilter(request, response);
}
}
I have a class implementing a JAX-RS endpoint, as per below:
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED})
#Path("/site/")
public class ApiSiteResource extends AbstractContentResource {
...
#GET
#Path("/article/")
public Map<String, Object> getArticle (#Context HttpServletRequest request, #Context HttpServletResponse reponse, #BeanParam ApiParams params) {
//do stuff
}
#GET
#Path("/category/")
public Map<String, Object> getCategory (#Context HttpServletRequest request, #Context HttpServletResponse reponse, #BeanParam ApiParams params) {
//do stuff
}
What I need is to perform common processing (for example, capture analytics data) when any of the endponts of the above REST class is invoked, e.g., both for /site/article/ and /site/category/. I'm ideally looking for a solution that would be invoked at the end of the method execution, and ideally with least possible change to the existing methods code, so adding another method call at the end of the method is not the best option as that leads to too much code coupling. Ideally, I would like processing to be fired from an external class.
Is there a way how that could be done?
I am using a method of the Resource class that is annotated with the #Context Annotation and has a parameter that is injected from the context scope.
/**
* This method is called by JAX-RS for each request before
* the identified resource method is invoked, since it is
* annotated with the Context Annotation and carries a
* context-scope parameter which is injected.
*/
#Context
public void setServletContext( ServletContext servletContext ) {
...
}
(If you remove the ServletContext parameter, the automatic invocation on each resource call vanishes - at least in Jersey.)
Furthermore, you can put this method in a base class, say DefaultResourceImpl, which your Resource classes can extend, so you have this for all your Resource classes.
You can use JAX-RS Filters and Interceptors
For example there exist Request filters and response filters. You may do some stuff there:
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;
public class PoweredByResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}
Thank you all for your useful replies and comments.
Actually, capturing analytics was just half the story. In fact, I've also needed to add response headers.
So, I ended up implementing a filter as below:
public class ApiResourceHeadersFilter extends OncePerRequestFilter {
public ApiResourceHeadersFilter() {
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Headers", "accept");
response.setHeader("Access-Control-Allow-Methods", "GET OPTIONS");
filterChain.doFilter(request, response);
}
}
Plus added a mapping in the web.xml:
<filter>
<filter-name>ApiResourceHeadersFilter</filter-name>
<filter-class>com.workangel.eap.filters.ApiResourceHeadersFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ApiResourceHeadersFilter</filter-name>
<url-pattern>/api/site/*</url-pattern>
</filter-mapping>
Works like a charm; no messy code dependencies or modification. I'm sure I can extend it further should I need to collect analytics data as well.
I'd like to redirect incoming http connections to https in Dropwizard, preferably togglable in a config file (e.g. with a YAML file, like other connection attributes).
[I've seen this question, and I'm reasonably certain that it's not a solution]
A solution I've found in several places involves hooking in a Filter that checks the schema, and if it finds "http", calls sendRedirect with a modified URL. This involves hardcoding the behavior to make this always happen though.
If I extend the HttpConnectorFactory, it seems like I could add configuration in the YAML for whether I want the redirection to happen. However, it's unclear to me how complicated it will be to add an attribute without breaking other code.
This seems like a common task; is there a standard, "preferred" way to do this? I would have expected Dropwizard to have elegant built-in support, like Jetty does, but I can't find it.
I don't know that there's a "preferred" way to do this but how about something like this (for Dropwizard 0.7.0):
void addHttpsForward(ServletContextHandler handler) {
handler.addFilter(new FilterHolder(new Filter() {
public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
StringBuffer uri = ((HttpServletRequest) request).getRequestURL();
if (uri.toString().startsWith("http://")) {
String location = "https://" + uri.substring("http://".length());
((HttpServletResponse) response).sendRedirect(location);
} else {
chain.doFilter(request, response);
}
}
public void destroy() {}
}), "/*", EnumSet.of(DispatcherType.REQUEST));
}
#Override
public void run(ExampleConfiguration configuration, Environment environment) throws Exception {
//...
if (configuration.forwardHttps()) {
addHttpsForward(environment.getApplicationContext());
}
//...
}
You'd just need to add a boolean to your application configuration and then you could easily switch https forwarding with your YAML.
You can use the redirect bundle at
https://github.com/dropwizard-bundles/dropwizard-redirect-bundle
#Override
public void initialize(final Bootstrap<PrmCatchConfiguration> bootstrap) {
bootstrap.addBundle(new RedirectBundle(new HttpsRedirect(false)));
Above HttpsRedirect is constructed with false for allowPrivateIps which makes testing things locally possible. HttpsRedirect docs has plenty of information on this.
So, let's have this simple controller:
#Controller
public class MyController {
private static final Logger logger = LoggerFactory.getLogger(MyController.class);
#RequestMapping(value="/entities", method = RequestMethod.GET)
public #ResponseBody ResultPojo getSomething() {
logger.info("getSometing");
return new ResultPojo();
}
}
...and the following context fragment:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
Which basically means I want to be able to return nothing but json representations of the result bean, otherwise return 406.
If I send a GET request with accept=application/json, everything works fine, a json representation is returned in the http response with the 200 Ok status.
If I send a GET request with accept=application/xml, 406 is returned.
My problem in the second case is that even though 406 is returned eventually, the getSomething() method is still called (which I can see in the log). While this is no big deal for GET methods, it can cause confusion for POST methods (the resource is altered, but 406 is returned).
Is there a simple way to tell SpringMVC to check the accept header and return 406 before invoking the controller method? Or do I have to develop a custom http SpringMVC interceptor?
Is there a simple way to tell SpringMVC to check the accept header and return 406 before
invoking the controller method? Or do I have to develop a custom http SpringMVC interceptor?
the problem is I would have to put the produces clause to every
#RequestMapping in every controller. I'd like to set this on an
application level.
as far as I know there is no simpler method with SpringMVC. However, using standard JEE filters this is not very hard to do either. Just do something like:
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class YourFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
if (request.getRequestHeader("Accept").contains("application/json")) {
chain.doFilter(req, res);
} else {
((HttpServletResponse)response).setStatus(SC_NOT_ACCEPTABLE);
}
}
public void init(FilterConfig config) throws ServletException {
// any startup stuff here if needed
}
public void destroy() {
// add code to release any resource
}
}
and:
<filter>
<filter-name>YourFilter</filter-name>
<filter-class>
path.to.YourFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>YourFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(didn't test the code, but it should be about right)
Maybe this is what you want:
#RequestMapping(value = "/entities", method = RequestMethod.GET, headers = {"content-type=application/json"})
methodName() {
...
}
I have a Tapestry application that is serving its page as UTF-8. That is, server responses have header:
Content-type: text/html;charset=UTF-8
Now within this application there is a single page that should be served with ISO-8859-1 encoding. That is, server response should have this header:
Content-type: text/html;charset=ISO-8859-1
How to do this? I don't want to change default encoding for whole application.
Based on google searching I have tried following:
#Meta({ "org.apache.tapestry.output-encoding=ISO-8859-1",
"org.apache.tapestry.response-encoding=ISO-8859-1",
"org.apache.tapestry.template-encoding=ISO-8859-1",
"tapestry.response-encoding=ISO-8859-1"})
abstract class MyPage extends BasePage {
#Override
protected String getOutputEncoding() {
return "ISO-8859-1";
}
}
But neither setting those values with #Meta annotation or overriding getOutputEncoding method works.
I am using Tapestry 4.0.2.
EDIT: I ended up doing this with a Servlet filter with subclassed HttpServletResposeWrapper. The wrapper overrides setContentType() to force required encoding for the response.
Have you considered a Filter? Maybe not as elegant as something within Tapestry, but using a plain Filter, that registers the url mapping(s) of interest. One of its init parameters would be the encoding your after. Example:
public class EncodingFilter implements Filter {
private String encoding;
private FilterConfig filterConfig;
/**
* #see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig fc) throws ServletException {
this.filterConfig = fc;
this.encoding = filterConfig.getInitParameter("encoding");
}
/**
* #see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
req.setCharacterEncoding(encoding);
chain.doFilter(req, resp);
}
/**
* #see javax.servlet.Filter#destroy()
*/
public void destroy() {
}
}
You could have done:
#Override
public ContentType getResponseContentType() {
return new ContentType("text/html;charset=" + someCharEncoding);
}
The filter suggestion is good. You can also mix servlets with Tapestry. For instance, we have servlets for serving displaying XML documents and dynamically generated Excel files. Just make sure that correctly set the mappings in web.xml so that that the servlets do not go through Tapestry.
Tapestry has the concept of filters that can be applied to the request/response pipeline, but with the advantage that you can access the T5 IoC Container & Services.
http://tapestry.apache.org/tapestry5/tapestry-core/guide/request.html