Spring中的HTTP请求与响应实体(以及 entity 与 body 的区别)
0、基本概念
报文(message):
HTTP的一个请求或响应叫做报文(message),是HTTP通信的基本单位,分为请求报文(request message)和响应报文(response message)两类。
报文由起始行(start line)、首部(header)和可选的主体(body)三部分(其实还包含header之后、body之前的空行CRLF。即使没有header或body,也应该有一个CRLF)。
首部(header):
首部分为通用首部、请求首部、响应首部、实体首部、扩展首部5类。
在Spring中,表示header的类为HttpHeaders,HttpHeaders是MultiValueMap<String, String>的子类,说明一个header可以有多个值。
主体(body):
主体是HTTP报文的载荷(payload),即HTTP要传输的内容。它包含实际的数据,对于一些请求(如GET请求)或响应可能不存在body。
主体又称为报文主体(message body)或实体主体(entity body),在Spring中用泛型表示,说明可以为任意类型。在没有传输编码时, 报文主体等于实体主体。
1、实体(entity)
实体包括实体首部(entity header)和实体主体(entity body)。entity的存在依赖于body,如果没有body,就没有了entity。
通常情况下,即在没有传输编码时,实体只有实体主体,报文主体等于实体主体。只有当传输中进行编码操作时,实体主体的内容发生变化,才导致它和报文主体产生差异。在现在的HTTP协议下,传输编码只有“Transfer-Encoding: chunked”这一种。
实体首部类型
实体首部是指那些用来描述实体主体内容的首部,它告知报文接收者body的一些信息。实体首部包括3类:
- 信息性首部:Allow(可对此body执行的请求方法),Location(即重定向中指出资源的位置)
- 内容首部:说明内容的类型、size以及其他信息,比如很多以Content开头的首部(Content-Type、Content-Length、Content-Encoding)。
- 实体缓存首部:说明如何或什么时候进行缓存,比如ETag、Expires、Last-Modified。
实体首部的位置
当报文body没有进行编码时,实体首部就位于报文首部中,是报文首部的一部分;
当报文body进行了编码,则报文首部和报文body中都会有实体首部。如下例子中,在传输中需要对body进行编码操作,以便传输表单内容:
POST /upload HTTP/1.1
Host: example.com
Content-Length: xxx
Content-Type: multipart/form-data; boundary=AaBbCcDd
--AaBbCcDd
Content-Disposition: form-data; name="username"
RuphiLau
--AaBbCcDd
Content-Disposition: form-data; name="file"; filename="picture.jpg"
Content-Type: image/jpeg
...(picture.jpg的数据)...
--AaBbCcDd--
在这个报文的首部和body中都有实体首部,而且该报文有多个实体(表单中的每段内容为一个实体),每个实体里有实体头部、实体主体,并过CR+LF分割。
entity被payload取代
另外,定义 entity 概念的RFC 2616,目前已经被 RFC 7230 到 7235 取代,术语实体(entity)被有效载荷(payload)代替。
报文(message)和有效载荷(payload)的区别比较明显,另一个容易混淆的点是 message body 和 payload body。
根据 RFC 7230:HTTP 报文的报文主体(message body)(如果存在的话)是用来运载请求或响应的有效载荷主体(payload body)的。除非应用了传输编码,报文主体等价于有效载荷主体。
以分块传输编码(Chunked transfer encoding)的一个示例来解释message body与payload body的区别:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25
This is the data in the first chunk
1C
and this is the second one
3
con
8
sequence
0
示例中的payload body为 This is the data in the first chunk、and this is the second one、con 和 sequence 这几行。而message body为第一个空行以后的所有部分,除了有效载荷主体之外,还包括了 25、1C 等行和几个空行。
Spring中的实体
在Spring中,有一个HttpEntity类,就表示HTTP请求实体,它有两个子类RequestEntity和ResponseEntity,分别表示请求实体和响应实体。
不论是请求实体还是响应实体,entity 都包含首部(header)和主体(body)(对于GET请求,body可能为空)。
2、RequestEntity
RequestEntity 的使用
RequestEntity 是用于发起HTTP请求的实体,相对于HttpEntity,它增加HTTP请求方法、URL。
RequestEntity 可以用在发起HTTP请求的客户端代码中,也可以用在处理HTTP请求的服务端代码中。
1)HTTP客户端使用:在 RestTemplate 的 exchange() 方法中使用
MyRequest body = ...
RequestEntity<MyRequest> request = RequestEntity
.post(new URI("https://example.com/bar"))
.accept(MediaType.APPLICATION_JSON)
.body(body);
ResponseEntity<MyResponse> response = template.exchange(request, MyResponse.class);
2)HTTP服务端使用
在Spring MVC中,RequestEntity 可以作为 Controller 方法的入参:
@RequestMapping("/handle")
public void handle(RequestEntity<String> request) {
HttpMethod method = request.getMethod();
URI url = request.getUrl();
String body = request.getBody();
}
RequestEntity 实例的创建
RequestEntity 可以通过普通的构造方法进行实例化,其参数最全的构造方法为:
public RequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers,
@Nullable HttpMethod method, URI url, @Nullable Type type)
RequestEntity 也可以通过Builder进行实例化。如 RequestEntity 的静态方法 get(URI url)、post(URI url) 等将会返回一个 HeadersBuilder<?> 或 BodyBuilder 实例,该实例包含了header信息,可以进一步设置其他的header。
然后调用该 Builder 的 body()(有body情况下用于指定body) 或 build() 方法,创建指定的RequestEntity实例。
// Create shared factory
UriBuilderFactory factory = new DefaultUriBuilderFactory();
// Use factory to create URL from template
URI uri = factory.uriString("https://example.com/{foo}").build("bar");
RequestEntity<MyRequest> request = RequestEntity.post(uri).accept(MediaType.APPLICATION_JSON).body(body);
3、ResponseEntity
ResponseEntity 的使用
ResponseEntity 是HTTP响应实体,相对于父类HttpEntity,它增加了响应状态码。
状态码用类HttpStatus表示,它包含了状态值(value,如200)和原因短语(reason phrase,如OK)。
ResponseEntity 可以在HTTP客户端请求数据时使用,也可以在HTTP服务端处理请求时使用。
1)HTTP客户端:通过 RestTemplate 的 getForEntity() 或 exchange() 方法请求数据时返回ResponseEntity:
ResponseEntity<String> entity = template.getForEntity("https://example.com", String.class);
String body = entity.getBody();
MediaType contentType = entity.getHeaders().getContentType();
HttpStatus statusCode = entity.getStatusCode();
2)HTTP服务端:在Spring的 Controller 方法中使用 ResponseEntity:
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
URI location = ...;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(location);
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
ResponseEntity 实例的创建
与 RequestEntity 实例的创建类似,ResponseEntity 实例既可通过普通的构造方法创建;
也可先通过对应某个响应状态的静态方法(如ok()、created(URI location)、status(int status)等),创建相应的BodyBuilder,然后进一步指定header或body,最后build出ResponseEntity实例。
参考文档
- 知乎-http报文和实体的差别?
- org.springframework.http.RequestEntity<T> 的 API 文档
- org.springframework.http.ResponseEntity<T> 的 API 文档