Spring MVC: How to modify json response sent from controller - java

I've built a json REST service with controllers like this one:
#Controller
#RequestMapping(value = "/scripts")
public class ScriptController {
#Autowired
private ScriptService scriptService;
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<Script> get() {
return scriptService.getScripts();
}
}
It works fine, but now I need to modify all responses and add "status" and "message" fields to all of them. I've read about some solutions:
return from all controller methods object of some specific class, for example,
RestResponse, which will contain "status" and "message" fields (but it's
not general solution, cause I will have to modify all my controllers
and write new controllers in new style)
intercept all controller methods with aspects (but in this case I can't change return type)
Can you suggest some other, general and correct solution, if I want to wrap values returned from controller methods into objects of class:
public class RestResponse {
private int status;
private String message;
private Object data;
public RestResponse(int status, String message, Object data) {
this.status = status;
this.message = message;
this.data = data;
}
//getters and setters
}

I've encountered with similar problem and suggest you to use Servlet Filters to resolve it.
Servlet Filters are Java classes that can be used in Servlet Programming to intercept requests from a client before they access a resource at back end or to manipulate responses from server before they are sent back to the client.
Your filter must implement the javax.servlet.Filter interface and override three methods:
public void doFilter (ServletRequest, ServletResponse, FilterChain)
This method is called every time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.
public void init(FilterConfig filterConfig)
Called before the filter goes into service, and sets the filter's configuration object.
public void destroy()
Called after the filter has been taken out of service.
There is possibility to use any number of filters, and the order of execution will be the same as the order in which they are defined in the web.xml.
web.xml:
...
<filter>
<filter-name>restResponseFilter</filter-name>
<filter-class>
com.package.filters.ResponseFilter
</filter-class>
</filter>
<filter>
<filter-name>anotherFilter</filter-name>
<filter-class>
com.package.filters.AnotherFilter
</filter-class>
</filter>
...
So, this filter gets the controller response, converts it into String, adds as feild to your RestResponse class object (with status and message fields), serializes it object into Json and sends the complete response to the client.
ResponseFilter class:
public final class ResponseFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
String responseContent = new String(responseWrapper.getDataStream());
RestResponse fullResponse = new RestResponse(/*status*/, /*message*/,responseContent);
byte[] responseToSend = restResponseBytes(fullResponse);
response.getOutputStream().write(responseToSend);
}
#Override
public void destroy() {
}
private byte[] restResponseBytes(RestResponse response) throws IOException {
String serialized = new ObjectMapper().writeValueAsString(response);
return serialized.getBytes();
}
}
chain.doFilter(request, responseWrapper) method invokes the next filter in the chain, or if the calling filter is the last filter in the chain invokes servlet logic.
The HTTP servlet response wrapper uses a custom servlet output stream that lets the wrapper manipulate the response data after the servlet is finished writing it out. Normally, this cannot be done after the servlet output stream has been closed (essentially, after the servlet has committed it). That is the reason for implementing a filter-specific extension to the ServletOutputStream class.
FilterServletOutputStream class:
public class FilterServletOutputStream extends ServletOutputStream {
DataOutputStream output;
public FilterServletOutputStream(OutputStream output) {
this.output = new DataOutputStream(output);
}
#Override
public void write(int arg0) throws IOException {
output.write(arg0);
}
#Override
public void write(byte[] arg0, int arg1, int arg2) throws IOException {
output.write(arg0, arg1, arg2);
}
#Override
public void write(byte[] arg0) throws IOException {
output.write(arg0);
}
}
To use the FilterServletOutputStream class should be implemented a class that can act as a response object. This wrapper object is sent back to the client in place of the original response generated by the servlet.
ResponseWrapper class:
public class ResponseWrapper extends HttpServletResponseWrapper {
ByteArrayOutputStream output;
FilterServletOutputStream filterOutput;
HttpResponseStatus status = HttpResponseStatus.OK;
public ResponseWrapper(HttpServletResponse response) {
super(response);
output = new ByteArrayOutputStream();
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
if (filterOutput == null) {
filterOutput = new FilterServletOutputStream(output);
}
return filterOutput;
}
public byte[] getDataStream() {
return output.toByteArray();
}
}
I think this approach will be a good solution for your issue.
Please, ask a questions, if something not clear and correct me if I'm wrong.

If you use spring 4.1 or above, you can use ResponseBodyAdvice to customizing
response before the body is written.

Related

Spring MVC: How to modify #Pathvariable(URI) in Interceptor before going to controller?

I was get Controller's #PathVariable in Pre-Handler Interceptor.
Map<String, String> pathVariable = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE );
But I wish to modify #PathVariable value(below).
#RequestMapping(value = "{uuid}/attributes", method = RequestMethod.POST)
public ResponseEntity<?> addAttribute(#PathVariable("uuid") String uuid, HttpServletRequest request, HttpServletResponse response) {
//LOGIC
}
How to modify #PathVariable("uuid") value in interceptor before going to controller??
I'm using Spring 4.1 and JDK 1.6. I can't upgrade its.
A general use for interceptors is to apply generic functionality to controllers. I.e. default data shown on all pages, security etc. You want to use it for a single piece of functionality which you generallly shouldn't do.
What are you trying to achieve isn't possible with an interceptor. As first the method to execute is detected based on the mapping data. Before executing the method the interceptor is executed. In this you basically want to change the incoming request and to execute a different method. But the method is already selected, hence it won't work.
As you eventually want to call the same method simply add another request handling method which either eventually calls addAttribute or simply redirects to the URL with the UUID.
#RequestMapping("<your-url>")
public ResponseEntity<?> addAttributeAlternate(#RequestParam("secret") String secret, HttpServletRequest request, HttpServletResponse response) {
String uuid = // determine UUID based on request
return this.addAttribute(uuid,request,response);
}
Try below given code.
public class UrlOverriderInterceptor implements ClientHttpRequestInterceptor {
private final String urlBase;
public UrlOverriderInterceptor(String urlBase) {
this.urlBase = urlBase;
}
private static Logger LOGGER = AppLoggerFactory.getLogger(UrlOverriderInterceptor.class);
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI uri = request.getURI();
LOGGER.warn("overriding {0}", uri);
return execution.execute(new MyHttpRequestWrapper(request), body);
}
private class MyHttpRequestWrapper extends HttpRequestWrapper {
public MyHttpRequestWrapper(HttpRequest request) {
super(request);
}
#Override
public URI getURI() {
try {
return new URI(UrlUtils.composeUrl(urlBase, super.getURI().toString())); //change accordingly
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
}

Servlet filter "proxy" that only acts on response from remote endpoint

I have a need where certain HTTP requests must be redirected to a Spring Boot web app/service, but that on the request-side, the Spring app does nothing and acts as a passthrough between the HTTP client (another service) and the request's true destination. But when the response comes back to the Spring app (from that destination), I need the Spring app to be able to inspect the response and possibly take action on it if need be. So:
HTTP client makes a request to, say, http://someapi.example.com
Network magic routes the request to my Spring app at, say, http://myproxy.example.com
On the request, this app/proxy does nothing, and so the request is forwarded on http://someapi.example.com
The service endpoint at http://someapi.example.com returns an HTTP response back to the proxy
The proxy at http://myproxy.example.com inspects this response, and possibly sends an alert before returning the response back to the original client
So essentially, a filter that acts as a pass-through on the request, and only really does anything after the remote service has executed and returned a response.
My best attempt thus far has been to setup a servlet filter:
#Override
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response)
// How and where do I put my code?
if(responseContainsFizz(response)) {
// Send an alert (don't worry about this code)
}
}
Is this possible to do? If so, where do I put the code that inspects and acts upon the response? With my code the way it is I get exceptions thrown when trying to hit a controller from a browser:
java.lang.IllegalStateException: STREAM
at org.eclipse.jetty.server.Response.getWriter(Response.java:910) ~[jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_92]
rest of stack trace omitted for brevity
Any ideas?
Per the Servlet API documentation, the reason you are getting the IllegalStateException is because you are attempting to call ServletResponse.getWriter after ServletResponse.getOutputStream has already been called on the response. So it appears that the method you need to call is ServletResponse.getOutputStream().
However, if you are trying to access the body of the response, the best solution is to wrap the response in a ServletResponseWrapper so that you can capture the data:
public class MyFilter implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void destroy()
{
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
MyServletResponseWrapper responseWrapper = new MyServletResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
if (evaluateResponse(responseWrapper)) {
// Send an alert
}
}
private boolean evaluateResponse(MyServletResponseWrapper responseWrapper) throws IOException
{
String body = responseWrapper.getResponseBodyAsText();
// Perform business logic on the body text
return true;
}
private static class MyServletResponseWrapper extends HttpServletResponseWrapper
{
private ByteArrayOutputStream copyOutputStream;
private ServletOutputStream wrappedOutputStream;
public MyServletResponseWrapper(HttpServletResponse response)
{
super(response);
}
public String getResponseBodyAsText() throws IOException
{
String encoding = getResponse().getCharacterEncoding();
return copyOutputStream.toString(encoding);
}
#Override
public ServletOutputStream getOutputStream() throws IOException
{
if (wrappedOutputStream == null) {
wrappedOutputStream = getResponse().getOutputStream();
copyOutputStream = new ByteArrayOutputStream();
}
return new ServletOutputStream()
{
#Override
public boolean isReady()
{
return wrappedOutputStream.isReady();
}
#Override
public void setWriteListener(WriteListener listener)
{
wrappedOutputStream.setWriteListener(listener);
}
#Override
public void write(int b) throws IOException
{
wrappedOutputStream.write(b);
copyOutputStream.write(b);
}
#Override
public void close() throws IOException
{
wrappedOutputStream.close();
copyOutputStream.close();
}
};
}
}
}
The response can be easy manipulated/replaced/extended e with a filter and a response wrapper.
In the filter before the call chain.doFilter(request, wrapper) you prepare a PrintWriter for the new response content and the wrapper object.
After the call chain.doFilter(request, wrapper) is the actuall response manipulation.
The wrapper is used to get access to the response as String.
The Filter:
#WebFilter(filterName = "ResponseAnalysisFilter", urlPatterns = { "/ResponseFilterTest/*" })
public class ResponseFilter implements Filter {
public ResponseFilter() {}
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, wrapper);
String oldResponseString = wrapper.toString();
if (oldResponseString.contains("Fizz")) {
// replace something
String newResponseString = oldResponseString.replaceAll("Fizz", "Cheers");
// show alert with a javascript appended in the head tag
newResponseString = newResponseString.replace("</head>",
"<script>alert('Found Fizz, replaced with Cheers');</script></head>");
out.write(newResponseString);
response.setContentLength(newResponseString.length());
}
else { //not changed
out.write(oldResponseString);
}
// the above if-else block could be replaced with the code you need.
// for example: sending notification, writing log, etc.
out.close();
}
}
The Response Wrapper:
public class CharResponseWrapper extends HttpServletResponseWrapper {
private CharArrayWriter output;
public String toString() {
return output.toString();
}
public CharResponseWrapper(HttpServletResponse response) {
super(response);
output = new CharArrayWriter();
}
public PrintWriter getWriter() {
return new PrintWriter(output);
}
}
The Test Servlet:
#WebServlet("/ResponseFilterTest/*")
public class ResponseFilterTest extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
response.getWriter().append(
"<html><head><title>replaceResponse filter</title></head><body>");
if (request.getRequestURI().contains("Fizz")) {
response.getWriter().append("Fizz");
}
else {
response.getWriter().append("Limo");
}
response.getWriter().append("</body></html>");
}
}
Test Urls:
https://yourHost:8181/contextPath/ResponseFilterTest/Fizz (Trigger response Replacement)
https://yourHost:8181/contextPath/ResponseFilterTest/ (response unchanged)
More Info and examples about filters:
http://www.oracle.com/technetwork/java/filters-137243.html#72674
http://www.leveluplunch.com/java/tutorials/034-modify-html-response-using-filter/
https://punekaramit.wordpress.com/2010/03/16/intercepting-http-response-using-servlet-filter/

How to alter the body of a http response in a filter

I am attempting to use a filter to check for HTML tags in a response body. The problem is that if I alter the body in the filter, it isn't altered when it gets to the client. I tried the solution shown here: Looking for an example for inserting content into the response using a servlet filter
but it didn't help.
I have two filters. SecureWrapperFilter wraps the request/response objects in our custom wrapper, and XSSFilter uses OWASP encode to encode for html content. The filters look like this:
public class SecureWrapperFilter implements Filter {
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response,
final FilterChain chain) throws IOException, ServletException
{
final ServletRequestWrapper securityRequest =
new ServletRequestWrapper((HttpServletRequest)request);
final ServletResponseWrapper securityResponse =
new ServletResponseWrapper((HttpServletResponse)response);
ESAPI.httpUtilities().setCurrentHTTP(securityRequest, securityResponse);
chain.doFilter(ESAPI.currentRequest(), ESAPI.currentResponse());
}
#Override
public void destroy() {
}
}
and:
public class XSSFilter implements Filter {
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response,
final FilterChain chain) throws IOException, ServletException
{
final ServletRequestWrapper requestWrapper = (ServletRequestWrapper)request;
final String body = Encode.forHtmlContent(requestWrapper.getBody());
requestWrapper.setBody(body);
chain.doFilter(requestWrapper, response);
final ServletResponseWrapper responseWrapper = (ServletResponseWrapper)response;
final byte[] copy = responseWrapper.getCopy();
final String oldBody = new String(copy, response.getCharacterEncoding());
final String newBody = Encode.forHtmlContent(oldBody);
if (!StringUtils.equals(oldBody, newBody)) {
responseWrapper.getResponse().getOutputStream().write(newBody.getBytes());
}
}
#Override
public void destroy() {
}
}
If I add some debug Logging, I can see that the securityResponse has the modified body in the SecureWrapperFilter, but on the client side, the body looks as if it was never modified.
Any suggestions would be greatly appreciated. Thanks.
The problem was that in my XSSFilter, I was appending the new response body onto the old one. This was causing invalid json like {"x"="y"}{"escapedx"="escapedy")
Our client deserializer was only printing the first json object so {"x"=y"} was all we were seeing on the client side.
To resolve this problem, I added the following line to the XSSFilter:
responseWrapper.getResponse().resetBuffer();
before
responseWrapper.getResponse().getOutputStream().write(newBody.getBytes());
This clears the buffer, allowing me to rewrite it on the line below. My json on the client side now looks like: {"escapedx"="escapedy"}
You need to make sure the HttpResponse is buffered. If the buffer is not big enough, then the reponse will be streamed to the client befire your filter is called.
Or maybe the servler is calling flush() on the response?
Sending back json can be done with jackson:
val res = response as HttpServletResponse
res.status = HttpStatus.UNAUTHORIZED.value()
res.contentType = MediaType.APPLICATION_JSON_UTF8_VALUE
res.outputStream.write(ObjectMapper().writeValueAsString(ResponseError(
"two_factor_auth_failed", "Two Factor Authorization is required to proceed."
)).toByteArray())

can we change servlet filter with in-it parameters as interceptor in spring

I have a filter in one of the jar files added as reference.
My project is in spring nature. I have developed a web services. All the requests to this web services should be intercepted by my filter "HelloWorld".
This filter is there in one of the reference files.
Here I thought I would implement it as Interceptor.
The filter in reference files looks like
public class HelloWorld implements Filter {
private static final Logger _logger = Logger.getLogger(HelloWorld.class.getName());
protected String name = null;
public HelloWorld()
{
}
public HelloWorld(String name) {
this.name = name;
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
PrintWriter out = response.getWriter();
out.println("Hello "+name);
chain.doFilter(req, res);
}
public void init(FilterConfig config) throws ServletException {
//Get init parameter
String testParam = config.getInitParameter("test");
//Print the init parameter
System.out.println("test param: " + testParam);
}
public void destroy() {
//add code to release any resource
}
//some other methods as well
}
What would be the best way to implement this. I cannot configure filter in my web.xml due to limitation in my application.
Can we directly give this HelloWorld filter as reference to interceptor, so that it behaves like interceptor.
Can we change this filter as interceptor in spring and configure in spring.xml without changing the functionality.
Apologies if my question is simple.
Thanks.

How to define message converter based on url extension in Spring MVC?

How can I influence the process of choosing a message converter in AnnotationMethodHandlerAdapter for resulting POJO by url extension?
I would like to have more representations of one data object, while data representation should be chosen by the requested url extension e.g. /users/2.xml or /users/2.json.
Current configuration of message handlers, which should be chosen based on url extension:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
p:marshaller-ref="xmlMarshaller" p:unmarshaller-ref="xmlMarshaller" />
</list>
</property>
</bean>
There is one way, which I'm nearly comfortable with and that is using ContentNegotiatingViewResolver, however I would like to bypass the process of view resolution and directly use message converters. Also when creating actions, using ResponseEntity in public ResponseEntity<User> showUser() provides fine grained control of resulting http status codes definitions (OK, NOT_FOUND, NO_CONTENT, ..). I couldn't find a way of using ResponseEntity with ContentNegotiatingViewResolver, which would also satisfy my needs.
Another way could be by modifying the request accept header to application/xml or application/json based on the url extension. This way, all the processing should go directly to the configured message converter. However I don't know a reasonable way to tamper the request headers.
Thanks.
Since the choose of HttpMessageConverters uses the Accept request header, perhaps the simpliest way to implement a content negotiation is to replace this header with the desired media type specified by the URL extension.
This can be implemented either as a Filter (using HttpServletRequestWrapper to replace header value) or by overriding AnnotationMethodHanlderAdapter.createHttpInputMessage() as suggested in SPR-7517 (requires Spring 3.0.2).
See also SPR-6993.
I had this same need and hacked together a servlet filter to accomplish the task. Not a work of art, but gets the job done:
public class UrlExtensionFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
if (httpServletRequest.getRequestURI().endsWith(".json")) {
MyAcceptHeaderRequest acceptHeaderRequest = new MyAcceptHeaderRequest(httpServletRequest);
acceptHeaderRequest.setAcceptHeader("application/json");
filterChain.doFilter(acceptHeaderRequest, response);
} else if (httpServletRequest.getRequestURI().endsWith(".xml")) {
MyAcceptHeaderRequest acceptHeaderRequest = new MyAcceptHeaderRequest(httpServletRequest);
acceptHeaderRequest.setAcceptHeader("text/xml");
filterChain.doFilter(acceptHeaderRequest, response);
} else {
filterChain.doFilter(request, response);
}
}
public void destroy() {
}
public class MyAcceptHeaderRequest extends HttpServletRequestWrapper {
private String accept = "application/json";
public MyAcceptHeaderRequest(HttpServletRequest request) throws IOException {
super(request);
}
public void setAcceptHeader(String value) {
accept = value;
}
#Override
public String getHeader(String name) {
if (name.equalsIgnoreCase("accept") || name.equalsIgnoreCase("content-type")) {
return accept;
} else {
return super.getHeader(name);
}
}
#Override
public Enumeration getHeaders(String name) {
if (name.equalsIgnoreCase("accept") || name.equalsIgnoreCase("content-type")) {
Enumeration enumeration = new StringTokenizer(accept);
return enumeration;
} else {
return super.getHeaders(name);
}
}
#Override
public String getContentType() {
return accept;
}
#Override
public String getParameter(String name) {
// When we're using this class and it is a POST operation then the body is JSON or XML so don't allow
// attempts to retrieve parameter names to consume the input stream
if (this.getMethod().equals("POST")) {
return null;
} else {
return super.getParameter(name);
}
}
#Override
public String[] getParameterValues(String name) {
// When we're using this class and it is a POST operation then the body is JSON or XML so don't allow
// attempts to retrieve parameter names to consume the input stream
if (this.getMethod().equals("POST")) {
return null;
} else {
return super.getParameterValues(name);
}
}
}
}

Categories

Resources