Spring RequestMethod "POST" not supported while File Upload - spring

I am including File Upload feature in my Spring Security, Spring MVC application.
Here is my JSP page:
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# page session="false" %>
<html>
<head>
<title>Upload File Request Page</title>
</head>
<body>
<form method="POST" action="uploadFile" enctype="multipart/form-data">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
File to upload: <input type="file" name="file"><br />
Name: <input type="text" name="name"><br /> <br />
<input type="submit" value="Upload"> Press here to upload the file!
</form>
</body>
</html>
Here is my Controller Method:
#RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
public #ResponseBody
String uploadFileHandler(#RequestParam("name") String name,
#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
try {
byte[] bytes = file.getBytes();
// Creating the directory to store file
String rootPath = System.getProperty("catalina.home");
File dir = new File(rootPath + File.separator + "tmpFiles");
if (!dir.exists())
dir.mkdirs();
// Create the file on server
File serverFile = new File(dir.getAbsolutePath()
+ File.separator + name);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
System.out.println("Server File Location="
+ serverFile.getAbsolutePath());
return "You successfully uploaded file=" + name;
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
} else {
return "You failed to upload " + name
+ " because the file was empty.";
}
}
MultipartResolver Bean has been declared as shown below:
#Bean(name = "multipartResolver")
public CommonsMultipartResolver createMultipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
//resolver.setDefaultEncoding("utf-8");
resolver.setMaxUploadSize(1000000);
resolver.setMaxInMemorySize(1000000);
return resolver;
}
And my Spring Security HttpSecurity Configuration is:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**").permitAll()
.antMatchers("/testDecorator").permitAll()
.antMatchers("/uploadFile").permitAll()
.antMatchers("/home")
.authenticated().and().formLogin().loginPage("/login")
.failureUrl("/login?error")
.successHandler(customSuccessHandler)
.usernameParameter("username").passwordParameter("password")
.and().logout().logoutSuccessUrl("/login?logout").and().csrf()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
}
When I chose file and submit the form. It is throwing below error:
org.springframework.web.servlet.PageNotFound handleHttpRequestMethodNotSupported
WARNING: Request method 'POST' not supported
When I use the same code in another project. It is working fine. I am not getting to know where I went wrong.
Can someone please guide me where can be the problem?
Regards,
Manju Raghavendra.

Have you configured a multipartResolver in your config files? It might look something like this:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="78643200"/>
<property name="maxInMemorySize" value="78643200"/>
</bean>

Related

Spring mvc form submission bindingresult throws error

So my bindingresult throws an error but I'm not able to see it for some reason and I'm not able to figure out what the problem is. I'm guessing the problem lies with the variable targetDate where there is a problem the type. I've pasted my controller and JSP code below. Any help is appreciated!
#Controller
public class ToDoController {
#Autowired
private ToDoService service;
// All date parameters displayed as mm/DD/yyyy
#InitBinder
protected void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("mm/DD/yyyy");
binder.registerCustomEditor(Date.class, new CustomDateEditor(
dateFormat, false));
}
#RequestMapping(value = "/list-todo", method= RequestMethod.GET)
// HttpSession allows access to the session
public String showToDo(ModelMap model, HttpSession httpSession) {
String user = (String) httpSession.getAttribute("name");
model.addAttribute("todos", service.retrieveTodos(user));
return "list-todos";
}
// redirect to update form
#RequestMapping(value = "/update-todo", method= RequestMethod.GET)
public String getUpdateForm(ModelMap model, #RequestParam int id) {
System.out.println("ID " + id);
// To work with command bean
model.addAttribute("id", id);
model.addAttribute("todo", service.retrieveTodo(id-1));
return "updateToDo";
}
// What does Valid do?
#RequestMapping(value = "/update-todo", method= RequestMethod.POST)
public String submitUpdate(ModelMap model, #Valid ToDo todo, BindingResult result) {
if (result.hasErrors()) {
System.out.println("ERROR" + result.getAllErrors());
// Redirect and pass on the id value
return "redirect:/update-todo?id=" + todo.getId();
}
System.out.println("Update todo" + todo);
service.updateToDo(todo);
model.clear();
return "redirect:/list-todo";
}
// Will be executed first
#RequestMapping(value = "/add-todo", method= RequestMethod.GET)
public String showAddForm(ModelMap model) {
model.addAttribute("todo", new ToDo());
return "addToDo";
}
/*
* Will be executed after form is submitted
* #Valid ToDo - command bean from addToDo.jsp.
* #Valid to validate the information
* #BindingResult showcases the result of the validation
*/
#RequestMapping(value = "/add-todo", method= RequestMethod.POST)
public String submitAddForm(ModelMap model , #Valid ToDo todo, HttpSession httpSession, BindingResult result) {
System.out.println("running" + result);
// If there is validation error , return to addToDos page for user to fix the error
if (result.hasErrors()) {
return "redirect:/showAddForm?id=?" + todo.getId();
}
String user = (String) httpSession.getAttribute("name");
service.addTodo(user, todo.getDescription(), todo.getTargetDate(), false);
// Clears the url e.g. name?=jyj123
model.clear();
// return to the url which executes the showToDO
return "redirect:/list-todo";
}
// delete to do entry
#RequestMapping(value = "/delete-todo", method= RequestMethod.GET)
public String deleteToDo(ModelMap model, #RequestParam int id) {
service.deleteTodo(id);
model.clear();
return "redirect:/list-todo"; }
}
My JSP
<%# taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%# include file = "common/header.jspf" %>
<%# include file = "common/nav.jspf" %>
<div class="container">
<H1>Update your task!</H1>
<form:form method="POST" commandName="todo">
<!-- Carry on the id value -->
<form:hidden path = "id"/>
<form:hidden path = "user"/>
<fieldset class="form-group">
<form:label path="description">Description:</form:label>
<form:input path="description" type="text" class="form-control"
required="required" />
<form:errors path="description" cssClass="text-warning" />
</fieldset>
<fieldset class="form-group">
<form:label path="targetDate">Target Date</form:label>
<form:input path="targetDate" type="text" class="form-control"
required="required" />
<form:errors path="targetDate" cssClass="text-warning" />
</fieldset>
<fieldset class="form-group">
<form:radiobutton path="completion" value="true" />
<form:radiobutton path="completion" value="false" />
</fieldset>
<button type="submit" class="btn btn-success" >Submit Update</button>
</form:form>
<spring:hasBindErrors htmlEscape="true" name="todo">
<c:if test="${errors.errorCount gt 0}">
<h4>The error list :</h4>
<font color="red">
<c:forEach items="${errors.allErrors}" var="error">
<spring:message code="${error.code}"
arguments="${error.arguments}"
text="${error.defaultMessage}"/><br/>
</c:forEach>
</font>
</c:if>
</spring:hasBindErrors>
</div>
<%# include file = "common/footer.jspf" %>
EDIT: bindingresult throws this
ERROR[Field error in object 'todo' on field 'targetDate': rejected value [Tue Jan 05 00:00:00 SGT 2021]; codes [typeMismatch.todo.targetDate,typeMismatch.targetDate,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [todo.targetDate,targetDate]; arguments []; default message [targetDate]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'targetDate'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value 'Tue Jan 05 00:00:00 SGT 2021'; nested exception is java.lang.IllegalArgumentException]]
In your jsp you submit the form to "todo", not "update-todo". It would be nice to see your ToDo bean. Also: result.getAllErrors().

Why the value in model.put is appended in URL

I am building a Spring MVC Web App. In one Controller, I used model.put method to pass some information to a jsp file.
The following is the snippet of controller
#RequestMapping(value = "/login" , method= RequestMethod.POST)
public String loginPost(HttpSession session,ModelMap model, #RequestParam(value = "username") String username, #RequestParam(value = "password") String password) throws Exception {
User user = getUser(username, password);
if(user !=null){
session.setAttribute("username",username);
model.put("message","login successful ");
return "redirect:/index";
}else{
model.put("message","login failed");
return "redirect:/login";
}
}
The index.jsp and login.jsp:
<%# page contentType="text/html; charset=UTF-8" %>
<html>
<body>
Click to upload page
</body>
</html>
<%--
Created by IntelliJ IDEA.
User: cbl
Date: 2016/1/6
Time: 15:34
To change this template use File | Settings | File Templates.
--%>
<%# page contentType="text/html;charset=UTF-8" language="java" %>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>${message}</h1>
<form method = "POST" action = "/login">
<label for="username">Username</label>
<input id="username" type="text" name = "username">
<label for="password">Password</label>
<input id="password"type="password" name="password">
<input type="submit" value="login">
</form>
</body>
</html>
The ${message} is extected to be display within body, however, it is appended after URl:http://localhost:8000/login?message=login+failed and http://localhost:8000/index?message=login+successful++
Any response is appreciated
Since you are returning a redirect, a new GET request is created under the covers, that is why is getting attached to the url.
If you want to keep the data in the model only, you can use the flash scope with RedirectAttributes
#RequestMapping(value = "/login" , method= RequestMethod.POST)
public String loginPost(HttpSession session,
RedirectAttributes ra,
#RequestParam(value = "username") String username,
#RequestParam(value = "password") String password) throws Exception {
User user = getUser(username, password);
if(user !=null){
session.setAttribute("username",username);
ra.addFlashAttribute("message","login successful ");
return "redirect:/index";
}else{
ra.addFlashAttribute("message","login failed");
return "redirect:/login";
}
}

SpringBoot: Large Streaming File Upload Using Apache Commons FileUpload

Am trying to upload a large file using the 'streaming' Apache Commons File Upload API.
The reason I am using the Apache Commons File Uploader and not the default Spring Multipart uploader is that it fails when we upload very large file sizes (~2GB). I working on a GIS application where such file uploads are pretty common.
The full code for my file upload controller is as follows:
#Controller
public class FileUploadController {
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void upload(HttpServletRequest request) {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
// Inform user about invalid request
return;
}
//String filename = request.getParameter("name");
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();
// Parse the request
try {
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (item.isFormField()) {
System.out.println("Form field " + name + " with value " + Streams.asString(stream) + " detected.");
} else {
System.out.println("File field " + name + " with file name " + item.getName() + " detected.");
// Process the input stream
OutputStream out = new FileOutputStream("incoming.gz");
IOUtils.copy(stream, out);
stream.close();
out.close();
}
}
}catch (FileUploadException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
#RequestMapping(value = "/uploader", method = RequestMethod.GET)
public ModelAndView uploaderPage() {
ModelAndView model = new ModelAndView();
model.setViewName("uploader");
return model;
}
}
The trouble is that the getItemIterator(request) always returns an iterator that does not have any items (i.e. iter.hasNext() ) always returns false.
My application.properties file is as follows:
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:19095/authdb
spring.datasource.username=georbis
spring.datasource.password=asdf123
logging.level.org.springframework.web=DEBUG
spring.jpa.hibernate.ddl-auto=update
multipart.maxFileSize: 128000MB
multipart.maxRequestSize: 128000MB
server.port=19091
The JSP view for the /uploader is as follows:
<html>
<body>
<form method="POST" enctype="multipart/form-data" action="/upload">
File to upload: <input type="file" name="file"><br />
Name: <input type="text" name="name"><br /> <br />
Press here to upload the file!<input type="submit" value="Upload">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</body>
</html>
What might I be doing wrong?
Thanks to some very helpful comments by M.Deinum, I managed to solve the problem. I have cleaned up some of my original post and am posting this as a complete answer for future reference.
The first mistake I was making was not disabling the default MultipartResolver that Spring provides. This ended up in the resolver processing the HttpServeletRequest and thus consuming it before my controller could act on it.
The way to disable it, thanks to M. Deinum was as follows:
multipart.enabled=false
However, there was still another hidden pitfall waiting for me after this. As soon as I disabled default multipart resolver, I started getting the following error when trying to make an upload:
Fri Sep 25 20:23:47 IST 2015
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
In my security configuration, I had enabled CSRF protection. That necessitated that I send my POST request in the following manner:
<html>
<body>
<form method="POST" enctype="multipart/form-data" action="/upload?${_csrf.parameterName}=${_csrf.token}">
<input type="file" name="file"><br>
<input type="submit" value="Upload">
</form>
</body>
</html>
I also modified my controller a bit:
#Controller
public class FileUploadController {
#RequestMapping(value="/upload", method=RequestMethod.POST)
public #ResponseBody Response<String> upload(HttpServletRequest request) {
try {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
// Inform user about invalid request
Response<String> responseObject = new Response<String>(false, "Not a multipart request.", "");
return responseObject;
}
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();
// Parse the request
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (!item.isFormField()) {
String filename = item.getName();
// Process the input stream
OutputStream out = new FileOutputStream(filename);
IOUtils.copy(stream, out);
stream.close();
out.close();
}
}
} catch (FileUploadException e) {
return new Response<String>(false, "File upload error", e.toString());
} catch (IOException e) {
return new Response<String>(false, "Internal server IO error", e.toString());
}
return new Response<String>(true, "Success", "");
}
#RequestMapping(value = "/uploader", method = RequestMethod.GET)
public ModelAndView uploaderPage() {
ModelAndView model = new ModelAndView();
model.setViewName("uploader");
return model;
}
}
where Response is just a simple generic response type I use:
public class Response<T> {
/** Boolean indicating if request succeeded **/
private boolean status;
/** Message indicating error if any **/
private String message;
/** Additional data that is part of this response **/
private T data;
public Response(boolean status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
// Setters and getters
...
}
If you're using a recent version of spring boot (I'm using 2.0.0.M7) then the property names have changed.
Spring started using technology specific names
spring.servlet.multipart.maxFileSize=-1
spring.servlet.multipart.maxRequestSize=-1
spring.servlet.multipart.enabled=false
If you're getting StreamClosed exceptions caused by multiple implementations being active, then the last option allows you to disable the default spring implementation
Please try to add spring.http.multipart.enabled=false in application.properties file.
I use kindeditor + springboot. When I use (MultipartHttpServletRequest) request. I could get the file, but I use appeche-common-io:upload.parse(request) the return value is null.
public BaseResult uploadImg(HttpServletRequest request,String type){
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
MultiValueMap<String, MultipartFile> multiFileMap = multipartRequest.getMultiFileMap();
You Can simply add spring properties:
spring.servlet.multipart.max-file-size=20000KB
spring.servlet.multipart.max-request-size=20000KB
here my maximum file size is 20000KB, you can change if required.

Handling MultipartException with Spring Boot and display error page

I have a very simple file upload set up with Spring Boot.
I was wondering if there was an easy way to display an error page when the maximum file size is exceeded.
I have uploaded a very simple example of what I'm trying to achieve on github.
Basically, the idea is to catch the MultipartException in a global Spring exception handler:
#ControllerAdvice
public class UploadExceptionHandler {
#ExceptionHandler(MultipartException.class)
public ModelAndView handleError(MultipartException exception) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("error", exception.getMessage());
modelAndView.setViewName("uploadPage");
return modelAndView;
}
}
The controller which handles the file upload is really simple:
#RequestMapping("/")
public String uploadPage() {
return "uploadPage";
}
#RequestMapping(value = "/", method = RequestMethod.POST)
public String onUpload(#RequestParam MultipartFile file) {
System.out.println(file.getOriginalFilename());
return "uploadPage";
}
And the uploadPage.html thymeleaf template associated with it too:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<title>Upload</title>
</head>
<body>
<div style="color: red" th:text="${error}" th:if="${error}">
Error during upload
</div>
<form th:action="#{/}" method="post" enctype="multipart/form-data">
<input type="file" id="file" name="file"/>
<button type="submit" name="save">Submit</button>
</form>
</body>
</html>
The idea is to display an error message in the same upload page when the file is too big.
It was my understanding that one would configure Spring's MultipartResolver to resolve exceptions lazily and be able to catch those exceptions at Spring's level (MVC exception handlers) but this code does not seem to help:
#Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(true);
return multipartResolver;
}
So before I resort to extreme measures like a filter or extending the MultipartResolver...
Do you know a clean way to handle those exceptions with Spring MVC?
Answer
Thanks to #rossen-stoyanchev.
Here is what I ended up doing:
#RequestMapping("uploadError")
public ModelAndView onUploadError(HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView("uploadPage");
modelAndView.addObject("error", request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE));
return modelAndView;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container -> container.addErrorPages(new ErrorPage(MultipartException.class, "/uploadError"));
}
Works like a charm and feels like an elegant solution.
I updated the project on github if someone is interested.
Many thanks!
Multipart request parsing happens before a handler is selected and hence there is no #Controller and therefore no #ControllerAdvice to speak of yet. You can use an ErrorController (see http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-error-handling) for that.
BTW you don't need #RequestParam. It's enough that the argument type is MultipartFile.

Getting error when submitting form using SpringMVC and REST

Created an online form in JSP using SpringMVC tag libraries. The controller for my form is a RESTful web service.
The RESTful web service has two calls:
(1) http://localhost:8080/myapp/applications/new
This brings up the online form in the browser (this works).
(2) http://localhost:8080/myapp/applications/create
This saves the form data to a database (handles submit). This is where it breaks.
Followed the conventions from the sample demo petclinic app which comes with the Spring Framework.
Online form:
<%# page contentType="text/html;charset=UTF-8" language="java"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<body>
<form:form modelAttribute="application" method="POST" action="create">
<table>
<tr>
<td>Name:</td>
<td><form:input path="name" size="30" maxlength="80"/></td>
</tr>
<tr>
<td>Description:</td>
<td><form:input path="description" size="30" maxlength="80"/></td>
</tr>
<tr>
<td>Image URL:</td>
<td><form:input path="imgUrl" size="30" maxlength="80"/></td>
</tr>
</table>
<input type="submit" value="Save" />
</form:form>
</body>
</html>
The RESTful web service which serves as form controller:
#Controller
#Path(ApplicationsResource.APPLICATION_URL)
public class ApplicationsResource
{
private final Logger log =
LoggerFactory.getLogger(ApplicationsResource.class);
public static final String APPLICATION_URL = "/applications";
#Autowired
private ApplicationManager applicationManager;
#Autowired
private ProfileManager profileManager;
#POST
#Path("create")
#Produces(MediaType.TEXT_HTML)
public Model getNewApplication(#Context HttpServletRequest request,
#RequestAttribute Model model)
{
Application app = new Application();
model.addAttribute("application", app);
try
{
if ("POST".equalsIgnoreCase(request.getMethod()))
{
if (app != null)
{
applicationManager.save(app);
log.info("Added application: " + app.getName());
}
else
{
log.info("Application not added");
}
}
}
catch (Exception e)
{
log.info("Exception: ", e);
throw new
WebApplicationException(Response.status(
RestError.SERVER_ERROR_HTTP_RESP).
type("application/json;charset=utf-8").
entity(new ErrorOutput(RestError.SERVER_ERROR_CODE, RestError.SERVER_ERROR_MSG, e.toString())).build());
}
return model;
}
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder)
{
dataBinder.setDisallowedFields(new String[] {"id"});
}
#GET
#Path("new")
#Produces( { MediaType.TEXT_HTML })
public ModelAndView getNewApplicationForm()
{
log.info("ApplicationsResource - Inside getNewApplicationForm");
ModelAndView mv = new ModelAndView("/applications/applications_new");
mv.addObject("application", new Application());
return mv;
}
}
Exception thrown when I click on submit:
Failed executing POST /applications/create
org.jboss.resteasy.spi.BadRequestException:
Could not find message body reader for type:
interface org.springframework.ui.Model of content type:
application/x-www-form-urlencoded at
org.jboss.resteasy.core.MessageBodyParameterInjector$1
createReaderNotFound(MessageBodyParameterInjector.java:73)
Does anyone know why I am getting this exception?
Would really appreciate it if someone could help me with this issue...
Happy programming and thank you for taking the time to read this.
It was a RESTEasy problem... The fix was putting the #Form Application App inside the parameter list and prepending the domain model object's setters with #FormParam("name").
See: Integrating RESTEasy with SpringMVC
You haven't told your controller what mime types it accepts and how to map them, it needs a Reader associated with that mime type to map it and send it into your method.

Resources