I'm new to Spring Cloud Gateway (spring boot 2.0.5.RELEASE). I try to read the request body from a web filter and the request is just stuck and cannot flow through the chain. Sample code:
#Component
public class TestFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(TestFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
try {
/* whenever I put the following line. The request cannot get through */
ByteBuffer byteBuffer = Mono.from(serverHttpRequest.getBody()).toFuture().get().asByteBuffer();
} catch (Exception ex) {
ex.printStackTrace();
}
return chain.filter(exchange);
}
}
If I remove the getBody() line, everything works fine. Any clue? Thx!
Have a look here: How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?
This remark explains it pretty accurate:
The biggest problem is that I find out that HttpServletRequest(get from NativeWebRequest) cannot read input stream(some parameters are in the request body) more than one time
I am trying to create a controller so that when a user goes to a non-existing URL, he/she will be mapped to a custom error page "error.jsp".
Currently, my Exception Handler Controller looks as followed:
#ControllerAdvice
public class ExceptionHandlerController {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlerController.class);
#ExceptionHandler(value = {Exception.class, RuntimeException.class})
public String defaultErrorHandler(Exception e) {
logger.error("Unhandled exception: ", e);
return "error";
}
#ExceptionHandler(NoHandlerFoundException.class)
public String handle(Exception e) {
logger.error("No handler found!", e);
return "error";
}
}
However, when I run my web app and visit a nonexisting URL, I get redirected to the default browser page saying '404 this page cannot be page.
Does anyone have any thoughts or suggestions as to why this is not working?
From the javaDoc of NoHandlerFoundException
By default when the DispatcherServlet can't find a handler for a
request it sends a 404 response. However if its property
"throwExceptionIfNoHandlerFound" is set to true this exception is
raised and may be handled with a configured HandlerExceptionResolver.
To solve this, You need to make sure you did these 2 things.
Creating SimpleMappingExceptionResolver and registering as a bean
#Bean
HandlerExceptionResolver customExceptionResolver () {
SimpleMappingExceptionResolver s = new SimpleMappingExceptionResolver();
Properties p = new Properties();
//mapping spring internal error NoHandlerFoundException to a view name.
p.setProperty(NoHandlerFoundException.class.getName(), "error-page");
s.setExceptionMappings(p);
//uncomment following line if we want to send code other than default 200
//s.addStatusCode("error-page", HttpStatus.NOT_FOUND.value());
//This resolver will be processed before default ones
s.setOrder(Ordered.HIGHEST_PRECEDENCE);
return s;
}
Set setThrowExceptionIfNoHandlerFound as true in your dispatcherServlet.
p
public class AppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
....
......
#Override
protected FrameworkServlet createDispatcherServlet (WebApplicationContext wac) {
DispatcherServlet ds = new DispatcherServlet(wac);
//setting this flag to true will throw NoHandlerFoundException instead of 404 page
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
}
Refer complete example here.
I'm using spring security in my spring boot app to provide user functionality. I've spent quiet some time searching for an answer to the problem, but did only find solutions for people using xml-based configurations.
My set-up is very similar to this: http://www.baeldung.com/spring-security-track-logged-in-users (alternative method at the bottom).
This is my SecurityConfiguration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().defaultSuccessUrl("/home.html", true)
//.and().exceptionHandling().accessDeniedPage("/home")
.and().authorizeRequests().antMatchers("/editor").hasAnyAuthority("SUPERUSER")
.and().authorizeRequests().antMatchers("/editor").hasAnyAuthority("ADMIN")
.and().authorizeRequests().antMatchers("/").permitAll().anyRequest().authenticated()
.and().formLogin().loginPage("/login").permitAll()
.and().authorizeRequests().antMatchers("/static/**").permitAll()
.and().logout().permitAll().logoutSuccessUrl("/login").logoutUrl("/logout").deleteCookies("JSESSIONID")
.and().csrf().disable();
http.sessionManagement().invalidSessionUrl("/login").maximumSessions(1).sessionRegistry(sessionRegistry()).expiredUrl("/login");
}
This is where i call the sessionRegistry:
public List<String> getAllLoggedUsernames() {
final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
// System.out.println("All Principals: " + sessionRegistry.getAllPrincipals());
List<String> allUsernames = new ArrayList<String>();
System.out.println(allUsernames.size());
for (final Object principal : allPrincipals) {
if (principal instanceof SecUserDetails) {
final SecUserDetails user = (SecUserDetails) principal;
//Make sure the session is not expired --------------------------------------------------▼
List<SessionInformation> activeUserSessions = sessionRegistry.getAllSessions(principal, false);
if (!activeUserSessions.isEmpty()) {
allUsernames.add(user.getUsername());
System.out.println(user.getUsername());
}
}
}
return allUsernames;
}
Now when I try to get the currently logged-in user i get it correctly like that:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
My sessionRegistry is defined as a Bean the following way:
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
It is left to say that I call the getAllLoggedUsernames() from a controller via a service much like this:
#Autowired
private SecUserDetailService service;
And later in a #RequestMapping function:
service.getAllLoggedUsernames();
And the list received there is always empty, no matter how many users are actually logged in.
Now my guess from other questions asked here would be that somehow my application gets loaded twice or that my bean setup is messed up. I kind of think that the #Autowired does not work, since I think the Service needs some kind of context information?
I'm really new to Dependency injection though, so it's kinda hard to get everything correct.
Thanks for any help in advance!
Edit - Minor clarifications
Solved: There was a type error, the if statement in the getAllLoggedUsers() method always resolved to false as the Object is not an instance of my own UserDetails class, but of org.springframework.security.core.userdetails.User!
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 have a Client-Server application using SpringBoot and Angular2.
I would like to load a image from the server by filename. This works fine.
I store the attribute image:string at the client and I place it in the template again.
You might pay attention to return res.url;; I do not use the actual ressource, which might be wrong.
My objective is that image is cached. To my understanding the web-browser can automatically cache the images. Correct?
But the caching does not work yet and maybe somebody could give me a hint what needs to be adjusted?
Is a different header required?
Server (SpringBoot)
public class ImageRestController {
#RequestMapping(value = "/getImage/{filename:.+}", method = RequestMethod.GET)
public ResponseEntity<Resource> getImage(#PathVariable String filename) {
try {
String path = Paths.get(ROOT, filename).toString();
Resource loader = resourceLoader.getResource("file:" + path);
return new ResponseEntity<Resource>(loader, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<Resource>(HttpStatus.NOT_FOUND);
}
}
}
Client (Angular2)
#Component({
selector: 'my-image',
template: `
<img src="{{image}}"/>
`
})
export class MyComponent {
image:string;
constructor(private service:MyService) {}
showImage(filename:string) {
this.service.getImage(filename)
.subscribe((file) => {
this.image = file;
});
}
}
export class MyService() {
getImage(filename:String):Observable<any> {
return this.http.get(imagesUrl + "getImage/" + filename)
.map(this.extractUrl)
.catch(this.handleError);
}
extractUrl(res:Response):string {
return res.url;
}
}
You could do something like this on the server side (and perhaps add an ETag or Last-Modified header if you can get that information):
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.body(loader);
See the HTTP caching part of the reference documentation in Spring.
If you're just serving resources and not applying any additional logic, then you'd better do the following:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/getImage/**")
.addResourceLocations("classpath:/path/to/root/")
.setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS).cachePublic());
}
}
See the other relevant part of the reference documentation. You can also apply transformations and leverage cache busting (see this section as well).