Im am writing a Spring MVC #Controller. On a #RequestMapping method I would like to return a View name OR report back a 404 HTTP status code.
How can I continue using Spring View Resolver (for when I need a view) and control response error code without throwing an exception?
just raw draft
//initialised by init
private Map<String, View> viewsMap;
#RequestMapping(value = "/{path}")
#ResponseStatus(HttpStatus.OK)
public View getResponse(HttpServletResponse resp, #PathVariable String path) {
if (viewsMap.get(path ) != null){
return viewsMap.get(path );
}
resp.setStatus(404);
return null;
}
You'd need to implement your own ViewResolver and a View that returns a particular HTTP status code. You can extend InternalResourceViewResolver (or whatever you're using now).
public class MyViewResolver extends InternalResourceViewResolver {
public static final String HTTP_404_VIEW = "http404view";
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (HTTP_404_VIEW.equals(viewName)) {
return new StatusCodeView(404);
}
else
{
return super.resolveViewName(viewName, locale);
}
}
}
public class StatusCodeView implements View
{
private final int code;
public StatusCodeView(int code)
{
this.code = code;
}
public void render(Map model, HttpServletRequest request, HttpServletResponse response)
{
response.sendError(this.code);
}
}
In your controller, just return MyViewResolver.HTTP_404_VIEW.
The best way I found to solve this is using directly the provided View Resolver for both cases. I simply created a view (in my case a .jsp file) for "not found" responses and:
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return "error/" + HttpServletResponse.SC_NOT_FOUND;
Extra: as there can be different ways to trigger a HTTP error response, I also mapped the error pages in web.xml to have a similar view for same HTTP error code responses.
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/error/404.jsp</location>
</error-page>
A little work for what I needed, but it made me set HTTP error handling more accurately in my application.
Related
I want to be able to handle 401 and show a specific page in angular 8 but currently only showing index.html file
Things to mind
Angular is the view for the spring boot so its not a separate application
I am not using spring security. Im just using filters in spring to determine if to be authorize
This is my filter.
#Component
public class CustomSessionFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) httpServletRequest;
if (!req.getRequestURI().startsWith("/sample/path")){
HttpServletResponse httpResponse = (HttpServletResponse) httpServletResponse;
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
}
Maybe its relevant that i have a Controller that extend ErrorController
#CrossOrigin
#RestController
public class IndexController implements ErrorController {
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public ModelAndView saveLeadQuery() {
return new ModelAndView("forward:/");
}
#Override
public String getErrorPath() {
return PATH;
}
}
EDIT: I didnt use spring security because i dont need to login i just have to go through specific path and do some authentication.. and there is no user for the application
I just wanted to place my 0.02$ here.
In order to secure a route in your application, you can create a Security Config that extends WebSecurityConfigurerAdapter, where you can protect certain routes through either antMatchers or mvcMathchers (second is recommended). Furthermore, there you can declare a set of roles or conditions (via Spring Expression Language) that will automatically throw a 401 Error in case a user that does not have an access is trying to access the route.
More about that you can find here https://www.baeldung.com/security-spring
As of ErrorController, I believe that controller advice would be more suitable for this use case. It pretty much intercepts all errors that you declare and can return more informative and generic response :)
More about that https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
As
I have a simple rest controller which captures errors. I want to get the actual URL that the browser sent to server, but the servlet is giving me a different URL.
If I navigate to 127.0.0.1/abc a 404 error is triggered, and it's routed to /error handler as defined. However, the output gives me the result 127.0.0.1/error instead of 127.0.0.1/abc. How can I obtain the original URL?
#RestController
public class IndexController implements ErrorController {
#RequestMapping("/")
public String index() {
return "OK";
}
#RequestMapping("/error")
public String error(HttpServletRequest request) {
System.out.println("ERR: " + request.getRequestURL() + " : " + request.getRequestURI());
return "ERR";
}
#Override
public String getErrorPath() {
return "/error";
}
}
You could define your own #ExceptionHandler that will save the original request URI as an attribute on your request before it's handled by /error mapping:
#ExceptionHandler(Exception.class)
public void handleException(HttpServletRequest req, HttpServletResponse resp) throws Exception {
req.setAttribute("originalUri", req.getRequestURI());
req.getRequestDispatcher("/error").forward(req, resp);
}
and then read the new originalUri attribute in whatever /error mapping implementation you are using:
#RequestMapping("/error")
public String error(HttpServletRequest request) {
System.out.println("ERR: " + request.getAttribute("originalUri"));
return "ERR";
}
You can also try extending other error handling classes available in Spring MVC to add this behavior e.g. ResponseEntityExceptionHandler. Please note that the forward() in my example might be considered superficial as it's ok to return the ResponseEntity from #ExceptionHandler.
I am working on the Spring Boot web application. I am running two different application one for service (Rest Resource) and other one is UI which show the request on HTML page on the bases of response got on the rest request.
My all rest services are created by
#Component
#Api(value = "/api/1/registration", description = "Access and manage your information")
#Path("/api/1/registration")
#Produces(MediaType.APPLICATION_JSON)
#Slf4j
public class RegistrationResource {
...
...
#ApiOperation(value = "Find user by id", notes = "Return a user by id", response = Registration.class)
#Path("/{id}")
#GET
#Timed
#Override
public Registration get(#PathParam("id") String id) {
// my logic
}
}
Restangular Request
Restangular.one("registration/1").get().then(function (data) {
...
},function(error) {
...
});
When I do restangular request from ui, its working fine. Now I need to have a servlet resource. For that I create new resource class
#Slf4j
#RestController
#RequestMapping("/api/1/test")
public class DownloadResource{
#RequestMapping(value = "/downloadtesting", method = RequestMethod.GET)
public void download(HttpServletResponse response, HttpServletRequest request){
// I need to call this method... How can I
}
}
FYI: All my resources are registered as following...
Reflections reflections = new Reflections("com.sample.resource");
Set<Class<? extends Object>> resources =
reflections.getTypesAnnotatedWith(Path.class); //
resources.forEach(r -> {
log.debug("Registering resource " + r);
register(beanFactory.getBean(r));
});
// Same I did for RequestMapping.class also but I am getting 404 error for downloadtesting api.
NOTE: If I try with following version for downloadtesting in RegistrationResource then I am getting HttpServletRequest / Response null.
public class RegistrationResource{
#Path("/downloadtesting")
public void download(HttpServletResponse response, HttpServletRequest request){
// I need to call this method... How can I
}
}
Can any one help me?
I have the following controller:
#Controller
#RequestMapping("/my-account")
public class AccountController {
#RequestMapping(value = "/foo/post",
method = RequestMethod.POST)
public String doPost(final RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("flashAttribute", "flashAttributeValue");
return "redirect:/my-account/foo/get";
}
#RequestMapping(value = "/foo/get",
method = RequestMethod.GET)
public void doGet(final HttpServletRequest request, final Model model) {
System.out.println("in request: " + RequestContextUtils.getInputFlashMap(request).get("flashAttribute"));
System.out.println("in model: " + model.asMap().get("flashAttribute"));
}
}
I would also like to access the flash attribute flashAttribute during the invocation of a filter in the filter chain that finally invokes springs default DispatcherServlet which in turn invokes AccountController.
public class FlashAttributeBasedFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
String flashAttribute = // how to access the redirectAttribute flashAttribute here?
// do something with flashAttribute ...
filterChain.doFilter(request, response);
}
The DispatcherServlet uses a org.springframework.web.servlet.FlashMapManager that handles these flash attributes, but it doesn't provide read-only access so I think I would be messing something up if I would use it in the filter. And also the FlashMapManager instance is kept in the dispatcher servlet privately.
Does anybody have an idea how I can make the redirect attribute accessible in the filter chain for the GET request succeeding the POST?
Considering that all these methods return null into my filter (I don't understand why):
RequestContextUtils.getFlashMapManager(httpRequest)
RequestContextUtils.getInputFlashMap(httpRequest)
RequestContextUtils.getOutputFlashMap(httpRequest)
I used a drastic solution: read directly the into the session (where flash attributes are stored).
CopyOnWriteArrayList<FlashMap> what = (CopyOnWriteArrayList<FlashMap>) httpRequest.getSession().getAttribute("org.springframework.web.servlet.support.SessionFlashMapManager.FLASH_MAPS");
if (what != null) {
FlashMap flashMap = what.get(0);
[read flashMap as you read a HashMap]
}
I know, this code is super ugly but at the moment I don't find another solution.
Had the same problem, following works for me.
FlashMap flashMap = new SessionFlashMapManager().retrieveAndUpdate(request, null);
flashMap.get("parameter");
I have a single-page web app that's using Backbone.js client routing with pushState. In order to get this to work, I have to tell my server (Java, Spring 3, Tomcat) which URLs should be resolved on the server (actual JSP views, API requets), and which should simply be sent to the index page to be handled by the client. Currently I'm using an InternalResourceViewResolver to simply serve JSP views that match the name of the URL request. Since client-side URLs don't have a view on the server, the server returns a 404.
What is the best way to specify to Spring (or Tomcat) that a few specific URLs (my client-side routes) should all resolve to index.jsp, and anything else should fall through to the InternalResourceViewResolver?
I found that Spring MVC 3 added a tag that does exactly what I need, the mvc:view-controller tag. This got it done for me:
<mvc:view-controller path="/" view-name="index" />
<mvc:view-controller path="/admin" view-name="index" />
<mvc:view-controller path="/volume" view-name="index" />
http://static.springsource.org/spring/docs/3.0.x/reference/mvc.html
In theory, to handle navigation via history.pushState you want to return index.html for unhandled resources. If you look at official documentation for modern web frameworks it's often realised based on 404 status.
In spring you should handle resources in order:
path mapped REST controllers
app static resources
index.html for others
To do this you have at least 4 possible solutions.
Using EmbeddedServletContainerCustomizer and custom 404 handler
#Controller
static class SpaController {
#RequestMapping("resourceNotFound")
public String handle() {
return "forward:/index.html";
}
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/resourceNotFound"));
}
Using custom default request mapping handler
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
static class SpaWithHistoryPushStateHandler {
}
static class SpaWithHistoryPushStateHandlerAdapter implements HandlerAdapter {
#Override
public boolean supports(final Object handler) {
return handler instanceof SpaWithHistoryPushStateHandler;
}
#Override
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
response.getOutputStream().println("default index.html");
return null;
}
#Override
public long getLastModified(final HttpServletRequest request, final Object handler) {
return -1;
}
}
#Bean
public SpaWithHistoryPushStateHandlerAdapter spaWithHistoryPushStateHandlerAdapter() {
return new SpaWithHistoryPushStateHandlerAdapter();
}
#PostConstruct
public void setupDefaultHandler() {
requestMappingHandlerMapping.setDefaultHandler(new SpaWithHistoryPushStateHandler());
}
Using custom ResourceResolver
#Autowired
private ResourceProperties resourceProperties;
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations(resourceProperties.getStaticLocations())
.setCachePeriod(resourceProperties.getCachePeriod())
.resourceChain(resourceProperties.getChain().isCache())
.addResolver(new PathResourceResolver() {
#Override
public Resource resolveResource(final HttpServletRequest request, final String requestPath, final List<? extends Resource> locations, final ResourceResolverChain chain) {
final Resource resource = super.resolveResource(request, requestPath, locations, chain);
if (resource != null) {
return resource;
} else {
return super.resolveResource(request, "/index.html", locations, chain);
}
}
});
}
Using custom ErrorViewResolver
#Bean
public ErrorViewResolver customErrorViewResolver() {
final ModelAndView redirectToIndexHtml = new ModelAndView("forward:/index.html", Collections.emptyMap(), HttpStatus.OK);
return (request, status, model) -> status == HttpStatus.NOT_FOUND ? redirectToIndexHtml : null;
}
Summary
Fourth option looks simplest but as always it depends what you need. You may also want to restric returning index.html only when request expects text/html (which BasicErrorController already do based on "produces" header).
I hope one of this options will help in your case.
I would give a clear scheme to my urls and separate frontend from backend.
Some suggestions:
Route all requests starting by /server to the backend and all others to the frontend.
Setup two different domains, one for the backend, one for the frontend.