Spring Boot - determining a sub-domain (with wildcard)? - java

New to Spring Boot here. Spring MVC provides the #SubdomainMapping annotation, which does not seem to be available from what I can see in Spring Boot. I've seen a few people discuss using a filter to handle this. Or other approaches that seem overly convoluted.
Would there not be a (simple/cleaner) way to handle all sub-domains within a standard controller such as:
#SubdomainMapping(value = {"**"}
public String data(ModelMap modelMap, HttpServletRequest request) {
//Code to handles subdomain logic here ....
}
This would be a simple approach where all values are treated equally with minor differences.
Any suggestions would be helpful!

I have worked on this myself and I have an answer that isn't as simple as you wanted, but I don't think there is one that simple.
So, you can create a handler interceptor adapter that will grab every request before it gets to your controller and grabs and processes the subdomain. This would require something like this:
#Component
public class SubDomainInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model)
throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
String mysub = request.getRequestURL().toString();
//...
// Do whatever you need to do with the subdomain
//
if (isGoodSubdomain){
session.sendAttribute("subdomain", mysub);
} else {
response.sendRedirect("http://www.basesite.com"):
}
return true;
}
You then use that session variable in your controllers to filter values or whatever you need to use them for. I know this isn't the simple answer you wanted, but it was the best one that I have found so far.

Related

Spring Boot Request Header Validation

I am using Spring Boot 2.3.8 for creating rest services. I have a requirement to validate the request headers such that it should have certain headers otherwise throw error. This should be common for all the methods or services. I tried below,
public ResponseEntity<Object> addEmployee(
#RequestHeader(name = "header1", required = true) String header1,
#RequestHeader(name = "header2", required = true) String header2,
#RequestBody Employee employee)
throws Exception
{
But I need to add this for all the methods in all the controllers. If this is the case how can I throw an error like "Header1 missing in request headers" / "header2 missing in request headers" for all the services globally ?
For global use you can register an interceptor.
#Component
public class MyHandlerInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3) throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model) throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//here check headers contained in request object
if (request.getHeader("header1") == null || request.getHeader("header2") == null) {
response.getWriter().write("something");
response.setStatus(someErrorCode);
return false;
}
return true;
}
And then register it
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Autowired
private MyHandlerInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
This is what filters are for. You want to filter out requests based on a header being there or not and return an error if its missing.
Extend OncePerRequestFilter optionally override shouldNotFilter if you do not want it to be used for all request OR you could implement a normal Filter and use FilterRegistrationBean to register it only for a specific path.
then in the filter you could throw an exception and let Spring figure out how to display that, or actually set the response to something meaningful.
If you have Zuul in place then you can read the request header attributes there in pre route and on validation failure reply back to the request with error populated.
This is a cross-cutting concern and should be done using AOP. Since you're using Spring, you can do the following:
Create an annotation called ValidateHeaders:
public #interface ValidateHeaders {}
Create a #Before advice that intercepts methods annotated with #ValidateHeaders:
#Before("#annotation(com.somepackage.ValidateHeaders)")
public void controllerProxy(JoinPoint jp) {
Object[] args = jp.getArgs();
//validation logic, throw exception if validation fails
}
Note that you'll have to extract the fields using reflection as:
Annotation[][] pa = ms.getMethod().getParameterAnnotations();
You can iterate through all the annotations and search for request headers as:
if(annotations[i][j].annotationType().getName().contains("RequestHeader")) {
RequestHeader requestHeader = (RequestHeader) m.getParameterAnnotations()[i][j];
//now access fields in requestHeader to do the validation
}
Here's an article that can get you started with Advice types:
https://www.baeldung.com/spring-aop-advice-tutorial

why is it necessary to implement a provider that throws exception when you manually seed a value?

While using google guice I came across this documentation of how to manually seed values in request scope.
[https://github.com/google/guice/wiki/ServletModule#dispatch-order]
You can implement a custom filter to seed values to be injected later e.g.
protected Filter createUserIdScopingFilter() {
return new Filter() {
#Override public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// ...you'd probably want more sanity checking here
Integer userId = Integer.valueOf(httpRequest.getParameter("user-id"));
httpRequest.setAttribute(
Key.get(Integer.class, Names.named("user-id")).toString(),
userId);
chain.doFilter(request, response);
}
#Override public void init(FilterConfig filterConfig) throws ServletException { }
#Override public void destroy() { }
};
}
In this documentation they are explaining the binding as
And the binding might look like this:
public class YourServletModule extends ServletModule {
#Override protected void configureServlets() {
.....
filter("/process-user*").through(createUserIdScopingFilter());
}
#Provides #Named("user-id") #RequestScoped Integer provideUserId() {
throw new IllegalStateException("user id must be manually seeded");
}
}
I would like to understand why is it necessary to implement a provides method that throws an exception ? What is its purpose ?
Scopes in Guice are implemented by taking a Provider and wrapping it in a new Provider: https://google.github.io/guice/api-docs/4.1/javadoc/com/google/inject/Scope.html#scope-com.google.inject.Key-com.google.inject.Provider-
There must be some initial provider to wrap, even if that one doesn't do anything useful.
In fact, if you omit the provider from the module, Guice will find dependencies on #Named("user-id") Integer, but no provider for it, and fail to even create the injector. It needs to be able to wire up every dependency to a provider ahead of time.

Spring MVC InterceptorHandler called twice with DeferredResult

When I am using custom HandlerInterceptor and my controller returns DeferredResult, the preHandle method of my custom interceptor called twice on each request. Consider a toy example.
My custom interceptor:
public class MyInterceptor implements HandlerInterceptor {
static int i = 0;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(i++);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
My Spring Java configuration:
#Configuration
#EnableWebMvc
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
My Controller:
#Controller
public class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public DeferredResult<String> test() {
DeferredResult<String> df = new DeferredResult<String>();
df.setResult("blank");
return df;
}
}
So, on each page load I see two outputs from preHandle method. However, if I modify MyController in order to return just "blank" template (instead of DeferredResult with "blank" template), I see just one output from preHandle on each page load.
So, my question is why preHandle called twice when I use DeferredResult and is it possible to avoid this?
You need to use org.springframework.web.servlet.AsyncHandlerInterceptor:
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
}
Spring MVC execute sequence:
preHandle
afterConcurrentHandlingStarted
preHandle
postHandle
afterCompletion
The difference between the two invocations can be seen by examining the value of request.getDispatcherType().
I followed what #thunder mentioned by checking the value of request.getDispatcherType() and this worked for me.
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// NOTE: For all dispatchers that are not "REQUEST" like "ERROR", do
// an early return which prevents the preHandle function from
// running multiple times
if (request.getDispatcherType() != DispatcherType.REQUEST) {
return true;
}
// ... do other stuff and then do a final return
return true;
}
}
As I'm exploring adding filter and Interceptor, I'm prettry sure that's caused by async calling. You use a DeferredResult here, which spring will do the filter in origin thread and filtering it again in a new thread. If you set log level to Debug, you will notice log like this.
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#efe6068
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#163cab73
15:14:07.148 [http-nio-8199-exec-6] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Bound request context to thread: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ org.apache.catalina.core.ApplicationHttpRequest#42f1262]]
In a word, it's executed once in one thread, but here're two threads.
As I digged the google, I found there's no good solution.
If you have something like auth in request, a walk around way is add
security.filter-dispatcher-types=REQUEST, ERROR
Then the new thread(async one) will not get the security context. You need to check it and stop the filter chain inside it.
Or you just use the tranditional sync call like:
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return "blank";
}
I found another helpful answer. https://jira.spring.io/browse/SPR-12608
Hope it helps!
Inside preHandle method, If you have response.sendError(<>, <>), then this preHandle method will execute twice for each API request. So remove sendError() method to execute this only once.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.sendError(401, "Token is invalid");
return false;
}
The prehandle method of the AsyncHandlerInterceptor will always be executed twice in async processing.

How are Spring HandlerInterceptors instantiated?

Is there a new instance of Spring HandlerInterceptors for each request?
I have an interceptor in Spring, which has a class field.
public class MyInterceptor extends HandlerInterceptorAdapter {
Private boolean state = false;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
state = true;
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
if (state == true) {
System.out.println("Success");
}
}
If this interceptor is used will it always print "Success"? (No matter how many threads are doing this at the same time?)
How the interceptor is instantiated depends how you configiure it as a bean. If you don't explicitly specify a scope for the bean, then it'll be a singleton like any other bean, and so the state field will be shared by all requests.
In this sense, interceptors are no different to controllers - be very careful about putting conversation state in them, since the objects will be shared across requests.
if you really need a stateful interceptor and you don't want to share the state between requests, then use a request-scoped bean.

Spring MVC - Request mapping, two urls with two different parameters

Is it possible in Spring to have one method with two different urls with different params for each method?
Below is pseudo code
#RequestMethod(URL1-param1, URL2-param2)
public void handleAction(#ModelAttribute("A") A a, ...) {
}
At the same time ULR1 is mapped in some other Controller as
#RequestMethod(URL1)
public void handleAction1(#ModelAttribute("A") A a, ...) {
}
Update: It appears your question is completely different.
No, you can't have the same url with different parameters in different controllers. And it doesn't make much sense - the url specifies a resource or action, and it cannot be named exactly the same way in two controllers (which denote different behaviours).
You have two options:
use different URLs
use one method in a misc controller that dispatches to the different controllers (which are injected) depending on the request param.
Original answer:
No. But you can have two methods that do the same thing:
#RequestMethod("/foo")
public void foo(#ModelAttribute("A") A a) {
foobar(a, null);
}
#RequestMethod("/bar")
public void bar(#ModelAttribute("B") B b) {
foobar(null, b);
}
If I haven't understood correctly, and you want the same ModelAttribute, then simply:
#RequestMapping(value={"/foo", "/bar"})
And finally - if you need different request parameters, you can use #RequestParam(required=false) to list all possible params.
you can supply multiple mappings for your handler like this
#RequestMapping(value={"", "/", "welcome"})
public void handleAction(#ModelAttribute("A") A a, ...) { }
But if you want to use different parameters for each mapping, then you have to extract your method.
Something like this
#RequestMapping(value={"URL1"}, method=RequestMethod.POST)
public String handleSubmit(#ModelAttribute("A") A command, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
return helperSubmit();
}
#RequestMapping(value={"URL2"}, method=RequestMethod.POST)
public String handleSubmit(#ModelAttribute("A") A command, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
return helperSubmit();
}
private helperSubmit() {
return "redirect:" + someUrl;
}

Categories

Resources