A Spring MVC controller needs to conditionally either:
1.) forward the user to a very different URI, or
2.) return the preceding view so that the user can re-enter data in the same form
My research indicates that the redirect should be done by giving the controller method a return type of ResponseEntity. How can I convert the HttpHeaders from the HttpServletResponse that was passed into the controller method into the new ResponseEntity that gets created in the controller?
There is no HttpServletResponse.getHeaders() method, and there must be a more elegant method than (in pseudocode):
HttpHeaders responseHeaders = new HttpHeaders();
for(String headerName : HttpServletResponse.getHeaderNames()) {
responseHeaders.add(httpServletResponse.getHeader(headerName));
or
responseHeaders.add(httpServletResponse.getHeaders(headerName));.
}
add responseHeaders to new ResponseEntity;
This is confounded a bit by the additional requirement that the controller should alternatively be able to return the previous FreeMarker view from which the user called the controller, if the user did not provide the correct information in the preceding FreeMarker view's form.
Here is the code I have do far. What specific changes need to be made to it?
#RequestMapping("/handle")
public ResponseEntity<?> handle( HttpServletRequest req, HttpServletResponse resp) {
HttpHeaders responseHeaders = resp.getHeaders();//THIS IS NOT IN THE API
responseHeaders.set("MyResponseHeader", "MyValue");
boolean locationRedirect = true;
// Add some logic to determine whether to 1.) forward the user or 2.) return the previous view so they can re-enter information
if(locationRedirect){
try {
URI location = new URI("some_uri_to_send_user_to_if_they_entered_correct_value_in_form");
responseHeaders.setLocation(location);
} catch (URISyntaxException e) {e.printStackTrace();}
return new ResponseEntity<Void>(responseHeaders, HttpStatus.CREATED);
} else{// send the user back to the freeMarker view that would otherwise be at 'return "viewName";'
return new ResponseEntity<String>("viewName", responseHeaders, HttpStatus.CREATED);
}
}
Related
When trying to get an access token using OAuth2RequestTemplate the call sends the below header in a request
"Content-Type", "application/x-www-form-urlencoded;charset=UTF-8ā
Iād like to drop the charset to not be included there.
I tried to set header value manually through AccessTokenRequest object and a CustomTokenProvider, but it did not work.
Any idea why it is actually including it there and how to get rid of it.
UPDATE : Including the code sample
OPTION 1 :
String oauthServerUri = "..../access_token";
ClientCredentialsResourceDetails ccDetails = new ClientCredentialsResourceDetails();
ccDetails.setClientId("clientId");
ccDetails.setClientSecret("clientSecret");
ccDetails.setGrantType("client_credentials");
ccDetails.setAccessTokenUri(oauthServerUri);
AccessTokenRequest tokenRequest = new DefaultAccessTokenRequest();
Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-Type", Arrays.asList("Some Proper Value"));
tokenRequest.setHeaders(headers);
OAuth2ClientContext context = new DefaultOAuth2ClientContext(tokenRequest);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(ccDetails, context);
OAuth2AccessToken accessToken = restTemplate.getAccessToken();
OPTION 2 :
As an alternative I have tried the approach described here :
How to set HTTP Header for OAuth2RestTemplate
Which is implementing AccessTokenProvider and setting headers in obtainAccessToken. But this did not help either.
When you make the constructor new OAuth2RestTemplate(ccDetails, context); behind it makes a super() which makes a RestTemplate because it extends from it.
public OAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
super();
if (resource == null) {
throw new IllegalArgumentException("An OAuth2 resource must be supplied.");
}
this.resource = resource;
this.context = context;
setErrorHandler(new OAuth2ErrorHandler(resource));
}
The RestTemplate constructor puts messageConverters by default.
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
......
Maybe a solution could be that once created the object OAuth2RestTemplate you make a restTemplate.setMessageConverters(messageConverters) with the MediaType that interests you, from this method (inside RestTemplate class) deletes the previous ones:
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
// Take getMessageConverters() List as-is when passed in here
if (this.messageConverters != messageConverters) {
this.messageConverters.clear();
this.messageConverters.addAll(messageConverters);
}
}
EDIT:
If you see the image above, you can see when you do:
restTemplate.getAccessToken();
call to
getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap());
protected RequestCallback getRequestCallback(OAuth2ProtectedResourceDetails resource,
MultiValueMap<String, String> form, HttpHeaders headers) {
return new OAuth2AuthTokenCallback(form, headers);
}
and look what his constructor does:
/**
* Request callback implementation that writes the given object to the request stream.
*/
private class OAuth2AuthTokenCallback implements RequestCallback {
private final MultiValueMap<String, String> form;
private final HttpHeaders headers;
private OAuth2AuthTokenCallback(MultiValueMap<String, String> form, HttpHeaders headers) {
this.form = form;
this.headers = headers;
}
public void doWithRequest(ClientHttpRequest request) throws IOException {
request.getHeaders().putAll(this.headers);
request.getHeaders().setAccept(
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED));
FORM_MESSAGE_CONVERTER.write(this.form, MediaType.APPLICATION_FORM_URLENCODED, request);
}
}
Maybe through inheritance and overwriting methods, you can pass on to that builder the headers that interest you.
Also in OAuth2AccessTokenSupport the method retrieveToken has interesting comments:
// Prepare headers and form before going into rest template call in case the URI is affected by the result
authenticationHandler.authenticateTokenRequest(resource, form, headers);
// Opportunity to customize form and headers
tokenRequestEnhancer.enhance(request, resource, form, headers);
I hope I helped you.
You could use a interceptor to wrap your request, see ClientHttpRequestInterceptor#intercept:
intercept
ClientHttpResponse intercept(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution)
throws IOException
Intercept the given request, and return a response. The given ClientHttpRequestExecution allows the interceptor to pass on the request and response to the next entity in the chain.
A typical implementation of this method would follow the following pattern:
Examine the request and body
Optionally wrap the request to filter HTTP attributes.
Optionally modify the body of the request.
Either
execute the request using ClientHttpRequestExecution.execute(org.springframework.http.HttpRequest, byte[]),
or
do not execute the request to block the execution altogether.
Optionally wrap the response to filter HTTP attributes.
Your modified code:
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(ccDetails, context);
restTemplate.setInterceptors(Arrays.asList(new new RestTemplateHeaderModifierInterceptor()));
OAuth2AccessToken accessToken = restTemplate.getAccessToken();
with
public class RestTemplateHeaderModifierInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpRequest requestWrapper = new CustomHttpRequestWrapper(request);
return execution.execute(requestWrapper, body);
}
}
public class CustomHttpRequestWrapper extends HttpRequestWrapper {
public CustomHttpRequestWrapper(HttpRequest request) {
super(request)
}
#Override
public HttpHeaders getHeaders() {
// return all headers, but change the charset
}
}
On the same controller handler method, I would like to either:
return a JSP view when the request is valid
set the appropriate HTTP status code and just write a simple String message to the Response body if the request is not valid
Is this possible with Spring MVC?
Yes possible.
#RequestMapping(value = "/show", method = RequestMethod.GET)
public String show(ModelMap model,
HttpServletRequest request,
HttpServletResponse response) {
if (isValidAsYouWant(request)) { // check validity
// setup reference data
return "viewName";
} else {
response.setStatus(400);
try {
response.getWriter().write("Invalid Request");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
I have a Spring Boot application that uses Spring MVC in the usual manner, with a bunch of #RequestMapping methods, Freemarker definitions, and the like. This is all tied together with a WebMvcConfigurerAdapter class.
I'd like to provide a service where the user submits a list of valid URLs, and the webapp would work out which controller would be called, passes in the parameters, and returns a combined result for every URL ā all in one request.
This would save the user from having to make hundreds of HTTP calls, but would still allow them to make one-off requests if need be. Ideally, I'd just inject an auto-configured Spring bean, so I don't have to repeat the URL resolving and adapting and handling that Spring does internally, and the controller's list of other controllers would never go out of sync with the real list of controllers.
I expected to write something like this (simplified to only deal with one URL, which is pointless but easier to understand):
#Autowired BeanThatSolvesAllMyProblems allMappings;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputPath) {
if (allMappings.hasMappingForPath(inputPath)) {
return allMappings.getMapping(inputPath).execute();
} else {
return "URL didn't match, sorry";
}
}
Instead, I've had to define Spring beans I don't know what they do and have been repeating some of what Spring is meant to do for me, which I'm worried won't work quite the same as it would if the user just made the call themselves:
// these two are #Beans, with just their default constructor called.
#Autowired RequestMappingHandlerMapping handlers;
#Autowired RequestMappingHandlerAdapter adapter;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputText) {
final HttpServletRequest mockRequest = new MockHttpServletRequest(null, inputText);
final StringBuilder result = new StringBuilder();
this.handlers.getHandlerMethods().forEach((requestMappingInfo, handlerMethod) -> {
if (requestMappingInfo.getPatternsCondition().getMatchingCondition(mockRequest) != null) {
try {
final MockHttpServletResponse mockResponse = new MockHttpServletResponse();
result.append("Result: ").append(adapter.handle(mockRequest, mockResponse, handlerMethod));
result.append(", ").append(mockResponse.getContentAsString());
result.append("\n");
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
});
return result.toString();
}
I thought I was doing quite well going down this path, but it's failing with Missing URI template variable errors, and not only do I have no idea how to put the request parameters in (another thing which Spring could be able to handle itself), but I'm not even sure that this is the right way to go about doing this. So how do I simulate a Spring MVC request "reflectively", from within the webapp itself?
JSON API spec. solves this problem by allowing sending multiple operations per request. There even exists a quite mature implementation that supports this feature which is called Elide. But I guess this is might not fully meet your requirements.
Anyway, here's what you can do.
You have to take into consideration that DispatcherServlet holds handlerMappings list that is used to detect appropriate request handler and handlerAdaptors. The selection strategy for both lists is configurable (see DispatcherServlet#initHandlerMappings and #initHandlerAdapters).
You should work out a way you would prefer to retrieve this lists of handlerMappings/initHandlerAdapters and stay in sync with DispatcherServlet.
After that you can implement your own HandlerMapping/HandlerAdaptor (or present a Controller method as in your example) that would handle the request to /encode path.
Btw, HandlerMapping as javadoc says is
Interface to be implemented by objects that define a mapping between
requests and handler objects
or simply saying if we take DefaultAnnotationHandlerMapping that would map our HttpServletRequests to #Controller methods annotated with #RequestMapping. Having this mapping HandlerAdapter prepares incoming request to consuming controller method, f.ex. extracting request params, body and using them to call controller's method.
Having this, you can extract URLs from main request, create a list of stub HttpRequests holding the information needed for further processing and loop through them calling this:
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
having a handlerMapping you call
HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
and then you can finally call
ha.handle(processedRequest, response, mappedHandler.getHandler());
which in turn would execute the controller's method with params.
But having all this, I would not recommend to following this approach, instead, think about usage of JSON API spec or any other.
How about using Springs RestTemplate as client for this? You could call your controllers within the spring controller as if it would be an external resource:
#ResponseBody
public List<String> encode(#RequestBody List inputPaths) {
List<String> response = new ArrayList<>(inputPaths.size());
for (Object inputPathObj : inputPaths) {
String inputPath = (String) inputPathObj;
try {
RequestEntity.BodyBuilder requestBodyBuilder = RequestEntity.method(HttpMethod.GET, new URI(inputPath)); // change to appropriate HttpMethod, maybe some mapping?
// add headers and stuff....
final RequestEntity<Void> requestEntity = requestBodyBuilder.build(); // when you have a request body change Void to e.g. String
ResponseEntity<String> responseEntity = null;
try {
responseEntity = restTemplate.exchange(requestEntity, String.class);
} catch (final HttpClientErrorException ex) {
// add your exception handling here, e.g.
responseEntity = new ResponseEntity<>(ex.getResponseHeaders(), ex.getStatusCode());
throw ex;
} finally {
response.add(responseEntity.getBody());
}
} catch (URISyntaxException e) {
// exception handling here
}
}
return response;
}
Note that generic do not work for the #RequestBody inputPaths.
See alse http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html and https://spring.io/guides/gs/consuming-rest/ .
I agree with the other answers that you should consider this feature outside of your project, instead of having it in the code. It is a question of design and you can choose the approach you want. Based on your comment that these are GET requests, you can achieve what you want with a request dispatcher to trigger your requests within your special Controller service method for each URL and capture the response with a HttpServletResponseWrapper instance.
In the following code sample, the "consolidate" method takes comma separated URLs like this ("http://localhost:8080/index/index1,index2", here "index1,index2" is the URL list), consolidates their text output into a single payload and returns it. For this example URL, the consolidated outputs of http://localhost:8080/index1 and http://localhost:8080/index2 will be returned. You might want to extend/modify this with added parameters, validation, etc for the URLs. I tested this code with Spring Boot 1.2.x.
#Controller
public class MyController {
#RequestMapping("/index/{urls}")
#ResponseBody
String consolidate(#PathVariable String[] urls, HttpServletRequest request, HttpServletResponse response) {
StringBuilder responseBody = new StringBuilder();
//iterate for each URL provided
for (String url : urls) {
RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher("/" + url);
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response) {
private CharArrayWriter output = new CharArrayWriter();
#Override
public PrintWriter getWriter() {
return new PrintWriter(output);
}
#Override
public String toString() {
return output.toString();
}
};
try {
dispatcher.include(request, wrapper);
//append the response text
responseBody.append(wrapper.toString());
} catch (ServletException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//This holds the consolidated output
return responseBody.toString();
}
#RequestMapping("/index1")
String index1() {
return "index1";
}
#RequestMapping("/index2")
String index2() {
return "index2";
}
}
I'm new in Spring and also in JSP. I'm working in the project and I needed to create a page where application will be redirected in case of specific exceptions.
I have service's method which throws one of exceptions. This method is called in one of our page controller with #RequestMapping annotation. So to redirect to specific error page, I created two methods with #ExceptionHanlder which handle this exceptions in this controller. How it looks:
#ExceptionHandler(IllegalStateException.class)
public ModelAndView handleIllegalStateException (IllegalStateException ex) {
ModelAndView modelAndView = new ModelAndView("redirect:/error");
modelAndView.addObject("exceptionMsg", ex.getMessage());
return modelAndView;
}
But there wasn't enough. I also need to create ErrorPageController:
#Controller
#RequestMapping("/error")
public class ErrorPageController {
#RequestMapping(method = RequestMethod.GET)
public ModelAndView displayErrorPage() {
return new ModelAndView("error");
}
}
And now works displaying error page. But my problem is, that I can't display error message in JSP...
I have:
<h3>Error page: "${exceptionMsg}"</h3>
But I don't see a message ;/ Instead of it, I see message in URL:
localhost/error?exceptionMsg=Cannot+change+participation+status+if+the+event+is+cancelled+or+it+has+ended.
And it's wrong because in URL I want to have only an "localhost/error" and nothing more. This message I want to display in JSP.
To fix both of your issues (show the message, and have the proper url) you should in original code change you exception handler method to e.g.
#ExceptionHandler(IllegalStateException.class)
public RedirectView handleIllegalStateException(IllegalStateException ex, HttpServletRequest request) {
RedirectView rw = new RedirectView("/error");
FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
if (outputFlashMap != null) {
outputFlashMap.put("exceptionMsg", ex.getMessage());
}
return rw;
}
Why? If you want your attributes to persist through redirect, you need to add them to flash scope. The code above uses the FlashMap, from the docs
A FlashMap is saved before the redirect (typically in the session) and
is made available after the redirect and removed immediately.
If it were to be a normal controller method, you could have simply added RedirectAttributes as an argument, but on #ExceptionHandler methods, the arguments of RedirectAttributes are not resolved, so you need to add the HttpServletRequest and use the RedirectView.
You have to change ModelAndView to:
#ExceptionHandler(IllegalStateException.class)
public ModelAndView handleIllegalStateException (IllegalStateException ex) {
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("exceptionMsg", ex.getMessage());
return modelAndView;
}
And have this part in error.jsp:
<h3>Error page: "${exceptionMsg}"</h3>
I am afraid to ask a strange question but I want to change "pathInfo" of HttpServletRequest at a handler method of a Controller. Please take look at below.
I know I can get "pathInfo" by using getPathInfo(). However. I don't know how to set up the pathInfo. Is it possible ? Any help will be appreciated
#RequestMapping(value = "show1" method = RequestMethod.GET)
public String show1(Model model, HttpServletRequest request) {
// I want to set up "PathInfo" but this kind of methods are not provided
//request.setPathInfo("/show2");
// I thought that BeanUtils.copy may be available.. but no ideas.
// I have to call show2() with the same request object
return show2(model, request);
}
// I am not allowed to edit this method
private String show2(Model model, HttpServletRequest request) {
// I hope to display "http://localhost:8080/contextroot/show2"
System.out.println(request.getRequestURL());
return "complete";
}
You can't set these values.
The only option is to create a wrapper for your request, something like this:
return show2(model, new HttpServletRequestWrapper(request) {
public StringBuffer getRequestURL() {
return new StringBuffer(
super.getRequestURL().toString().replaceFirst("/show1$", "/show2"));
}
});
Path Info is set by the browser (client) when it requests a certain URL.