Setting the response content-type without using HttpServletResponse - spring-mvc

How can I get HttpServletResponse object in a method in my spring controller so that my application remains loosely coupled with Http API?
Thanks...
Edit: Actually what i want is to set the ContentType of the HttpServletResponse object in my controller.Does spring provides any way for this without getting HttpServletResponse object as argument in the method of controller?

I can see two options:
If the content-type that you want is static, then you can add it to #RequestMapping, e.g.
#RequestMapping(value="...", produces="text/plain")
This will only work if the HTTP request contains the same content-type in its Accept header, though. See 16.3.2.5 Producible Media Types.
Alternatively, use ResponseEntity, e.g.
#RequestMapping("/something")
public ResponseEntity<String> handle() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(new MediaType("text", "plain"));
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
MediaType also has a handful of common mime types defined as constants, e.g. MediaType.TEXT_PLAIN.
See 16.3.3.6 Using HttpEntity<?>

Just pass it as an argument, e.g.
#RequestMapping( value="/test", method = RequestMethod.GET )
public void test( HttpServletResponse response ) { ... }

I think the best way to handle this is to use a specific View-Implementation, since here to response-rendering should take place.

Related

Serve static content in Spring Boot despite using #RequestMapping("**")

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 {
...
}

Spring controller return string as pure json

I am returning a string object from spring controller like
#RequestMapping(value = "/persons.html", method = RequestMethod.GET)
public #ResponseBody String listPersonHtml(Model model) {
return "{\"abc\":\"test\"}";
}
I am getting response on ui like "{\"abc\":\"test\"}",i want this response as
{"abc":"test"}
i.e pure json object.
what type of configuration I need?
On UI side,if I set Accept */* then I face this issue,if I set Accept text/html or Accept text/plain then no issue is there,but I can't change accept header.
I found the way.Its all about spring message-converters.I added MappingJackson2HttpMessageConverter in this list and this converter tries to convert string to json and produces this result.
Just add org.springframework.http.converter.StringHttpMessageConverter before MappingJackson2HttpMessageConverter so that StringHttpMessageConverter can come into action and string can be returned as it is.
Old question, but I just had to solve the same issue and most of the answers I found resulted misleading, so here's mine:
It all starts with the Controller, and Spring trying to answer a mapped request in the format that the invoking client is expecting. The client can inform this using different HTTP features, and there is where the different HttpMessageConverter implementations are involved. Spring pick's the format to answer based on different strategies, applied by the ContentNegotiationManager.
By prioritizing StringHttpMessageConverter over MappingJackson2XmlHttpMessageConverter you are only telling Spring to answer in "text/plain" format over "application/json", and it will work until a client specifies that is expecting a json response (this is mostly done by setting the Accept header in the request, although there are other ways to do it). The important thing is that if a client sets that header to "application/json", Spring will use MappingJackson2XmlHttpMessageConverter that will translate the Java String to a Json String, ending up with something like "{\"abc\":\"test\"}" instead of {"abc":"test"}
So, the real issue that every developer faces in this case is that MappingJackson2XmlHttpMessageConverter translates a Java String to a Json String, and in some cases, you might not need that, because the string contains valid json that needs to be returned without modifications. There are some configuration classes for this MessageConverter but I did'n went that road, because I need to return Strings like "raw" Json only in some specific endpoints (performance is the key driver). Here's an expample that resumes my "approach":
#RestController
#RequestMapping(value = "test", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class TestController {
#RequestMapping(method = RequestMethod.GET, value = "endpoint")
public JsonObject getSomeJson() {
return new JsonObject("{\"abc\":\"test\"}");
}
private static class JsonObject {
private String rawJsonValue;
JsonObject(String rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
#JsonValue #JsonRawValue
public String getRawJsonValue() {
return rawJsonValue;
}
}
}
#JsonValue and #JsonRawValue are Jackson annotations that tell MappingJackson2XmlHttpMessageConverter to treat the getRawJsonValue method result as the Json representation of JsonObject, without making any modification. The response of the endpoint will be {"abc":"test"}

Spring MVC: headers added in a RestController not returned

I have a simple Spring MVC RestController
#RestController
#RequestMapping(value = "/xxx")
public class MyResource {
One of the endpoint is meant to return a ZIP file so I have this:
#RequestMapping(value = "/xxx", method = RequestMethod.GET)
public void downloadZip(HttpServletResponse response) {
InputStream is = ...
IOUtils.copy(is, response.getOutputStream());
response.setContentType("application/zip");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, attachment; filename=myfile.zip);
response.flushBuffer();
}
The above is meant to return a ZIP and add two extra header for the browser to know it's something that trigger a download.
But this is not the case, when I check the Response headers with chrome network tools, the header are exactly the same as before the request hit the endpoint.
The content-type is still application/json and the content-disposition is not even there.
I have custom header called "app-version". I did a test an try amending the value to see if it actually gets changed
response.setHeader("app-version","hello");
But no, when I check the Response header the "app-version" is still what was before the call.
Do I have to enable anything to be able to modify header inside my controller ?
Try changing the return type from void to String
public String downloadZip(HttpServletResponse response) {
...
return ""
}
I think spring won't apply any change in header if the endpoint doesn't return anything.

How HttpServletRequest works

i'm trying to understand how HttpServletRequest works:
in a demo application I have this code in a .class file:
HttpServletRequest req = (HttpServletRequest)
FacesContext.getCurrentInstance().getExternalContext().getRequest();
String parameter = req.getHeader("PARAMETER");
is this a JSF implementation? Does it read a parameter from the session right?
How can I retrieve the same parameter without using JSF?
Yes, this snippet is part of a JSF application.
No it doesn't read a parameter from the session, since it calls getHeader() on a HttpServletRequest object. So it reads a request header (as the javadoc of HttpServletRequest.getHeader() explains).
To retrieve the same header in a simple servlet-based application, you would use the HttpServletRequest argument passed to every servlet method:
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
String header = request.getHeader("PARAMETER");
...
}
It looks like you're not familiar at all with servlets. Read an introductory tutorial to grasp the basics before dving into code.

Servlet doFilter setAttributes not available in Servlet

I'm trying to add an Attribute to the request via doFilter before passing it to the Servlet, so that a value in JSTL will be set. I simply do req.setAttribute("b", "blah") in the filter, but it doesn't seem to get set in the JSTL file. How would I do this?
It's hard to pinpoint the root cause without seeing the code. There are several possible causes.
You're sending a redirect after setting the attribute instead of continuing with the same request.
You're accessing the attribute with the wrong name (case sensitive!).
You're accessing the attribute the wrong way.
The attribute is been overridden somewhere further down in the request processing.
There's a page scoped attribute with the same name which has no value.
You're misinterpreting the results.
Etc.
By the way, there's no such thing as a "JSTL file". Perhaps you meant "JSP file".
The problem is that the doFilter method uses ServletRequest instead of HttpServletRequest which is the one that has the setAttribute method. Most of my filters are something like this:
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// do what you must...
chain.doFilter(servletRequest, servletResponse);
}

Resources