I want to hide the Apache Camel Servlet behind Spring MVC Controller entry point, because I have some proprietary components I have to use that depend on Sping Boot and Spring MVC Controller.
I have the following Apache Camel Route, which is working fine
<route id="Route">
<from uri="servlet:messages?httpMethodRestrict=POST"/>
<process ref="..."></process>
<to uri="{{storage.service.endpoint}}?bridgeEndpoint=true"/>
</route>
I have registered the Camel Servlet without any URL mappings, because I don't want to be accessible directly. The Spring Bean:
#Bean
ServletRegistrationBean servletRegistrationBean() {
CamelHttpTransportServlet camelServlet = new CamelHttpTransportServlet();
ServletRegistrationBean servletBean = new ServletRegistrationBean(camelServlet, false, new String[]{});
servletBean.setName("CamelServlet");
return servletBean;
}
In Spring Controller Entry point I just need to forward to Camel Servlet:
#RequestMapping(method=RequestMethod.POST, value="/api/v1/*")
public void wrapper(HttpServletRequest request, HttpServletResponse response) throws Exception{
context.getNamedDispatcher("CamelServlet").forward(request, response);
}
The problem is that Camel Servlet relies on ServletResolveConsumerStrategy using request.getPathInfo(), which is always null in Spring Controller entry point. I have tried different paths in #RequestMapping, but always request.getServletPath() has the full path and the pathInfo is null
P.S. The application is running on Tomcat 8.
Modify the Spring Controller entry point using path variables and set the value of the path variable a pathInfo() in a custom HttpServletRequestWrapper:
#RequestMapping(path="/api/v1/{messages}" , method=RequestMethod.POST)
public void wrapper2(HttpServletRequest request, HttpServletResponse response, #PathVariable String messages) throws Exception{
request = new PathInfoRequestWrapper(request, messages);
context.getNamedDispatcher("CamelServlet").forward(request, response);
}
class PathInfoRequestWrapper extends HttpServletRequestWrapper{
private String pathInfo;
public PathInfoRequestWrapper(HttpServletRequest request, String pathInfo) {
super(request);
this.pathInfo = pathInfo;
}
#Override
public String getPathInfo(){
String origPathInfo = super.getPathInfo();
if(origPathInfo == null || origPathInfo.equals("")){
return this.pathInfo;
}else{
return origPathInfo;
}
}
}
One solution is to create custom ServletResolveConsumerStrategy, which relies only on request.getServletPath(), to find the appropriate route. Unfortunately only setting the Strategy with org.apache.camel.http.common.CamelServlet.setServletResolveConsumerStrategy(ServletResolveConsumerStrategy) does not work, because the it is overwritten in org.apache.camel.component.servlet.CamelHttpTransportServlet.init method. SO you need to override the init in the Camel Servlet as well:
CamelHttpTransportServlet camelServlet = new CamelHttpTransportServlet(){
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ServletResolveConsumerStrategy servletResolveConsumerStrategy = new CamelSpringResolveConsumerStrategy();
setServletResolveConsumerStrategy(servletResolveConsumerStrategy );
}
};
Related
I am facing weird problem. I am developing web application based on Spring Boot with WebSockets. I need to track anonymous web socket connections, therefore I am using custom Principal implementation which I am creating in custom DefaultHandshakeHandler -> determineUser method. To create such Principal object, I need some data from httpsession, therefore I am using HttpSessionHandshakeInterceptor to fill Map<String, Object> attributes attribute in mentioned method.
Everything works like a charm, until I switch application packaging from JAR (using embeded Tomcat) to WAR and deploy it on standalone Tomcat. Suddenly, attribute attributes is empty as if WS connection is using different session than HTTP connection and therefore HttpSessionHandshakeInterceptor will not pass required attributes. Any ideas, why it is behaving differently?
A few parts of the code:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/ws");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("ws-endpoint")
.setHandshakeHandler(new AnonymousHandshakeHandler())
.addInterceptors(new HttpSessionHandshakeInterceptor())
.withSockJS();
}
}
Custom handshake handler:
public class AnonymousHandshakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
Principal principal = request.getPrincipal();
if (principal == null) {
SessionData sd = (SessionData) attributes.get(AppVariables.MODEL_PARAM_SESSION);
if (sd != null){
principal = new AnonymousPrincipal();
((AnonymousPrincipal) principal).setName(sd...);
}
}
return principal;
}
}
UPDATE:
Using custom handshake interceptor I can see that WS connections have different session IDs. That explains empty attributes, but why? Why requests on embeded Tomcat are using the same session but on standalone Tomcat they are creating new session on every request?
Handshake interceptor:
public class HttpHandshakeInterceptor implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
System.out.println(session.getId());
attributes.put(AppVariables.MODEL_PARAM_SESSION, session.getAttribute(AppVariables.MODEL_PARAM_SESSION));
}
return true;
}
#Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
}
}
I resolved my problem. It consisted of two problems + my stupidity:
I am using <CookieProcessor sameSiteCookies="none" /> and I forgot about it. That is why every request was generating new session (sameSite without https was not creating JSESSIONID cookie).
My proxy was not set up well. That is why my AnonymousHandshakeHandler was not used. See: link
How to make jersey and #webservlet working together ?
jersey ResourceConfig:
#ApplicationPath("/*")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
register(Greetings.class);
}
}
jersey Resource registered in resourceConfig:
#Path("/login")
public class Greetings {
#GET
public Response getHelloGreeting(#Context HttpServletRequest httpRequest) {
System.out.println("In the Greetings resource");
String url= "http://"+httpRequest.getServerName()+":"+httpRequest.getServerPort()+httpRequest.getContextPath();
String newURL = url+"/login.jsp";
System.out.println(newURL);
return Response.seeOther(URI.create(newURL)).build();
}
}
web servlet
#WebServlet(name = "LoginServlet", urlPatterns = { "/hello" })
public class LoginServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext servletContext = getServletContext();
System.out.println("inside login servlet");
request.getRequestDispatcher("/login.jsp").forward(request, response);
System.out.println("request forwarded");
}
//other functions not important so deleted
}
Case 1: on accessing this
http://localhost:8088/ResponseFilterweb/login
console logs:
In the Greetings resource
http://localhost:8088/ResponseFilterweb/login.jsp (no ui comes)
on accessing this
http://localhost:8088/ResponseFilterweb/hello
(nothing happens 404 error)
Case 2: Changing application config resource path:
#ApplicationPath("/auth")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
register(Greetings.class);
}
}
on accessing this
http://localhost:8088/ResponseFilterweb/auth/login
In the Greetings resource
http://localhost:8088/ResponseFilterweb/login.jsp (Ui comes)
on accessing this
http://localhost:8088/ResponseFilterweb/hello
inside login servlet (Ui comes)
userid is
Encoded string
request forwarded
doubts:
don't know why login.jsp is blocked in the first case:
why http://localhost:8088/ResponseFilterweb/login not showing any ui .. i think it should come ?
why http://localhost:8088/ResponseFilterweb/hello not showing any ui ?
If you were using web.xml, this or this would be your solution, but since you're not, this might be your only option. The problem is that when you use /* as the servlet mapping for Jersey, it hogs up all the requests. So the request to /hello would go to Jersey and not the LoginServlet. What those solutions I linked to do is cause Jersey to forward the request if it can't find it within the Jersey application. The other solution is to just change the servlet mapping to something like /api/* (which is pretty common), then you would just prefix your API requests with /api.
Using asterisk (*) won't work using #ApplicationPath
If you use /*, then you're making it too greedy and saying it should match everything all the time, and the default servlet will never be invoked
Use #ApplicationPath("/") instead
If you use /, then you're replacing the container's default servlet
I'm in a Spring project, and I need of filters.
Now I have already read about "Interceptor Vs Filter" and for now I choose filter.
So I have this class as filter
public class LogFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String ipAddress = request.getRemoteAddr();
this.log.info("IP " + ipAddress + ", Time " + new Date().toString());
chain.doFilter(req, res);
}
public void init(FilterConfig config) throws ServletException {
// Get init parameter
String testParam = config.getInitParameter("test-param");
this.log.info("Test Param:" + testParam);
}
public void destroy() {
// Add code to release any resource
}
}
And this method for registration of filter's bean in java config ( no xml configuration )
#Bean
public LogFilter filter() {
LogFilter filter = new LogFilter();
this.beanFactory.autowireBean(filter);
return filter;
}
Now this filter works for every url of my app, how can I choose which url should be "under filter" ?
EDIT
I solved this in this way
#Bean
public FilterRegistrationBean regFilter() {
FilterRegistrationBean regFilter = new FilterRegistrationBean();
regFilter.setFilter(new LogFilter());
regFilter.addUrlPatterns("/test");
return regFilter;
}
Thanks to the hint in comment, I found this
If you're using annotation to configure your web #WebFilter to annotate your Filter as per http://docs.oracle.com/javaee/6/tutorial/doc/bnagb.html.
Note that those annotation should be put in the LogFilter itself. And that it has no relation to spring bean or spring context. Those configuration are scanned and processed by the application server, e.g Tomcat, but Spring.
For a WebApp, I need to serve all .js, .css and all images from corresponding folders in my web app development tree ./js, ./css, ... through a default handling.
So any URL like
http://www.example.com/js/x.js
should be served straight from the static files in the war.
The main servlet should deal with all requests that are not for the above.
I need to be able to process requests like
http://www.example.com/PROJ/ELEM/WHATEVER
with the same unique main servlet.
So I thought I'd do this in the web.xml:
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/css/*,/js/*,/WEB-INF/*</url-pattern>
</servlet-mapping>
and map the main servlet like this to make some JSTL mods in a JSP file:
#WebServlet(urlPatterns="/*")
public class Main extends HttpServlet {
protected void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("test", "ok");
request.getRequestDispatcher("/WEB-INF/index.jsp")
.forward(request, response);
}
}
When I do this I end up in a recursive loop.
Is there a way to achieve this?
Here is the explanation with same problem.
http://www.kuligowski.pl/java/rest-style-urls-and-url-mapping-for-static-content-apache-tomcat,5
This is what already happens. There is a 'default servlet' that handles any request that isn't specifically mapped to an installed servlet.
A simple variation on Rahul Jain's answer. You could do what spring MVC does for static resources : DispatcherServlet is a catch all, and it is configured to delegate to default servlet for a number or url. This may be interesting for a catch all servlet because it is often designed as a front controller that delegates actual serving to other controllers (be them servlets or not).
You can simply give the prefixes of urls that should serve static resources in a comma separated string in a context param of name static_path_prefixes and put that in your servlet :
String[] staticPathPrefixes;
RequestDispatcher defaultDispatcher;
#Override
protected void service(HttpServletRequest hsr, HttpServletResponse hsr1) throws ServletException, IOException {
String path = hsr.getServletPath();
for (String url: staticPathPrefixes) {
if (path.startsWith(url)) {
defaultDispatcher.forward(hsr, hsr1);
return;
}
}
super.service(hsr, hsr1);
}
#Override
public void init() throws ServletException {
String urls = getServletConfig().getInitParameter("static_path_prefixes");
staticPathPrefixes = urls.split(" *, *");
defaultDispatcher = getServletConfig().getServletContext().
getNamedDispatcher("default");
}
Is there a way to secure a forward URL?
To be clear, I've an error handler:
#Component
public class MyAuthenticationFailureHandler implements
AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
if (exception.getClass().isAssignableFrom(
MyException.class)) {
MyException myException = (MyException) exception;
RequestDispatcher dispatcher = request.getRequestDispatcher("/signup/exception");
request.setAttribute("userID", myException.getUserID());
dispatcher.forward(request, response);
}
}
}
and a web controller:
#Controller
#RequestMapping("/signup")
public class SignupController {
#RequestMapping("/exception")
public ModelAndView signup() {
ModelAndView model = new ModelAndView(new InternalResourceView("/WEB-INF/jsp/signup.jsp", true));
return model;
}
}
I'd like that the route http://{hostname:port}/signup/exception will be accessible only as forward from my own handler, not directly (by writing URL and params on browser bar).
Add an attribute like
request.setAttribute("isForwarded","True")
in handler and check that attribute inside your controller.
If yes, go ahead else redirect to appropriate page.
I didn't test it, but I know that servlet containers refuse to answer to request relative to WEB-INF, but that you can forward to jsp servlets under WEB-INF. May be you could try to use an url like /WEB-INF/signup/exception ?
Alternatively, I think you're using Spring Security. I do not think that security filters are applied to forwarded requests. What gives the following intercept-url ?
<intercept-url pattern="/signup/exception" access="denyAll" />