HTTP 协议的内容比较多,本文咱们将分六部分来介绍。javascript
首先咱们来看协议是什么?协议是指计算机通讯网络中两台计算机之间进行通讯所必须共同遵照有规则的文本格式。一但有了协议,就可使不少公司分工起来,有些公司作 Server 端,如 Tomcat,而有些公司就能够作浏览器了。这样你们只要一套约定,彼此的通信就会相互兼容。css
接下来咱们看什么是 HTTP?HTTP 是基于 TCP/IP 的应用层通讯协议,它是客户端和服务器之间相互通讯的标准。它规定了如何在互联网上请求和传输内容。经过应用层协议,个人意思是,它只是一个规范了主机(客户端和服务器)如何通讯的抽象层,而且它自己依赖于 TCP/IP 来获取客户端和服务器之间的请求和响应。默认的 TCP 端口是80端口,固然,使用其余端口也是能够的。然而,HTTPS 使用的端口是443端口。html
根据上图,咱们可将 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年问世,它在以前的基础上作了不少的改进。主要内容包含:面试
第四个阶段,SPDY - 2009。Google 走在前面,它开始试验一种可替换的协议来减小网页的延迟,使得网页加载更快、提高 Web 安全性。2009年,他们称这种协议为 SPDY。SPDY 的功能包含多路复用、压缩、优先级、安全等。2015年,谷歌不想存在两个相互竞争的标准,所以他们决定把它合并到 HTTP 中成为 HTTP/2,同时放弃 SPDY。spring
第五个阶段,HTTP/2 - 2015。HTTP/2 是专为低延迟传输的内容而设计。关键特征或与 HTTP / 1.1 旧版本的差别,以下。sql
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
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的请求协议。
而咱们大多数的网站的协议的版本都是 HTTP 1.1。
而咱们平时老生常谈的 HTTP 的协议大都是指的是 HTTP 1.1 协议的内容,接下去咱们一块儿看一下 HTTP 1.1 协议的结构。以下图所示。
接下来,我将经过四部分大概介绍一下 HTTP 协议的基本内容。
1.URL & URI
schema://host[:port#]/path/.../[;url-params][?query-string][#anchor]
URL(Uniform Resource Locator)主要包括如下几部分。
URI,在 Java 的 Servlet 中指的是 resource path 部分。
2.请求方法 Method
主要包括如下几种请求方法。
Method 名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码 405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码 501(Not Implemented)。
3.HTTP 之状态码
状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
常见状态码有:
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 类型。以下图所示。
(2)x-www-form-urlencoded
即 application/x-www-from-urlencoded,将表单内的数据转换为 Key-Value。这种和 Get 方法把参数放在 URL 后面同样的想过,这种不能文件上传。
(3)raw
能够上传任意格式的“文本”,能够上传 Text、JSON、XML、HTML 等。
(4)binary
即 Content-Type:application/octet-stream,只能够上传二进制数据流,一般用来上传文件。因为没有键值,因此一次只能上传一个文件。
(5)Header
HTTP 消息的 Headers 共分为三种,分别是 General Headers、Entity Headers、Request/Response 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 主要用来描述消息体(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-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 消息格式和 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 协议内容其实也挺多的,架构师其实也应该有重点,哪些是咱们必须重点关注的,内心要清楚。
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 类里面的具体描述便可。
通常不多针对单个项目去设置的,通常都是经过开源的云如 ali、asw里面的 SLB 配置 HTTP2和证书,在网关那层统一作掉。客户端通常是各个浏览器或者 App 的浏览器内核库来支持的。其实也不多须要开发来关心具体如何按照 HTTP2 来实现一些代码逻辑。
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
主要包括如下几个。
经常使用的值有:
(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=max-age + 请求时间,须要和 Last-modified 结合使用。但在上面咱们提到过 cache-control 的优先级更高。Expires 是 Web 服务器响应消息头字段,在响应 HTTP 请求时告诉浏览器在过时时间前浏览器能够直接从浏览器缓存取数据,而无需再次请求。
服务器端文件的最后修改时间,须要和 cache-control 共同使用,是检查服务器端资源是否更新的一种方式。当浏览器再次进行请求时,会向服务器传送 If-Modified-Since 报头,询问 Last-Modified 时间点以后资源是否被修改过。若是没有修改,则返回码为304,使用缓存;若是修改过,则再次去服务器请求资源,返回码和首次请求相同为200,资源为服务器最新资源。
根据实体内容生成一段 hash 字符串,标识资源的状态,由服务端产生。浏览器会将这串字符串传回服务器,验证资源是否已经修改。
为何要使用 Etag 呢?Etag 主要为了解决 Last-Modified 没法解决的一些问题。
一些文件也许会周期性的更改,可是它的内容并不改变(仅仅改变的修改时间),这个时候咱们并不但愿客户端认为这个文件被修改了,而从新 Get。
某些文件修改很是频繁,好比在秒如下的时间内进行修改(比方说1s内修改了 N 次),If-Modified-Since 能检查到的粒度是 s 级的,这种修改没法判断(或者说 UNIX 记录 MTIME 只能精确到秒)。
某些服务器不能精确的获得文件的最后修改时间。
缓存过程以下图所示。
很好的解决了 HTTP 通信中状态问题,但其自己也存在一些问题,好比:
与 Cookie 相比,Session有必定的优点,如:
基于 Cookie 实现 Session 的实现原理以下图的示。
由上可见,基于 Cookie 实现 Session 时,其本质上仍是在客户端保存一个 Cookie 值。这个值就是 sessionID,sessionID 的名称也可按须要设置,为保存安全,其值也可能会在服务器端作加密处理。服务器在收到 sessionID 后,就能够对其解密及查找对应的用户信息等。
我为何想提一下这个呢,我看到太多的开发者遇到 HTTP 协议都喜欢自定义变量,自定义类,其实彻底没有必要。且看下面的分析。
在 spring-web**.jar 里面咱们能够找到以下几个类:
须要咱们重点关注的有 HttpStatus、MediaType等。
Spring web bind中对应的注解,以下图所示。
上面是一些主要的(须要注意的是 @RequestParam、@PostMapping、@****Mapping 与咱们上面的 HttpMethod 相对应。而里面还有 Headers、Consumes 和 produces 等参数来肯定一些 Mapping 的条件),下面的能够根据须要查看源码里面有哪些注解。
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 提供的用于访问 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 Web 作了很好的支持。
咱们能够重点看一下 Pageable 和 Page,以及 PageImpl 和 PageRequest 对分页和排序作了很好的封装,及其返回的 JSON 格式也作了很好的约定。
Spring Cloud 的微服务管理都是基于 HTTP 协议的 Rest 风格的 API 来管理的,因此咱们详细了解 HTTP 协议仍是很是有必要的。
咱们都知道了约定的好处。若是你和你的团队曾经争论过使用什么方式构建合理 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 键和 errors 键不能再一个文档中同时存在。
文档可能包含如下任何 top-level 键。
若是文档不包含 top-level data 键,included 键也不该该出现。
文档的 top-level 连接对象可能包含如下键。
文档中的“primary data”表明一个请求所要求的资源或资源集合。
Primary data 必须是如下列举的一种。
例如,如下 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键不是必须的。
此外,资源对象可能包含如下 top-level 键。
一篇文献(即一个“文献”类型的资源)在文档中这样表示:
{
"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”必须包含如下至少一种键:
更多介绍见官方文档。
点击这里,访问 Yahoo elide官方网站。
1. elide介绍
elide 经过 Spring Data Jpa 的 Entity,加上自定义的 @Include(rootLevel = true) 注解,来完成 JSON API 标准的输出。
使用方法以下图所示。
效果以下:
代码以下。
@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); } }
针对 HTTP 里 Etag 的支持,也须要咱们框架层面去支持 Etag 缓存,这里就不给你们贴代码了,你们能够思考一下。
以下表所示。
比较项 | 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