I'm updating my application from
spring 3.2.5 to 4.2.0
spring security 3.1.4 to 4.0.2.
A lot of my controllers which used to display HTML now display plain text.
I created a few test methods, the following displayed HTML in version 3 and displays plain text in version 4:
#RequestMapping(value = "/htmltest", method = RequestMethod.GET)
public void test(Writer writer, HttpServletRequest request, HttpServletResponse response) throws IOException {
writer.append("<html><head></head><body>"); //$NON-NLS-1$
writer.append("this is a HTML test");
writer.append("</body></html>"); //$NON-NLS-1$
writer.flush();
}
I also added produces = "text/html" to the #RequestMapping, did not have an effect.
I think this only has an effect using #ResponseBody? Did the following test:
#ResponseBody
#RequestMapping(value = "/htmltest2", method = RequestMethod.GET, produces = "text/html")
public String test2(HttpServletRequest request, HttpServletResponse response) {
StringBuilder str = new StringBuilder();
str.append("<html><head></head><body>"); //$NON-NLS-1$
str.append("this is a HTML test");
str.append("</body></html>"); //$NON-NLS-1$
return str.toString();
}
This displays HTML and if I switch to produces = "text/plain" returns text.
So the question is how do I get controllers to produce HTML where the writer is being used?
I found out that I can set the content type on the response response.setContentType("text/html"), but would prefer not having to do that for all my controllers.
Especially since it worked in the previous spring version, could it be a configuration problem?
#RequestMapping(value = "/htmltest3", method = RequestMethod.GET)
public void test3(Writer writer, HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
writer.append("<html><head></head><body>"); //$NON-NLS-1$
writer.append("this is a HTML test");
writer.append("</body></html>"); //$NON-NLS-1$
writer.flush();
}
Edit: I just found out that only Chrome (44.0.2403.155 m) does not display HTML, Firefox (40.0.2) and InternetExplorer (11.0.9600.17959) work as expected.
Still it may also not work in older browser versions, but maybe this gives a hint?
So the problem was the headers which have been introduced in Spring Security 3.2.0:
http://spring.io/blog/2013/08/23/spring-security-3-2-0-rc1-highlights-security-headers/
If I add the following lines to my application context, the controllers display HTML again.
<http>
...
<headers disabled="true"/>
</http>
Well but this is a bad idea since it will disable all the additional seurity features (X-XSS-Protection, X-Frame-Options, HSTS).
So one option could be to disable the defaults and configure own headers:
<http>
...
<headers defaults-disabled="true">
<frame-options policy="SAMEORIGIN" />
</headers>
</http>
After narrowing it down this solves my issue:
<http>
...
<headers>
<content-type-options disabled="true" />
</headers>
</http>
There are some security aspects that need to be considered, but for me this works for now.
http://docs.spring.io/autorepo/docs/spring-security/4.0.x/reference/html/headers.html#headers-content-type-options
Try:
#ResponseBody
#RequestMapping(value = "/htmltest3", method = RequestMethod.GET)
public void test3(Writer writer, HttpServletRequest request, HttpServletResponse response) throws IOException {
String html = "<html><head></head><body>this is a HTML test</body></html>";
return html;
}
Related
When I tried to user Micro Message Public Platform, the weChat server will invoke one of my API and I need to return a token to validate my identity. However,when I return the token directly like this, the weChat server alerts that validation is error.
#RequestMapping(value="/userFollow", method= RequestMethod.GET)
#ResponseBody
public String weChatToken(HttpServletRequest request,String signature,String timestamp,String nonce,String echostr,HttpServletResponse response) throws IOException, DocumentException {
String result=weChatService.checkSignature(signature,timestamp,nonce,echostr);
return result;
}
Then I changed my code as below. This time, the validation is correct.
#RequestMapping(value="/userFollow", method= RequestMethod.GET)
#ResponseBody
public String weChatToken(HttpServletRequest request,String signature,String timestamp,String nonce,String echostr,HttpServletResponse response) throws IOException, DocumentException {
String result=weChatService.checkSignature(signature,timestamp,nonce,echostr);
PrintWriter pw=response.getWriter();
pw.write(result);
pw.flush();
return null;
}
I Googled and got that when using #Responsebody, Spring write messages to the body of response.
So what's the difference between them? Why the first way is Wrong?
An HTTP response consists of a status code, some headers, and a body. Using #ResponseBody means your method gives the content of the body, and nothing else. Using HttpServletResponse enables your method to set all aspects of the response, but is a little inconvenient to use.
You should use ResponseBody for returning some data structure. Since you need "only" String, you should change the return type of your method to void from String and remove ResponseBody annotation.
#RequestMapping(value="/userFollow", method= RequestMethod.GET)
public void weChatToken(HttpServletRequest request,String signature,String timestamp,String nonce,String echostr,HttpServletResponse response) throws IOException, DocumentException {
String result=weChatService.checkSignature(signature,timestamp,nonce,echostr);
PrintWriter pw=response.getWriter();
pw.write(result);
pw.flush();
}
I have a simple method where I need to change the contentType to "text/plain" but when I am using Jboss EAP 6.2 it is ignored and the returned value is "application/json.
#RestController
#RequestMapping("/upload")
public class UploadController {
...
#RequestMapping(method = RequestMethod.POST)
public Result foo(MultipartFile arquivo, HttpServletResponse response) {
response.setContentType("text/plain");
return uploadService.saveFile(arquivo);
}
}
I am using SpringMVC (rest controller) and Jackson to parse to JSON. The exactly same method works fine when I use Jetty instead of Jboss.
I can't use #RequestMapping(produces="text/plain") because the actual method has a bit more logic into it and sometimes it will return application/json others text/plain, depending if the request came from IE9 browser or not.
Can someone please tell me how can I set the contentType on Jboss?
Unfortunately the only way I could get this working for Jboss was creating a different method for IE9:
#RequestMapping(value = "/ie9", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public String fooIE9(#RequestParam("arquivo") MultipartFile arquivo) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer();
return ow.writeValueAsString(uploadService.saveFile(arquivo));
}
I try to have a redirect with 301 Status Code (you know I want to be SEO friendly etc).
I do use InternalResourceViewResolver so I wanted to use some kind of a code similar to return "redirect:http://google.com" in my Controller.
This though would send a 302 Status Code
What I have tried is using a HttpServletResponse to set header
#RequestMapping(value="/url/{seo}", method = RequestMethod.GET)
public String detail(#PathVariable String seo, HttpServletResponse response){
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return "redirect:http://google.com";
}
It does still return 302.
After checking documentation and Google results I've come up with the following:
#RequestMapping(value="/url/{seo}", method = RequestMethod.GET)
public ModelAndView detail(#PathVariable String seo){
RedirectView rv = new RedirectView();
rv.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
rv.setUrl("http://google.com");
ModelAndView mv = new ModelAndView(rv);
return mv;
}
It does work perfectly fine and as expected, returning code 301
I would like to achieve it without using ModelAndView (Maybe it's perfectly fine though). Is it possible?
NOTE: included snippets are just parts of the detail controller and redirect does happen only in some cases (supporting legacy urls).
I would suggest using redirectView of spring like you have it. You have to have a complete URL including the domain etc for that to work, else it will do a 302. Or if you have access to HttpServletResponse, then you can do the below as below.
public void send301Redirect(HttpServletResponse response, String newUrl) {
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", newUrl);
response.setHeader("Connection", "close");
}
Not sure when it was added, but at least on v4.3.7 this works. You set an attribute on the REQUEST and the spring View code picks it up:
#RequestMapping(value="/url/{seo}", method = RequestMethod.GET)
public String detail(#PathVariable String seo, HttpServletRequest request){
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.MOVED_PERMANENTLY);
return "redirect:http://google.com";
}
If you already return a ModelAndView and don't want to use HttpServletResponse, you can use this snippet:
RedirectView rv = new RedirectView("redirect:" + myNewURI);
rv.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
return new ModelAndView(rv);
I know that you can do this
<img src="http://some_svg_on_the_web" />
But what if I have a controller method annotated with #ResponseBody
#RequestMapping(value="getSVG")
public #ResponseBody String getSVG(HttpServletRequest request, HttpServerletResponse response) {
String SVG = // build the SVG XML as a string
return SVG;
}
Can I then say
<img src="/getSVG" />
I have tested and the controller is definitely being hit but no image is being shown on the page.
I believe the problem is Spring is setting the default content type to application/octet-stream and the browser can't read your XML as that. Instead, you need to actually set the Content-Type header, either through the HttpServerletResponse or with Spring's ResponseEntity.
#RequestMapping(value="getSVG")
public #ResponseBody ResponseEntity<String> getSVG(HttpServletRequest request, HttpServerletResponse response) {
String SVG = // build the SVG XML as a string
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("image/svg+xml"));
ResponseEntity<String> svgEntity = new ResponseEntity<String>(svg, headers, HttpStatus.OK);
return svgEntity;
}
The fact that the you have the XML as a String doesn't really matter, you could've used getBytes() to make the content byte[]. You can also use the Resource class to have Spring get the bytes directly from a classpath or file system resource. You would parameterize ResponseEntity accordingly (there are a number of supported types).
I am converting Java web application to Spring framework and appreciate some advice on the issues I am facing with the file upload. Original code was written using org.apache.commons.fileupload.
Does Spring MultipartFile wraps org.apache.commons.fileupload or I can exclude this dependency from my POM file?
I have seen following example:
#RequestMapping(value = "/form", method = RequestMethod.POST)
public String handleFormUpload(#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
} else {
return "redirect:uploadFailure";
}
}
Originally I tried to follow this example but was always getting an error as it couldn't find this request param. So, in my controller I have done the following:
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody
ExtResponse upload(HttpServletRequest request, HttpServletResponse response)
{
// Create a JSON response object.
ExtResponse extResponse = new ExtResponse();
try {
if (request instanceof MultipartHttpServletRequest)
{
MultipartHttpServletRequest multipartRequest =
(MultipartHttpServletRequest) request;
MultipartFile file = multipartRequest.getFiles("file");
InputStream input = file.getInputStream();
// do the input processing
extResponse.setSuccess(true);
}
} catch (Exception e) {
extResponse.setSuccess(false);
extResponse.setMessage(e.getMessage());
}
return extResponse;
}
and it is working. If someone can tell me why #RequestParam did not work for me, I will appreciate. BTW I do have
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2097152"/>
</bean>
in my servlet context file.
I had to
add commons-fileupload dependency to my pom,
add multipartResolver bean (mentioned in the question),
use #RequestParam("file") MultipartFile file in the handleFormUpload method and
add enctype in my jsp : <form:form method="POST" action="/form" enctype="multipart/form-data" >
to get it to work.
spring does not have a dependency on commons-fileupload, so you'll need it. If it's not there spring will use its internal mechanism
You should pass a MultipartFile as a method parameter, rather than #RequestParam(..)
This works for me.
#RequestMapping(value = "upload.spr", method = RequestMethod.POST)
public ModelAndView upload(#RequestParam("file") MultipartFile file, HttpServletResponse response)
{
// handle file here
}
The General sysntax for request param is this #RequestParam(value="Your value", required=true),
mode over request param is used to get a value frm the Url.
In a POST you will only send the params in the request body, not in the URL (for which you use #RequestParams)
Thats why your second method worked.
In Spring MVC 3.2 support for Servet 3.0 was introduced. So you need to include commons-file upload if you use earlier versions of Spring.