I have the code like:
#PostConstruct
public void init() throws IOException {
resourceHttpRequestHandler = new ResourceHttpRequestHandler();
resourceHttpRequestHandler.setServletContext(servletContext);
resourceHttpRequestHandler.setLocations(... my locations here... );
}
#RequestMapping(value = "/mypath/**", method = RequestMethod.GET)
public void getResource(HttpServletRequest request, HttpServletResponse response) throws Exception {
resourceHttpRequestHandler.handleRequest(request, response);
/* here */
}
Is it possible to know if the resolving of resource request was successful or not (after the call of method handleRequest() )?
Related
I am wondering how to read response in filter from request body if #Controller method returns Callable interface.
My filter looks like this. Response is always empty. Any solution to this? Is this allowed only using AsyncListener?
#Component
public class ResposeBodyXmlValidator extends OncePerRequestFilter {
private final XmlUtils xmlUtils;
private final Resource xsdResource;
public ResposeBodyXmlValidator(
XmlUtils xmlUtils,
#Value("classpath:xsd/some.xsd") Resource xsdResource
) {
this.xmlUtils = xmlUtils;
this.xsdResource = xsdResource;
}
#Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain
) throws ServletException, IOException {
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(httpServletResponse);
doFilter(httpServletRequest, response, filterChain);
if (MediaType.APPLICATION_XML.getType().equals(response.getContentType())) {
try {
xmlUtils.validate(new String(response.getContentAsByteArray(), response.getCharacterEncoding()), xsdResource.getInputStream());
} catch (IOException | SAXException e) {
String exceptionString = String.format("Chyba při volání %s\nNevalidní výstupní XML: %s",
httpServletRequest.getRemoteAddr(),
e.getMessage());
response.setContentType(MediaType.TEXT_PLAIN_VALUE + "; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(exceptionString);
}
}
response.copyBodyToResponse(); // I found this needs to be added at the end of the filter
}
}
The problem of Callable is that the dispatcher servlet itself starts async processing and the filter is exited before actually processing of a request.
When Callable arrives to dispatcher servlet, it frees container thread from pool by releasing all filters (filters basically finish their work). When Callable produces results, the dispatcher servlet is called again with the same request and the response is immidiately fulfilled by the data return from Callable. This is handled by request attribute of type AsyncTaskManager which holds some information about processing of async request. This can be tested with Filter and HandlerInterceptor. Filter is executed only once but HandlerInterceptor is executed twice (original request and the request after Callable completes its job)
When you need to read request and response, one of the solution is to rewrite dispatcherServlet like this:
#Bean
#Primary
public DispatcherServlet dispatcherServlet(WebApplicationContext context) {
return new DispatcherServlet(context) {
#Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
super.service(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}
};
}
This way you ensure that you can read request and response multiple times. Other thing is to add HandlerInterceptor like this (you have to pass some data as request attribute):
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData == null) {
request.setAttribute(LOGGER_FILTER_ATTRIBUTE, new AsyncRequestData(request));
}
return true;
}
#Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex
) throws Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData != null && response instanceof ContentCachingResponseWrapper) {
log(request, (ContentCachingResponseWrapper) response, (AsyncRequestData) asyncRequestData);
}
}
afterCompletion method is called only once after async request has been completely processed. preHandle is called exactly twice so you have to check existance of your attribute. In afterCompletion, the response from the call is already present and if you do want to replace it, you should call response.resetBuffer().
This is one possible solution and there could be better ways.
I wrote a Request Interceptor to add some Information to Requests in Test-Environment.
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
...
}
public void postHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)
throws Exception {
...
}
Currently I'm retrieving the URLs like this:
String url = request.getServletPath();
For a Controller like this:
#RequestMapping(value = "/{id}",
method = RequestMethod.GET)
public ResponseEntity<?> getByID(#PathVariable long ID) {
...
}
And for a Request like /1/
url would be /1/
Is there any way to get the Request-Mapping-Value ==> /{id}
Thanks in advance
#RequestMapping and its composed annotation methods (i.e. #GetMapping , #PostMapping etc.) are handled by HandlerMethod. So cast the handler object to it and you can access the #RequestMapping information that you want:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
RequestMapping mapping = hm.getMethodAnnotation(RequestMapping.class);
if (mapping != null) {
for(String val : mapping.value()) {
//***This is the mapping value of #RequestMapping***
System.out.println(val);
}
}
}
}
I have to read HttpServletRequest multiple times. I have wrapped HttpServletRequest like said in those posts Http Servlet request lose params from POST body after read it once
In my filter class which extends AbstractAuthenticationProcessingFilter, i can consume and chain request in successfulAuthentication method since it has chain parameter. But in addition to those solutions i have to chain request between attempt and succesful authentication steps:
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
// wrapping request and consuming
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// Since i couldn't chain wrapped httpServletRequest from attemptAuthentication step, this request still gets non-wrapping one and inputstream is empty
}
How can i pass wrapped request from attemptAuthentication to successfulAuthentication?
This is an old question but in case someone gets the same problem:
You can wrap a request like suggested in the previous answer but you need to wrap it before it gets filtered by Authentication filter:
Spring security configuration will look something like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
AuthFilter authFilter = new AuthFilter();
WrapperFilter wrapperFilter = new WrapperFilter();
http .cors()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(exceptionHandler)
.and()
.authorizeRequests()
.antMatchers("/v1/*", "/api/*")
.authenticated()
.and()
.addFilterBefore(authFilter, BasicAuthenticationFilter.class)
.addFilterBefore(wrapperFilter, AuthFilter.class);
}
So that wrapper filter goes before your auth filter and wrapper filter's doFilter wraps the request and passes it on:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MultiReadHttpServletRequest wrapper = new MultiReadHttpServletRequest((HttpServletRequest) request);
chain.doFilter(wrapper, response);
}
And MultiReadHttpServletRequest is the following:
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private byte[] body;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
try {
body = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
body = new byte[0];
}
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
ByteArrayInputStream wrapperStream = new ByteArrayInputStream(body);
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener readListener) {
}
#Override
public int read() throws IOException {
return wrapperStream.read();
}
};
}
}
There is no such way. The only thing you can do is wrap the request every time you gonna read it in one more filter. Just wrap the request every time before you read it copying body.
See some working code for a wrapper e.g. here
I have Servlet which add users in database. Servlet use instance of module for work with database he's alias DBJoint. And Servlet get instance of DBJoint from ServletContext.
#WebListener
public class ContextListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
final ServletContext servletContext =
servletContextEvent.getServletContext();
final DBJoint joint = new DBJointHandler(
"database_scripts",
"authentication_database");
servletContext.setAttribute("db", joint);
}
}
And in each Servlet when I need work with database I call ServletContext and get DBJoint by key "db".
public class AddUserServlet extends HttpServlet {
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF8");
try {
final boolean success = addUserInDatabase(req);
if (success) req.setAttribute("serverAnswer", EDIT_SUCCESS.get());
else req.setAttribute("serverAnswer", ERR_UNIQUE_L_P.get());
req.getRequestDispatcher(ANSWER.get())
.forward(req, resp);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* Addition user in database.
*
* #return true if addition success, else false.
*/
private boolean addUserInDatabase(final HttpServletRequest req)
throws SQLException {
final User user = getUserFromRequest(req);
return getDatabaseExecutor().addUserAndGetSuccess(user);
}
/**
* Extracts user's data from HttpServletRequest.
*
* #return user from request.
*/
private User getUserFromRequest(final HttpServletRequest req) {
return new User(
req.getParameter("name"),
req.getParameter("login"),
req.getParameter("password"),
req.getParameter("email"),
req.getParameter("role")
);
}
/**
* Get executor database requests.
*/
private ScriptExecutor getDatabaseExecutor() throws SQLException {
final DBJoint db = (DBJoint) getServletContext().getAttribute("db");
return db.getDBScriptExecutor();
}
}
I need test my Servlet, and use mock object instead original object:
In this test I try verify what body doPost call addUser(User u) in executor of database script. But get NPE.
#Test
public void whenUserAddThenAddUserCall() throws ServletException, IOException, SQLException {
final AddUserServlet servlet = new AddUserServlet();
//mock http.
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
User user = new User("name", "login", "password", "email");
//mock database.
ScriptExecutor executor = mock(ScriptExecutor.class);
DBJoint joint = mock(DBJointHandler.class);
when(joint.getDBScriptExecutor()).thenReturn(executor);
final ServletContext context = request.getServletContext();
//In this place I get NPE but why?
context.setAttribute("db", joint);
servlet.doPost(request, response);
verify(executor).addUser(user);
}
Why I have NullPointerException? How test this class? Thank You!
you need to include these as well
ServletContext context = mock(ServletContext .class)
when(request.getServletContext()).thenReturn(context );
You are calling request.getServletContext() on the request object which is a mock.
Mocked objects do no provide real method implementations. Instead of the real getServletContext() implementation, a stubbed version of the method is called returning null.
A mock should be told what it needs to return when a method is called on it, you need to define behavior just like you did for the DBJoinHandler mock.
To solve the NPE, do something like this:
when(request.getServletContext()).thenReturn(context);
where context could be a ServletContext mock.
With the above code I always get an error in line of test
when(request.getServletContext().getAttribute("SessionFactory"))
.thenReturn(factory);
Any ideas?
Java class
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
SessionFactory sessionFactory = (SessionFactory) request.getServletContext().getAttribute("SessionFactory");
...............
}
Test class
#Test
public void testServlet() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
factory = contextInitialized();
when(request.getServletContext().getAttribute("SessionFactory")).thenReturn(factory); //Always error here
when(request.getParameter("empId")).thenReturn("35");
PrintWriter writer = new PrintWriter("somefile.txt");
when(response.getWriter()).thenReturn(writer);
new DeleteEmployee().doGet(request, response);
verify(request, atLeast(1)).getParameter("username"); // only if you want to verify username was called...
writer.flush(); // it may not have been flushed yet...
assertTrue(FileUtils.readFileToString(new File("somefile.txt"), "UTF-8")
.contains("My Expected String"));
}
when(request.getServletContext().getAttribute("SessionFactory")).thenReturn(factory);
This bit:
request.getServletContext().getAttribute("SessionFactory")
is a chained call; you're trying to stub both the request, and the servlet context that the request returns.
You can do that, but you need to use deep stubs:
HttpServletRequest request = mock(HttpServletRequest.class, RETURNS_DEEP_STUBS);