Java 架构师眼中的 HTTP 协议

HTTP 协议的内容比较多,本文咱们将分六部分来介绍。javascript

HTTP 协议的基本内容

什么是 HTTP 协议

首先咱们来看协议是什么?协议是指计算机通讯网络中两台计算机之间进行通讯所必须共同遵照有规则的文本格式。一但有了协议,就可使不少公司分工起来,有些公司作 Server 端,如 Tomcat,而有些公司就能够作浏览器了。这样你们只要一套约定,彼此的通信就会相互兼容。css

接下来咱们看什么是 HTTP?HTTP 是基于 TCP/IP 的应用层通讯协议,它是客户端和服务器之间相互通讯的标准。它规定了如何在互联网上请求和传输内容。经过应用层协议,个人意思是,它只是一个规范了主机(客户端和服务器)如何通讯的抽象层,而且它自己依赖于 TCP/IP 来获取客户端和服务器之间的请求和响应。默认的 TCP 端口是80端口,固然,使用其余端口也是能够的。然而,HTTPS 使用的端口是443端口。html

HTTP 协议的简单历史

http 协议历史

根据上图,咱们可将 HTTP 协议的发展历程分为五个阶段。java

第一阶段,1996年以前。初版的 HTTP 文档是1991年提出来的 HTTP/0.9,其主要特色有:(1)它仅有一个 GET 方法。(2)没有 header 数据块。(3)必须以HTML格式响应。git

第二阶段,HTTP/1.0 - 1996。HTML 格式响应,HTTP/1.0 可以处理其余的响应格式,例如:图像、视频文件、纯文本或其余任何的内容类型(Content-Type 来区分)。它增长了更多的方法(即 POST 和 HEAD),请求/响应的格式也发生了改变,请求和响应中均加入了 HTTP 头信息,响应数据还增长了状态码标识,还介绍了字符集的支持、多部分发送、权限、缓存、内容编码等不少内容。HTTP/1.0 的主要缺点之一是,你不能在每一个链接中发送多个请求。也就是说,每当客户端要向服务器端请求东西时,它都会打开一个新的 TCP 链接,而且在这个单独请求完成后,该链接就会被关闭。每一次链接里面都包含了著名的三次握手协议。因而有些 HTTP/1.0 的实现试图经过引入一个新的头信息 Connection: keep-alive,来解决这个问题。web

第三个阶段,HTTP/1.1 - 1999。HTTP/1.0 发布以后,随着 HTTP 开始普及以后,它的缺点也开始展示。时隔三年,HTTP/1.1 便在1999年问世,它在以前的基础上作了不少的改进。主要内容包含:面试

  • 新增的 HTTP 方法有 PUT、PATCH、HEAD、OPTIONS、DELETE。
  • 主机名标识。在 HTTP/1.0 中,Host 头信息不是必须项,但 HTTP/1.1 中要求必需要有 Host 头信息。
  • 持久性链接。正如前面所说,在 HTTP/1.0 中每一个链接只有一个请求,且在这个请求完成后该链接就会被关闭,从而会致使严重的性能降低及延迟问题。HTTP/1.1 引入了对持久性链接的支持,例如:默认状况下链接不会被关闭,在多个连续的请求下它会保存链接的打开状态。想要关闭这些链接,须要将 Connection: close 加入到请求的头信息中。客户端一般会在最后一次请求中发送这个头信息用来安全的关闭链接。
  • 管道机制。HTTP/1.1 也引入了对管道机制的支持,客户端能够向服务器发送多个请求,而无需等待来自同一链接上的服务器响应,而且当收到请求时服务器必须以相同的顺序来响应。但你可能会问客户端是怎么知道第一个响应下载完成和下一个响应内容开始的?要解决这个问题,必需要有 Content-Length 头信息,客户端能够用它来肯定响应结束,而后开始等待下一个响应。

第四个阶段,SPDY - 2009。Google 走在前面,它开始试验一种可替换的协议来减小网页的延迟,使得网页加载更快、提高 Web 安全性。2009年,他们称这种协议为 SPDY。SPDY 的功能包含多路复用、压缩、优先级、安全等。2015年,谷歌不想存在两个相互竞争的标准,所以他们决定把它合并到 HTTP 中成为 HTTP/2,同时放弃 SPDY。spring

第五个阶段,HTTP/2 - 2015。HTTP/2 是专为低延迟传输的内容而设计。关键特征或与 HTTP / 1.1 旧版本的差别,以下。sql

  • 二进制协议。HTTP/2 倾向于使用二进制协议来减小 HTTP/1.x 中的延迟。二进制协议更容易解析,而不具备像 HTTP/1.x 中那样对人的可读性。HTTP/2 中的数据块是帧和流。
    帧和流:

HTTP 消息是由一个或多个帧组成的。有一个叫作 HEADERS 的帧存放元数据,真正的数据是放在 DATA 帧中的,帧类型定义在the HTTP/2 specs(HTTP/2规范),如 HEADERS、DATA、RST_STREAM、SETTINGS、PRIORITY 等。每一个 HTTP/2 请求和响应都被赋予一个惟一的流 ID 且放入了帧中。帧就是一块二进制数据。一系列帧的集合就称为流。每一个帧都有一个流 id,用于标识它属于哪个流,每个帧都有相同的头。同时,除了流标识是惟一的,值得一提的是,客户端发起的任何请求都使用奇数和服务器的响应是偶数的流 id。除了 HEADERS 和 DATA, 另一个值得说一说帧类型是 RST_STREAM,它是一个特殊的帧类型,用于停止流,如客户端发送这儿帧来告诉服务器我再也不须要这个流了。在 HTTP/1.1 中只有一种方式来实现服务器中止发送响应给客户端,那就是关闭链接引发延迟增长,由于后续的请求就须要打开一个新的链接。 在 HTTP/2 中,客户端可使用 RST_FRAME 来中止接收指定的流而不关闭链接且还能够在此链接中接收其它流。apache

  • 多路复用。因为 HTTP/2 如今是一个二进制协议,且是使用帧和流来实现请求和响应,一旦 TCP 链接打开了,全部的流都经过这一链接来进行异步的发送而不须要打开额外的链接。反过来,服务器的响应也是异步的方式,如响应是无序的、客户端使用流 id 来标识属于流的包。这就解决了存在于 HTTP/1.x 中 head-of-line 阻塞问题,如客户端将没必要耗时等待请求,而其余请求将被处理。以下图所示。

http2.0 Multiplexing

  • HPACK 头部压缩。它是一个单独的用于明确优化发送 Header RFC 的一部分。它的本质是,当咱们同一个客户端不断的访问服务器时,在 header 中发送不少冗余的数据,有时 cookie 就增大 header,且消耗带宽和增长了延迟。为了解决这个问题, HTTP/2 引入了头部压缩。与请求和响应不一样,header 不是使用 gzip 或 compress 等压缩格式,它有不一样的机制,它使用了霍夫曼编码和在客户端和服务器维护的头部表来消除重复的 headers(如 User Agent),在后续的请求中就只使用头部表中引用。它与 HTTP/1.1 中的同样,不过增长了伪 header,如 :method、:scheme、:host 和:path。

  • 服务器推送。在服务器端,Server Push 是 HTTTP/2 的另一个重要功能,咱们知道,客户端是经过请求来获取资源的,它能够经过推送资源给客户端而不需客户端主动请求。例如,浏览器载入了一个页面,浏览器解析页面时发现了须要从服务器端载入的内容,接着它就发送一个请求来获取这些内容。Server Push容许服务器推送数据来减小客户端请求。它是如何实现的呢,服务器在一个新的流中发送一个特殊的帧 PUSH_PROMISE,来通知客户端:“嘿,我要把这个资源发给你!你就不要请求了。”

  • 请求优先级。客户端能够在一个打开的流中在流的 HEADERS 帧中放入优先级信息。在任什么时候间,客户端均可以发送一个 PRIORITY 的帧来改变流的优先级。若是没有优先级信息,服务器就会异步的处理请求,好比无序处理。若是流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定须要多少资源来处理该请求。

  • 安全。你们对 HTTP/2 是否强制使用安全链接(经过 TLS)进行了充分的讨论。最后的决定是不强制使用。然而,大多数厂商表示,他们将只支持基于 TLS 的 HTTP/2。因此,尽管 HTTP/2 规范不须要加密,但它已经成为默认的强制执行的。在这种状况下,基于 TLS 实现的 HTTP/2 须要的 TLS 版本最低要求是1.2。 所以必须有最低限度的密钥长度、临时密钥等。

经过开发者工具咱们看一下Google的请求协议。

enter image description here

而咱们大多数的网站的协议的版本都是 HTTP 1.1。

enter image description here

HTTP 协议的具体内容

而咱们平时老生常谈的 HTTP 的协议大都是指的是 HTTP 1.1 协议的内容,接下去咱们一块儿看一下 HTTP 1.1 协议的结构。以下图所示。enter image description here

接下来,我将经过四部分大概介绍一下 HTTP 协议的基本内容。

1.URL & URI

enter image description here

schema://host[:port#]/path/.../[;url-params][?query-string][#anchor] 

URL(Uniform Resource Locator)主要包括如下几部分。

  • scheme:指定低层使用的协议,通常是 HTTP,若是强调安全的话能够是 HTTPS。
  • host:HTTP 服务器的 IP 地址或者域名。
  • port:HTTP 服务器的默认端口是80,这种状况下端口号能够省略。若是使用了别的端口,必须指明。
  • path:访问资源的路径。
  • url-params:URL 的参数。
  • query-string:发送给 HTTP 服务器的数据。
  • anchor:锚。

URI,在 Java 的 Servlet 中指的是 resource path 部分。

2.请求方法 Method

主要包括如下几种请求方法。

  • GET:向指定的资源发出“显示”请求。使用 GET 方法应该只用在读取数据,而不该当被用于产生“反作用”的操做中,例如在 Web Application 中。其中一个缘由是 GET 可能会被网络蜘蛛等随意访问。
  • POST:向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会建立新的资源或修改现有资源,或两者皆有。
  • PUT:向指定资源位置上传其最新内容。
  • DELETE:请求服务器删除 Request-URI 所标识的资源。
  • OPTIONS:这个方法可以使服务器传回该资源所支持的全部 HTTP 请求方法。用“*”来代替资源名称,向 Web 服务器发送 OPTIONS 请求,能够测试服务器功能是否正常运做。
  • HEAD:与 GET 方法同样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法能够在没必要传输所有内容的状况下,就能够获取其中“关于该资源的信息”(元信息或称元数据)。
  • TRACE:回显服务器收到的请求,主要用于测试或诊断。
  • CONNECT:HTTP/1.1 协议中预留给可以将链接改成渠道方式的代理服务器。一般用于 SSL 加密服务器的连接(经由非加密的 HTTP 代理服务器)。

Method 名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码 405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码 501(Not Implemented)。

3.HTTP 之状态码

状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:

  • 1xx:指示信息--表示请求已接收,继续处理。
  • 2xx:成功--表示请求已被成功接收、理解、接受。
  • 3xx:重定向--要完成请求必须进行更进一步的操做。
  • 4xx:客户端错误--请求有语法错误或请求没法实现。
  • 5xx:服务器端错误--服务器未能实现合法的请求。

常见状态码有:

200 OK //客户端请求成功 400 Bad Request //客户端请求有语法错误,不能被服务器所理解 401 Unauthorized //请求未经受权,这个状态代码必须和WWW-Authenticate报头域一块儿使用 403 Forbidden //服务器收到请求,可是拒绝提供服务 404 Not Found //请求资源不存在,eg:输入了错误的URL 500 Internal Server Error //服务器发生不可预期的错误 503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常 

4.请求体&响应体

请求体&响应体,这个没有特殊规定,须要配合不一样的 Content-Type 来使用。

惟一须要注意的是 multipart/form-data、application/x-www-from-urlencoded、raw、binary 的区别。

(1)multipart/form-data

它将表单的数据组织成 Key-Value 形式,用分隔符 boundary(boundary 可任意设置)处理成一条消息。因为有 boundary 隔离,因此立即上传文件,又有参数的时候,必需要用这种 content-type 类型。以下图所示。

enter image description here

(2)x-www-form-urlencoded

即 application/x-www-from-urlencoded,将表单内的数据转换为 Key-Value。这种和 Get 方法把参数放在 URL 后面同样的想过,这种不能文件上传。

enter image description here

(3)raw

能够上传任意格式的“文本”,能够上传 Text、JSON、XML、HTML 等。

enter image description here

(4)binary

即 Content-Type:application/octet-stream,只能够上传二进制数据流,一般用来上传文件。因为没有键值,因此一次只能上传一个文件。

(5)Header

enter image description here

HTTP 消息的 Headers 共分为三种,分别是 General Headers、Entity Headers、Request/Response Headers。

  • General Headers

我把被 Request 和 Response 共享的 Headers 成为General Headers,具体有:

general-header = Cache-Control | Connection | Date | Pragma | Trailer | Transfer-Encoding | Upgrade | Via | Warning 

其中,Cache-Control 指定请求和响应遵循的缓存机制;Connection 容许客户端和服务器指定与请求/响应链接有关的选项;Date 提供日期和时间标志,说明报文是什么时间建立的;Pragma 头域用来包含实现特定的指令,最经常使用的是 Pragma:no-cache;Trailer,若是报文采用了分块传输编码(chunked transfer encoding) 方式,就能够用这个首部列出位于报文拖挂(trailer)部分的首部集合;Transfer-Encoding 告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式;Upgrade 给出了发送端可能想要“升级”使用的新版本和协议;Via 显示了报文通过的中间节点(代理,网嘎un)。

  • Entity Headers

Entity Headers 主要用来描述消息体(message body)的一些元信息,具体有:

entity-header  = Allow                   
               | Content-Encoding | Content-Language | Content-Length | Content-Location | Content-MD5 | Content-Range | Content-Type | Expires | Last-Modified 

其中,以 Content 为前缀的 Headers 主要描述了消息体的结构、大小、编码等信息,Expires 描述了 Entity 的过时时间,Last-Modified 描述了消息的最后修改时间。

  • Request/Response Headers

Request-Line 是 Request 消息体的第一部分,其具体定义以下:

Request-Line = Method SP URI SP HTTP-Version CRLF
Method = "OPTIONS" | "HEAD" | "GET" | "POST" | "PUT" | "DELETE" | "TRACE" 

其中 SP 表明字段的分隔符,HTTP-Version 通常就是"http/1.1",后面紧接着是一个换行。

在 Request-Line 后面紧跟着的就是 Headers。咱们在上面已经介绍了 General Headers 和 Entity Headers,下面即是 Request Headers的定义。

request-header = Accept                   
               | Accept-Charset | Accept-Encoding | Accept-Language | Authorization | Expect | From | Host | If-Match | If-Modified-Since | If-None-Match | If-Range | If-Unmodified-Since | Max-Forwards | Proxy-Authorization | Range | Referer | TE | User-Agent 

Request Headers 扮演的角色其实就是一个 Request 消息的调节器。须要注意的是若一个 Headers 名称不在上面列表中,则默认当作 Entity Headers 的字段。前缀为 Accept 的 Headers 定义了客户端能够接受的媒介类型、语言和字符集等。From、Host、Referer 和 User-Agent 详细定义了客户端如何初始化 Request。前缀为 If 的 Headers 规定了服务器只能返回符合这些描述的资源,若不符合,则会返回 304 Not Modified。

Request Body,若 Request-Line 中的 Method 为 GET,请求中不包含消息体,若为 POST,则会包含消息体。

一个具体的 Request 消息实例,以下。

GET /articles/http-basics HTTP/1.1 Host: www.articles.com Connection: keep-alive Cache-Control: no-cache Pragma: no-cache Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
  • Response 消息体

Response 消息格式和 Request 相似,也分为三部分,即 Response-Line、Response Headers、Response Body。

Response-Line 具体定义以下:

Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
HTTP-Version字段值通常为HTTP/1.1
Status-Code前面已经讨论过了
Reason-Phrase 是对status code的具体描述

一个最多见的 Response 响应为:

HTTP/1.1 200 OK 

Response Headers的定义以下。

response-header = Accept-Ranges
                | Age | ETag | Location | Proxy-Authenticate | Retry-After | Server | Vary | WWW-Authenticate 

其中,Age 表示消息自 server 生成到如今的时长,单位是秒;ETag 是对 Entity 进行 MD5 hash 运算的值,用来检测更改;Location 是被重定向的 URL;Server 表示服务器标识。

Http 更加详细的介绍,请参考这里 。

架构师关注 HTTP 协议的重点

HTTP 协议内容其实也挺多的,架构师其实也应该有重点,哪些是咱们必须重点关注的,内心要清楚。

采用哪一个 HTTP 协议版本及其 Java 里面如何配置

1.Tomcat 的原始配置在 server.xml 里面。

<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" > <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> <SSLHostConfig> <Certificate certificateKeyFile="conf/localhost-rsa-key.pem" certificateFile="conf/localhost-rsa-cert.pem" certificateChainFile="conf/localhost-rsa-chain.pem" type="RSA" /> </SSLHostConfig> </Connector> 

2.Spring boot2.0 项目的配置方法。

只须要在 properties 里面选择以下配置便可。

server.ssl.enabled=true server.ssl.****//等等 server.http2.enabled=true server.tomcat.protocol-header-https-value=https 

须要注意的是通常 HTTP 2的使用都要伴随着证书。详细的配置直接参考 ServerProperties 类里面的具体描述便可。

HTTPS 配置注意事项

通常不多针对单个项目去设置的,通常都是经过开源的云如 ali、asw里面的 SLB 配置 HTTP2和证书,在网关那层统一作掉。客户端通常是各个浏览器或者 App 的浏览器内核库来支持的。其实也不多须要开发来关心具体如何按照 HTTP2 来实现一些代码逻辑。

缓存机制 HTTP 缓存

1.如何缓存

下降网络上发送 HTTP 请求的次数,这里采用“过时”机制。

HTTP 服务器经过两种实体头(Entity-Header)来实现“过时”机制:Expires 头和 Cache-Control 头的 max-age 子项。

Expires/Cache-Control 控制浏览器是否直接从浏览器缓存取数据仍是从新发请求到服务器取数据。只是 Cache-Control 比 Expires 能够控制的多一些,并且 Cache-Control 会重写 Expires 的规则。

下降网络上完整回复 HTTP 请求包的次数,这里采用“确证”机制。

HTTP服务器经过两种方式实现“确证”机制:ETag 以及 Last-Modified。

2.相关的 Header

主要包括如下几个。

  • Cache-Control

经常使用的值有:

(1)max-age(单位为 s)指定设置缓存最大的有效时间,定义的是时间长短。当浏览器向服务器发送请求后,在 max-age 这段时间里浏览器就不会再向服务器发送请求了。 (2)s-maxage(单位为 s)同 max-age,只用于共享缓存(好比 CDN 缓存),也就是说 max-age 用于普通缓存,而 s-maxage 用于代理缓存。若是存在 s-maxage,则会覆盖掉 max-age 和 Expires header。 (3)public 指定响应会被缓存,而且在多用户间共享。若是没有指定 public 仍是 private,则默认为 public。 (4)private 响应只做为私有的缓存,不能在用户间共享。若是要求 HTTP 认证,响应会自动设置为 private。 (5)no-cache 指定不缓存响应,代表资源不进行缓存,好比,设置了 no-cache 以后并不表明浏览器不缓存,而是在缓存前要向服务器确认资源是否被更改。所以有的时候只设置 no-cache 防止缓存仍是不够保险,还能够加上 private 指令,将过时时间设为过去的时间。 (6)no-store 表示绝对禁止缓存。一看就知道,若是用了这个命令,固然就是不会进行缓存啦!每次请求资源都要从服务器从新获取。 (7)must-revalidate 指定若是页面是过时的,则去服务器进行获取。这个指令并不经常使用,就不作过多的讨论了。

  • Expires

缓存过时时间,用来指定资源到期的时间,是服务器端的具体时间点。也就是说,Expires=max-age + 请求时间,须要和 Last-modified 结合使用。但在上面咱们提到过 cache-control 的优先级更高。Expires 是 Web 服务器响应消息头字段,在响应 HTTP 请求时告诉浏览器在过时时间前浏览器能够直接从浏览器缓存取数据,而无需再次请求。

  • Last-modified

服务器端文件的最后修改时间,须要和 cache-control 共同使用,是检查服务器端资源是否更新的一种方式。当浏览器再次进行请求时,会向服务器传送 If-Modified-Since 报头,询问 Last-Modified 时间点以后资源是否被修改过。若是没有修改,则返回码为304,使用缓存;若是修改过,则再次去服务器请求资源,返回码和首次请求相同为200,资源为服务器最新资源。

  • Etag

根据实体内容生成一段 hash 字符串,标识资源的状态,由服务端产生。浏览器会将这串字符串传回服务器,验证资源是否已经修改。

为何要使用 Etag 呢?Etag 主要为了解决 Last-Modified 没法解决的一些问题。

一些文件也许会周期性的更改,可是它的内容并不改变(仅仅改变的修改时间),这个时候咱们并不但愿客户端认为这个文件被修改了,而从新 Get。

某些文件修改很是频繁,好比在秒如下的时间内进行修改(比方说1s内修改了 N 次),If-Modified-Since 能检查到的粒度是 s 级的,这种修改没法判断(或者说 UNIX 记录 MTIME 只能精确到秒)。

某些服务器不能精确的获得文件的最后修改时间。

缓存过程以下图所示。
enter image description here

Session 与 Cookie 必知必会

很好的解决了 HTTP 通信中状态问题,但其自己也存在一些问题,好比:

  • 客户端存储,可能会被修改或删除。
  • 发送请求时,Cookie 会被一块儿发送到服务器,当 Cookie 数据量较大时也会带来额外的请求数据量。
  • 客户端对 Cookie 数量及大小有必定的限制,Session 解决了 Cookie 的一些缺点。Session 一样是为了记录用户状态,对于每一个用户来讲都会有相应的一个状态值保存在服务器中,而只在客户端记录一个 sessionID 用于区分是哪一个用户的 Session。

与 Cookie 相比,Session有必定的优点,如:

  • Session 值存储在服务器,相对来讲更安全。
  • 客户端发送给服务器的只有一个 sessionID,数据量更小。Session一样须要在客户端存储一个 sessionID。能够这个值存储在 Cookie,每次发送请求时经过 Cookie 请求头将其发送到服务器;也能够不使用 Cookie,而将 sessionID 做为一个额外的请求参数,经过 URL 或请求体发送到服务器。

基于 Cookie 实现 Session 的实现原理以下图的示。

enter image description here

由上可见,基于 Cookie 实现 Session 时,其本质上仍是在客户端保存一个 Cookie 值。这个值就是 sessionID,sessionID 的名称也可按须要设置,为保存安全,其值也可能会在服务器端作加密处理。服务器在收到 sessionID 后,就能够对其解密及查找对应的用户信息等。

协议格式如何统一(见文章后面内容)

Spring 对 HTTP 协议的支持

我为何想提一下这个呢,我看到太多的开发者遇到 HTTP 协议都喜欢自定义变量,自定义类,其实彻底没有必要。且看下面的分析。

Spring MVC Web

在 spring-web**.jar 里面咱们能够找到以下几个类:

enter image description here

须要咱们重点关注的有 HttpStatus、MediaType等。

enter image description here

Spring web bind中对应的注解,以下图所示。

enter image description here

上面是一些主要的(须要注意的是 @RequestParam、@PostMapping、@****Mapping 与咱们上面的 HttpMethod 相对应。而里面还有 Headers、Consumes 和 produces 等参数来肯定一些 Mapping 的条件),下面的能够根据须要查看源码里面有哪些注解。

enter image description here

Spring Data Rest

Spring Data Rest 是基于 Spring Data repositories,分析实体之间的关系。为咱们生成Hypermedia API(HATEOAS)风格的Http Restful API接口。

Spring Data REST经过构建在Spring Data repositories之上,自动将其导出为REST资源的api,减小了大量重复代码和无聊的样板代码。它利用超媒体来容许客户端查找存储库暴露的功能,并将这些资源自动集成到相关的超媒体功能中。

Spring Data REST自己就是一个Spring MVC应用程序,它的设计方式应该是尽量少的集成到现有的Spring MVC应用程序中。现有的(或未来的)服务层能够与Spring Data REST一块儿运行,只有较小的考虑。

Spring RestTemplate 的实际使用

Spring RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 HTTP 服务的方法,可以大大提升客户端的编写效率。

简单例子,以下。

RestTemplate restTemplate = new RestTemplate(); String fooResourceUrl = "http://localhost:8080/spring-rest/foos"; ResponseEntity<String> response = restTemplate.getForEntity(fooResourceUrl + "/1", String.class); assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); 

更详尽例子,以下。

/** * 发送一个get请求,并接受封装成map */ @Test public void restTemplateMap() { RestTemplate restTemplate = new RestTemplate(); Map map=restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN",Map.class); System.out.println(map.get("errmsg")); } /** * 发送一个get请求,并接受封装成string */ @Test public void restTemplateString() { RestTemplate restTemplate = new RestTemplate(); String str=restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN",String.class); System.out.println(str); } /** * 添加消息头 */ @Test public void httpHeaders() { final String uri = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN"; RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); HttpEntity<String> entity = new HttpEntity<String>("parameters", headers); ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class); System.out.println(result); } 

咱们在实际工做中会覆盖默认的 RestTemplate。

/** * 替代默认的SimpleClientHttpRequestFactory * 设置超时时间重试次数 * 还能够设置一些拦截器以便监控 * * @return */ @Bean public RestTemplate restTemplate() { //生成一个设置了链接超时时间、请求超时时间、异常重试次数3次 RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build(); //实际工做中,这个地方还会加上filter来抓取每次restTemplate的日志信息。 HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false)); HttpClient httpClient = builder.build(); ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); RestTemplate restTemplate = new RestTemplate(requestFactory); return restTemplate; } 

使用的地方会变成以下便可。

@Autowired public RestTemplate restTemplate; 

Spring Data Jpa

随着使用 Spring Data Jpa 的人愈来愈多,它里面也对 Spring Web 作了很好的支持。

enter image description here

咱们能够重点看一下 Pageable 和 Page,以及 PageImpl 和 PageRequest 对分页和排序作了很好的封装,及其返回的 JSON 格式也作了很好的约定。

Spring Cloud 中的知识点

Spring Cloud 的微服务管理都是基于 HTTP 协议的 Rest 风格的 API 来管理的,因此咱们详细了解 HTTP 协议仍是很是有必要的。

enter image description here

JSON API

咱们都知道了约定的好处。若是你和你的团队曾经争论过使用什么方式构建合理 JSON 响应格式, 那么 JSON API 就是你的 anti-bikeshedding 武器。

经过遵循共同的约定,能够提升开发效率,利用更广泛的工具,能够是你更加专一于开发重点:你的程序。

点击这里,访问 JSON API 官方。

(1)JSON API 介绍

JSON API 是数据交互规范,用以定义客户端如何获取与修改资源,以及服务器如何响应对应请求。

JSON API 设计用来最小化请求的数量,以及客户端与服务器间传输的数据量。在高效实现的同时,无需牺牲可读性、灵活性和可发现性。

JSON API 须要使用 JSON API 媒体类型(application/vnd.api+json)进行数据交互。

JSON API 服务器支持经过 GET 方法获取资源。并且必须独立实现 HTTP POST,PUT 和 DELETE 方法的请求响应,以支持资源的建立、更新和删除。

JSON API 服务器也能够选择性支持 HTTP PATCH 方法 [RFC5789]和 JSON Patch 格式 [RFC6902],进行资源修改。JSON Patch 支持是可行的,由于理论上来讲,JSON API 经过单一 JSON 文档,反映域下的全部资源,并将 JSON 文档做为资源操做介质。在文档顶层,依据资源类型分组。每一个资源都经过文档下的惟一路径辨识。

(2)规则约定

文档中的关键字MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、 SHOULD NOT、RECOMMENDED、MAY 和 OPTIONAL。依据 RFC 2119 [RFC2119] 规范解释。

(3)内容约定

客户端职责,即客户端必须在包含 Content-Type: application/vnd.api+json 头而且不包含媒体类型参数的请求文档中发送全部 JSON API 数据。在 Accept 头中包含 JSON API 媒体类型而且不包含媒体类型参数的客户端必须在 Accept 头中指定媒体类型至少一次。

客户端必须忽略任何从响应文档的 Content-Type 头中获取的 application/vnd.api+json 媒体类型参数。

服务器职责,即服务器必须在包含 Content-Type: application/vnd.api+json 头而且不包含媒体类型参数的请求文档中发送全部 JSON API 数据。若是接收到一个用任何媒体类型参数指定 Content-Type: application/vnd.api+json 头的请求,服务器必须返回一个 415 Unsupported Media Type 状态码响应。若是接收到一个在 Accept 头中包含任何 JSON API 媒体类型而且全部实体都以媒体类型参数更改的请求,服务器必须返回一个 406 Not Acceptable状态码响应。

(4)文档结构

咱们将描述 JSON API 文档结构 ,经过媒体类型 application/vnd.api+json 标示。JSON API 文档使用 JavaScript 对象(JSON)[RFC4627]定义。

尽管同种媒体类型用以请求和响应文档,但某些特性只适用于其中一种。差别在下面呈现。

除非另有说明,根据本规范定义的对象都不该该包含任何其余键。客户端和服务器实现必须忽略本规范未指定的键。

(5)Top Level

JSON 对象必须位于每一个 JSON API 文档的根级。这个对象定义文档的“top level”。

文档必须包含如下至少一种 top-level 键。

  • data: 文档的”primary data”。
  • errors: 错误对象列表。
  • meta: 包含非标准元信息的元对象。

data 键和 errors 键不能再一个文档中同时存在。

文档可能包含如下任何 top-level 键。

  • jsonapi: 描述服务器实现的对象。
  • links: 与primary data相关的连接对象。
  • include: 与 primary data 或其余资源相关的资源对象(included resources)列。

若是文档不包含 top-level data 键,included 键也不该该出现。

文档的 top-level 连接对象可能包含如下键。

  • self: 生成当前响应文档的连接。
  • related: 当primary data表明资源关系时,表示相关资源连接。
  • Primary data的分页连接。

文档中的“primary data”表明一个请求所要求的资源或资源集合。

Primary data 必须是如下列举的一种。

  • 若是请求要求单一资源,应该是一个单一资源对象,或一个单一资源标识符,或 null。
  • 若是请求要求资源集合,应该是一个资源对象列表,或一个空列表([])。

例如,如下 primary data 表示一个单一资源对象。

{
  "data": { "type": "articles", "id": "1", "attributes": { // ... this article's attributes }, "relationships": { // ... this article's relationships } } } 

如下 primary data 表示一个指向一样资源的单一资源标识符。

{
  "data": { "type": "articles", "id": "1" } } 

即便只包含一个元素或为空,资源的一个逻辑集合也必须表示为一个列表。

资源对象,即 JSON API 文档中的“Resuorce objects”,表明资源。

一个资源对象必须至少包含如下 top-level 键。

  • id
  • `type'

例外,当资源对象来自客户端而且表明一个将要在服务器建立的新资源时,id键不是必须的。

此外,资源对象可能包含如下 top-level 键。

  • 'attribute': 属性对象表明资源的某些数据。
  • relationshiops: 关联对象描述该资源与其余JSON API资源之间的关系。
  • links: 连接资源包含与资源相关的连接。
  • meta: 元数据资源包含与资源对象相关的非标准元信息,这些信息不能被做为属性或关联对象表示。

一篇文献(即一个“文献”类型的资源)在文档中这样表示:

{
  "type": "articles", "id": "1", "attributes": { "title": "Rails is Omakase" }, "relationships": { "author": { "links": { "self": "/articles/1/relationships/author", "related": "/articles/1/author" }, "data": { "type": "people", "id": "9" } } } } 

标识符,即每一个资源对象包含一个 id 键和一个 type 键。id 键和 typ e键的值必须是字符串类型。

对于每个既定 API,每一个资源对象的 type 和 id 对必须定义一个单独且惟一的资源(由一个或多个但行为表现为一个服务器的服务器控制的 URI 集合构成一个API)。

type 键用于描述共享相同属性和关联的资源对象。type键的值必须与遵循键名称相同的约束条件。

字段,即资源对象的属性和关联被统称为“fields”。

一个资源对象的全部字段必须与 type 和 id 在同一命名空间中。即一个资源不能拥有名字相同的属性与关联,也不能拥有被命名为 type 或 id 的属性和关联。

属性,即 attribute,键的值必须是一个对象(一个“attributes object”)。属性对象的键(“attributes”)表明与资源对象中定义的与其有关的信息。

属性能够包含任何合法 JSON 值。JSON 对象和列表涉及的复杂数据结构能够做为属性的值。可是一个组成或被包含于属性中的对象不能包含 relationships 或 links 键,由于这些键为此规范将来的用途所预留。

虽然一些 has-one 关系的外键(例如author_id)被在内部与其余将要在资源对象中表达的信息一块儿储存,可是这些键不能做为属性出现。

关联,即relationships,键的值必须是一个对象(“relationships object”)。关联对象(“relationships”)的键表示在资源对象中定义的与其相关的其余资源对象。

关联能够是单对象关联或多对象关联。

一个“relationship object”必须包含如下至少一种键:

  • links: 一个连接对象至少包含如下一种键:
    • self: 指向关联自己的连接(“relationship link”)。此连接容许客户端直接修改关联。例如,经过一个 articale 的关联 URL 移除一个 author 将会解除一我的与 article 的关系,而不须要删除这个 people 资源自己。获取成功后,这个连接将返回一个相关资源之间的链接,将其做为 primary data(见获取关联)。
    • related: 相关资源连接。
  • data: 资源链接。
  • meta: 包含关于此关联的非标准元信息的元对象。

更多介绍见官方文档

Yahoo elide 对 JSON API 的支持

点击这里,访问 Yahoo elide官方网站。

1. elide介绍

elide 经过 Spring Data Jpa 的 Entity,加上自定义的 @Include(rootLevel = true) 注解,来完成 JSON API 标准的输出。

使用方法以下图所示。

enter image description here

效果以下:

enter image description here

生产环境中 HTTP 协议有哪些架构

RestTemplate 的重试和监控

代码以下。

@SpringBootApplication public class DubbingApiApplication { /** * 使用全局的RestTemplate * 设置了链接超时时间、请求超时时间、异常重试次数3次 * 而且会记录全部请求的详细日志 * * @return */ @Bean public RestTemplate restTemplate() { RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build(); HttpClentBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false)); HttpClient httpClient = builder.build(); ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); RestTemplate restTemplate = new RestTemplate(requestFactory); restTemplate.setInterceptors(Collections.singletonList(new LoggingRestTemplate())); restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory()); return restTemplate; } public static void main(String[] args) { SpringApplication.run(DubbingApiApplication.class, args); } } 

请求格式和返回结果的格式约定。

1.实现 ResponseBodyAdvice 对 controller 返回的 Result 进行统一的包装,以下代码。

public class ElideResponseBodyAdvice implements ResponseBodyAdvice { @Autowired private ElideProperties elideProperties; /** * 配置注解能够跳过去,类上,方法上都行 * * @param returnType * @param converterType * @return */ @Override public boolean supports(MethodParameter returnType, Class converterType) { if (converterType != null && converterType.isAssignableFrom(StringHttpMessageConverter.class)) { return false; } ElideSkippable elideSkippable = returnType.getMethodAnnotation(ElideSkippable.class); if (elideSkippable == null) { elideSkippable = returnType.getDeclaringClass().getAnnotation(ElideSkippable.class); } return !(elideSkippable != null && elideSkippable.value()); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest(); //only process elide persistable return ResultElideWrapHandler.process(httpServletRequest, body); } } 

2.异常统一格式的处理,返回固定的 error 级别的格式结果,以下代码。

/** * 框架级别的通用异常处理 */ @ControllerAdvice @Slf4j @Order(4) public class ExceptionAdvice { @ExceptionHandler({BindException.class}) @ResponseBody public JsonApiErrorDocument exception(BindException e, HttpServletResponse response) { log.warn(e.getMessage(), e); ErrorResponse errors = new ErrorResponse(Constant.ERROR_VALIDATION, response.getStatus(), e.getMessage()); errors.setMeta(e.getAllErrors()); return new JsonApiErrorDocument(errors); } } 

对缓存 Etag 的支持。

针对 HTTP 里 Etag 的支持,也须要咱们框架层面去支持 Etag 缓存,这里就不给你们贴代码了,你们能够思考一下。

微服务中 HTTP 与 RPC 的权衡

HTTP 与 RPC 比较

以下表所示。

比较项 HTTP RPC
微服务治理 Spring Cloud ali dubbo
耦合度 代码无耦合 service jar依赖
api升级 彻底无影响 须要注意java Serializable的使用
周边开源监控产品
开发效率 更快
语言要求 能够不一个体系的语音 只能同一个体系如Java
性能 能够 很是好
带宽 http<http1.1<http2.0 更小带宽

实际工做中建议二者都用,API 对外,Ali Dubbo的 RPC 对内部使用,这样两个的优势都能使用到。

总结:面试中起到的关键做用是什么

若是面试中问到你这个问题,主要的考验的点有:

  • 思路是否清晰。
  • 实战解决那些问题如缓存可能会让你说的很是细。
  • 知识是否全面,及其是否针对一个问题了解的足够多。

基本上面的东西若是你都能提到,面试官的印象基本上是很是好的,加分会加不少。

交流:欢迎你们留言,把本身碰到的 Java 数据结构问题一块儿讨论。也可加入 QQ 交流群(群1:240619787,群2:559701472)。


本文首发于GitChat,未经受权不得转载,转载需与GitChat联系。

http://gitbook.cn/books/5a82b227e2f8ea02e7742197/index.html

相关文章
相关标签/搜索