基于Spring MVC的Web应用开发(7) - Headers

本文接上一篇文章,介绍@RequestMapping中的headers属性,并进一步研究produces属性以及和它配对的consumes属性。

首先看看讲解用到的类:

Java代码 复制代码  收藏代码
  1. package org.springframework.samples.mvc.simple;   
  2.   
  3. import org.springframework.stereotype.Controller;   
  4. import org.springframework.web.bind.annotation.RequestMapping;   
  5. import org.springframework.web.bind.annotation.RequestMethod;   
  6. import org.springframework.web.bind.annotation.ResponseBody;   
  7.   
  8. @Controller  
  9. public class SimpleControllerRevisited {   
  10.   
  11.     @RequestMapping(value="/simple/revisited", method=RequestMethod.GET, headers="Accept=text/plain")   
  12.     public @ResponseBody String simple() {   
  13.         return "Hello world revisited!";   
  14.     }   
  15.   
  16. }  
package org.springframework.samples.mvc.simple;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SimpleControllerRevisited {

	@RequestMapping(value="/simple/revisited", method=RequestMethod.GET, headers="Accept=text/plain")
	public @ResponseBody String simple() {
		return "Hello world revisited!";
	}

}

header是HTTP协议中的一个概念,一般翻译成头域,为了不陷入讲解理论的泥潭,我们使用Firefox的firebug看一下header到底是什么,还记得HelloWorld那一篇文章的例子么?访问http://localhost:8080/web/simple,浏览器显示Hello World!,与本例唯一的区别就是@RequestMapping没有加headers, 打开firebug,在"网络"这一栏看到有一行记录URL "GET simple",Status "200 OK",Domain "localhost:8080"等信息,展开,有三列数据,分别为Headers,Response,HTML,看Headers这列,

Request Headers:

Firebug代码 复制代码  收藏代码
  1. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
  2. Accept-Encoding: gzip, deflate   
  3. Accept-Language: en-us,en;q=0.5  
  4. Connection: keep-alive   
  5. Host: localhost:8080  
  6. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-us,en;q=0.5
Connection: keep-alive
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2

 Response Headers:

Firebug代码 复制代码  收藏代码
  1. Content-Length: 12  
  2. Content-Type: text/html   
  3. Server: Jetty(7.2.0.v20101020)  
Content-Length: 12
Content-Type: text/html
Server: Jetty(7.2.0.v20101020)

Request Headers中有个Accept,它是由浏览器发出的,表示浏览器认为它可以支持的格式,并不是真的只能支持这些格式,而Response Headers中有个Content-Type,这个值是服务端(如Servlet)封装到Respose的Headers中的。

访问例子程序的URL:http://localhost:8080/web/simple/revisited

Request Headers:

Firebug代码 复制代码  收藏代码
  1. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
  2. Accept-Encoding: gzip, deflate   
  3. Accept-Language: en-us,en;q=0.5  
  4. Connection: keep-alive   
  5. Host: localhost:8080  
  6. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-us,en;q=0.5
Connection: keep-alive
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2

 Response Headers:

Firebug代码 复制代码  收藏代码
  1. Content-Length: 22  
  2. Content-Type: text/plain   
  3. Server: Jetty(7.2.0.v20101020)  
Content-Length: 22
Content-Type: text/plain
Server: Jetty(7.2.0.v20101020)

Response Headers中的Content-Type变成了text/plain,因为RequestMapping的headers设置了Accept=text/plain。

注意这个Accept和Request Headers中的Accept没有半点关系,它跟Response Headers的Content-Type有关。

我们已经数次提及Content-Type了,其实@RequestMapping的headers属性除了Accept外也有Content-Type:

Java代码 复制代码  收藏代码
  1. @RequestMapping(value="/simple/revisited2", method=RequestMethod.GET, headers="Content-Type=text/plain")   
  2. public @ResponseBody String simple2() {   
  3.     return "Hello world 2 revisited!";   
  4. }  
	@RequestMapping(value="/simple/revisited2", method=RequestMethod.GET, headers="Content-Type=text/plain")
	public @ResponseBody String simple2() {
		return "Hello world 2 revisited!";
	}

访问http://localhost:8080/web/simple/revisited2,发现浏览器上返回HTTP状态码为415的出错页面:

Html代码 复制代码  收藏代码
  1. HTTP ERROR 415   
  2.   
  3. Problem accessing /web/simple/revisited2. Reason:   
  4.   
  5.     Unsupported Media Type   
  6.   
  7. Powered by Jetty://  
HTTP ERROR 415

Problem accessing /web/simple/revisited2. Reason:

    Unsupported Media Type

Powered by Jetty://

这是因为headers="Content-Type=text/plain"的含义是

它必须要求Request Headers里的Content-Type为"text/plain"才能执行该方法,

看firebug的Request Headers:

Firebug代码 复制代码  收藏代码
  1. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
  2. Accept-Encoding: gzip, deflate   
  3. Accept-Language: en-us,en;q=0.5  
  4. Connection: keep-alive   
  5. Host: localhost:8080  
  6. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-us,en;q=0.5
Connection: keep-alive
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2

没有Content-Type属性,不光这个,前面的那个Request Headers也没有Content-Type,其实GET方式访问的URL的Request Header里压根就没有Content-Type(我原来也不知道,直到我搜索到了这篇文章)! 所以我们只能使用POST方式测试这个特性,很遗憾,我暂时没有找到如何通过浏览器地址栏的方式来POST提交,但是,想到了上一篇文章附录中的RestTemplate,可以写个Java小程序模拟一下,在SimpleControllerRevisited中增加一个方法:

Java代码 复制代码  收藏代码
  1. @RequestMapping(value="/simple/revisited3", method=RequestMethod.POST, headers="Content-Type=text/plain")   
  2. public @ResponseBody String simple3() {   
  3.     return "Hello world 3 revisited!";   
  4. }  
	@RequestMapping(value="/simple/revisited3", method=RequestMethod.POST, headers="Content-Type=text/plain")
	public @ResponseBody String simple3() {
		return "Hello world 3 revisited!";
	}

写个RestTemplate的小程序:

Java代码 复制代码  收藏代码
  1. package org.springframework.samples.mvc.simple;   
  2.   
  3. import org.springframework.http.HttpStatus;   
  4. import org.springframework.http.MediaType;   
  5. import org.springframework.http.ResponseEntity;   
  6. import org.springframework.web.client.RestTemplate;   
  7.   
  8. public class SimpleControllerRevisitedTest {   
  9.   
  10.     public static void main(String[] args) {   
  11.         testPost();   
  12.     }   
  13.        
  14.     public static void testPost() {   
  15.         RestTemplate template = new RestTemplate();   
  16.         ResponseEntity<String> entity = template.postForEntity(   
  17.                 "http://localhost:8080/web/simple/revisited3"null, String.class);   
  18.         String body = entity.getBody();   
  19.         MediaType contentType = entity.getHeaders().getContentType();   
  20.         System.out.println("contentType:[" + contentType + "]");   
  21.         HttpStatus statusCode = entity.getStatusCode();   
  22.         System.out.println("statusCode:[" + statusCode + "]");   
  23.     }   
  24. }  
package org.springframework.samples.mvc.simple;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class SimpleControllerRevisitedTest {

	public static void main(String[] args) {
		testPost();
	}
	
	public static void testPost() {
		RestTemplate template = new RestTemplate();
		ResponseEntity<String> entity = template.postForEntity(
				"http://localhost:8080/web/simple/revisited3", null, String.class);
		String body = entity.getBody();
		MediaType contentType = entity.getHeaders().getContentType();
		System.out.println("contentType:[" + contentType + "]");
		HttpStatus statusCode = entity.getStatusCode();
		System.out.println("statusCode:[" + statusCode + "]");
	}
}

 执行,报错:

日志代码 复制代码  收藏代码
  1. DEBUG: org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/web/simple/revisited3"  
  2. DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]   
  3. WARN : org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/web/simple/revisited3" resulted in 415 (Unsupported Media Type); invoking error handler   
  4. Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type   
  5.     at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:76)   
  6.     at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486)   
  7.     at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443)   
  8.     at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)   
  9.     at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:302)   
  10.     at org.springframework.samples.mvc.simple.SimpleControllerRevisitedTest.testPost(SimpleControllerRevisitedTest.java:16)   
  11.     at org.springframework.samples.mvc.simple.SimpleControllerRevisitedTest.main(SimpleControllerRevisitedTest.java:11)   
DEBUG: org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/web/simple/revisited3"
DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]
WARN : org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/web/simple/revisited3" resulted in 415 (Unsupported Media Type); invoking error handler
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:76)
	at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
	at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:302)
	at org.springframework.samples.mvc.simple.SimpleControllerRevisitedTest.testPost(SimpleControllerRevisitedTest.java:16)
	at org.springframework.samples.mvc.simple.SimpleControllerRevisitedTest.main(SimpleControllerRevisitedTest.java:11) 

还是报415 Unsupported Media Type,

这说明Content-Type并不是text-plain,那么到底是什么呢?新写一个方法,去掉headers属性,仅保留POST:

Java代码 复制代码  收藏代码
  1. @RequestMapping(value="/simple/revisited4", method=RequestMethod.POST)   
  2. public @ResponseBody String simple4() {   
  3.     return "Hello world 4 revisited!";   
  4. }  
	@RequestMapping(value="/simple/revisited4", method=RequestMethod.POST)
	public @ResponseBody String simple4() {
		return "Hello world 4 revisited!";
	}

 将SimpleControllerRevisitedTest中的URL改为http://localhost:8080/web/simple/revisited4, 查看服务端打印的日志(注意访问前一个URL时,服务端是不打印此日志的,因为Request Header中的Content-Type就不满足要求,直接拒掉了):

日志代码 复制代码  收藏代码
  1. DEBUG: org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Reading [[B] as "application/x-www-form-urlencoded" using [org.springframework.http.converter.ByteArrayHttpMessageConverter@1d05c9a1]  
DEBUG: org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor - Reading [[B] as "application/x-www-form-urlencoded" using [org.springframework.http.converter.ByteArrayHttpMessageConverter@1d05c9a1]

发现使用POST方式,Request Headers中的Content-Type为"application/x-www-form-urlencoded",

那么再增加一个方法:

Java代码 复制代码  收藏代码
  1. @RequestMapping(value="/simple/revisited5", method=RequestMethod.POST, headers="Content-Type=application/x-www-form-urlencoded")   
  2. public @ResponseBody String simple5() {   
  3.     return "Hello world 5 revisited!";   
  4. }  
	@RequestMapping(value="/simple/revisited5", method=RequestMethod.POST, headers="Content-Type=application/x-www-form-urlencoded")
	public @ResponseBody String simple5() {
		return "Hello world 5 revisited!";
	}

 将SimpleControllerRevisitedTest中的URL改为http://localhost:8080/web/simple/revisited5

日志代码 复制代码  收藏代码
  1. DEBUG: org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/web/simple/revisited5"  
  2. DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]   
  3. DEBUG: org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/web/simple/revisited5" resulted in 200 (OK)   
  4. DEBUG: org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain;charset=ISO-8859-1" using [org.springframework.http.converter.StringHttpMessageConverter@5f326484]   
  5. contentType:[text/plain;charset=ISO-8859-1]   
  6. statusCode:[200]  
DEBUG: org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/web/simple/revisited5"
DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]
DEBUG: org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/web/simple/revisited5" resulted in 200 (OK)
DEBUG: org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain;charset=ISO-8859-1" using [org.springframework.http.converter.StringHttpMessageConverter@5f326484]
contentType:[text/plain;charset=ISO-8859-1]
statusCode:[200]

从程序运行结果看,一切OK(注意不要被这个日志中的contentType弄混了,这个contentType是Response的Headers里的)。

总结一下,@RequestMapping中的headers有两种写法,

一种是:headers="Accept=text/plain",

另一种是:headers="Content-Type=application/x-www-form-urlencoded",

Accept指定的格式为Response Headers的Content-Type的格式,

Content-Type指定的格式为Reqeust Headers必须要满足的格式,

否则将被SpringMVC直接拒绝掉,并返回415的HTTP状态码。

大家可能会想,@RequestMapping的headers属性搞这么复杂干嘛?Spring团队应该是听到了社区群众的呼声,或者他们自己都觉得晕?因此在Spring 3.1 M2发布的时候,他们做了改进,SpringSource的Team Blog上有一篇文章 SPRING 3.1 M2: SPRING MVC ENHANCEMENTS,说道了Spring 3.1 M2版本针对以前版本的编程模型改进(programming model improvement),将第四点和第五点简而言之就是:

@RequestMapping中

headers="Content-Type=application/json"被consumes="application/json"替换了,

headers="Accept=application/json"被produces="application/json"替换了。

现在应该对上一篇文章中,我写道

后者通过指定@RequestMapping的produces属性为text/plain,字符集为UTF-8,可以避免乱码,这个是可以理解的。

 理解了,任何一个框架都是不断的在发展和完善的,如果能在学习的过程中,知道一点框架的变迁史,将会对我们的理解产生莫大的好处。

猜你喜欢

转载自zxb1985.iteye.com/blog/1814098
今日推荐