官方解释,HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。通俗来讲,就是规定服务端和客户端通讯的一种规则。更多的是基于浏览器环境下使用,那么从你浏览器输入地址开始到最终页面的呈现,到底通过了哪些过程呢?废话很少说,先贴一张图,以下: javascript
如上图,就是http请求发起到返回的完证过程,本觉得本身对http
的了解还算能够,可是乍一看图仍是很蒙的,好比说若是让你优化http过程,你该从何下手呢?我想大部分仍是比较关心request
和response
,以及数据返回以后的DOMContentLoad
和load
,至于http
中的一些配置并非很清楚,其实经过优化配置,一样可以加速网页的打开速度,所以,我大体总结一些关于http
中会常用的配置项,以及这些使用通常会在哪些场景中使用到。html
注意:上图中的
domContentLoadedEventEnd
表明DOMContentLoaded事件完成的时间节点,也就是jQuery中的domready时间。load
表明的是onload
事件触发和结束的时间节点。前端
这里主要介绍JSONP
和CORS
跨域,现实场景中,以上两种使用居多,因此其余跨域方案不作详细介绍。形成跨域的主要缘由主要是浏览器自己的同源策略
引发的。java
JSONP
可以实现跨域主要是由于浏览器上容许标签上经过src/href加载外链路径
,可是JSONP
只支持GET
请求,同时由于浏览器中url
长度的限制,所以JSONP
能传输的数据大小也有必定的限制。node
CORS
跨域可以支持的全部ajax的方法,固然,目前是支持ie9+,低版本暂时不支持,随时互联网的发展,相信低版本的浏览器会逐渐被淘汰。在只用CORS
只须要服务端可以开启容许跨域的头设置便可,也就是Access-Control-Allow-Origin
。git
跨域大体的流程图以下:github
注意:
JSONP
中的数据限制并非GET
请求自己的限制,而是浏览器中url
自己有长度限制,GET
方法是没有任何长度限制的;无论是JSONP
仍是CORS
跨域,其实服务器均可以接收来自客户端的数据请求,而且也都成功返回了,只是浏览器自己有同源策略
的限制,才会进一步判断返回的数据是否符合浏览器的限制。web
这里有个题外话,Access-Control-Allow-Origin
这个配置项默认支持配置单个域名或者*,为了安全起见,不建议配置*
,那么如何配置才能支持多个域名跨域呢?有一个简易的方法能够解决,主要思路是经过服务端定义可支持的跨域域名集合,经过循环判断当前请求是否支持便可,片断代码以下:ajax
// 服务端代码,如下是node服务作测试
const http = require('http')
const allowDomains = [
'http://www.a.com',
'http://www.b.com',
'http://www.c.com'
]
const server = http.createServer((req, res) => {
let acao = ''
for(let i = 0, l = allowDomains.length; i < l; i++) {
if(allowDomains[i].indexOf(req.headers.host) > -1) {
acao = allowDomains[i]
break
}
}
res.writeHead(200,
{
'Access-Control-Allow-Origin': acao
}
)
res.end('Hello World\n')
}).listen(3001)
console.log('server listen 3001')
复制代码
GET
、HEAD
、POST
。text/plain
、multipart/form-data
、application/x-www-form-urlencoded
。Accept
、Accept-Language
、Content-Language
、Last-Event-ID
。在请求包含以上内容的时候,其实就是简单请求,在跨域的状况下,浏览器默认是直接经过的,其他剩下的称之为复杂请求,浏览器会默认发送一次预请求做为验证,若是验证经过则表明请求成功。express
所以须要对上图增长限制的修改,最终以下:
其实就是对于复杂请求
作了一次校验,大体能够这样解释,若是在发送请求时,例如额外带了headers
的配置项,若是须要验证经过就必须在服务端也要配置容许该headers
的返回,这样预请求的验证才会经过。也能够经过代码作一下验证,基本以下:
// 后端服务代码
const http = require('http')
const server = http.createServer((req, res) => {
res.writeHead(200,
{
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'aa' // 经过设置了这个,才能使得预请求验证经过
}
)
res.end('Hello World\n')
}).listen(3001)
console.log('server listen 3001')
// 前端服务代码
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const html = fs.readFileSync('index.html', 'utf8')
res.writeHead(200,
{
'Content-Type': 'text/html'
}
)
res.end(html)
}).listen(3000)
console.log('server listen 3000')
// index.html主要代码以下
fetch('http://localhost:3001', {
method: 'post',
headers: {
aa:'123'
}
})
复制代码
以上测试代码主要是在发起post
请求的时候额外携带了一个headers
参数,只有在服务端配置了容许该headers
传输才能使得浏览器预请求验证经过,反之则会失败。你们能够根据以上测试代码在本身的本机测试就能明白了。
注意:经过设置
Access-Control-Request-Method
能够配置其余的方法,例如PUT
、DELETE
等。
细心的同窗可能会发现,根据以上代码的确能够经过预请求,可是若是再次刷新网页,会发现仍然还会存在预请求,对于第一次预请求已经经过了,为何一样的请求还会再发送一次呢?其实这里能够作一个优化,减小预请求的发送。
经过设置Access-Control-Max-Age
来肯定预请求的有效时间,只要在有效时间内,就不会再次发送预请求了。
Cache-Control
包含不少特性,其中no-cache
这个配置项确定最熟悉,官方解释是在释放缓存副本以前,强制高速缓存将请求提交给原始服务器进行验证,其实就是表明没有缓存。可是其实它依然有不少特性,通过资料查询,大体分为如下几类,
介绍下通常经常使用的配置参数的文字解释:
public
表明http从请求到返回的整个路径上的均可以被缓存,例如客户端浏览器,通过的代理服务器等等。
private
指发起的浏览器这一端才能进行缓存,也就是代理服务器是不能缓存的。
no-cache
是否使用缓存须要经过服务器验证后才能判断。
max-age=<seconds>
最大能缓存多少秒,过时以后,请求会再次发送到服务端,对于返回的数据会再次被缓存。
s-maxage=<seconds>
会覆盖max-age
或者Expires
头,应用于共享(如:代理服务器)缓存,而且在代理服务器生效,客户端不生效。
max-stale[=<seconds>]
代表客户端愿意接收一个已通过期的资源。即便max-age
已通过期,一样会使用本地的过时缓存。
must-revalidate
若是max-age
过时,必须经过服务端来验证返回的数据是否真的过时。
proxy-revalidate
主要使用在代理服务器端,对于过时的数据必须向服务端从新请求一遍。
no-store
本地和代理服务器都不容许存缓存。
no-transform
不得对资源进行转换或转变,主要使用在代理服务器上。
具体每一个配置的官方解释参考具体说明
Last-Modified
代表请求的资源上次的修改时间,主要配合If-Modified-Since
(客户端保留的资源上次的修改时间)进行使用,主要是在发送请求的时候带上。经过对比上次修改时间以验证资源是否更新。Etag
资源的内容标识。(不惟一,一般为文件的md5或者一段hash值,只要保证写入和验证时的方法一致便可),配合If-Match
或者If-None-Match
进行使用,对比资源的内容标识来判断是否使用缓存。其实服务器能够经过Etag
来区分返回哪些数据,具体能够参考下面的示例:
// server.js
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const url = req.url
const html = fs.readFileSync('index.html', 'utf8')
const etag = req.headers['if-none-match']
if(url === '/') {
res.writeHead(200,
{
'Content-Type': 'text/html',
'Cache-Control': 'max-age=2000, no-cache',
'Last-Modified': '123',
'Etag': '444'
}
)
res.end(html)
}
if(url === '/aa.js') {
if(etag === '444') {
res.writeHead(304,
{
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=2000, no-cache',
'Last-Modified': '123',
'Etag': '444'
}
)
res.end('888888')
} else {
res.writeHead(200,
{
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=2000, no-cache',
'Last-Modified': '123',
'Etag': '444'
}
)
res.end('1111111111')
}
}
}).listen(3000)
console.log('server listen 3000')
复制代码
<!-- html文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="/aa.js"></script>
</body>
</html>
复制代码
第一次刷新和第二次刷新截图以下:
根据测试代码能得出,在服务端经过
etag === '444'
作了判断,可是最终返回的依然是1111111111
,这是因为服务端选取了第一次的缓存数据做为返回。虽然发生了一次请求,但请求内容长度减小了,节省了带宽。
其实以上全部的概念基本能够用一个图来展现,大体分为2个主要部分:
一、本地缓存,其实也就是本地资源的缓存。 二、服务器缓存,包含验证缓存和非验证缓存。
Set-Cookie
来设置cookie
,下次请求的时候,会自动带上以前设置的cookie
(同域状况下),取值类型是String/Array。max-age
和Expires
设置过时时间。Secure
来设置只能在https
的时候发送。HttpOnly
来设置没法经过document.cookie
访问。domain=
来设置该cookie
是否在同域下共享。以上内容基本能汇总成一张图解释,以下:
目前开发web网页,全部请求默认都是Connection: keep-alive
,除非服务端手动去关闭配置(Connection: close
),使用keep-alive
能够复用以前的请求的信道,这样减小tcp
三次握手的时间(同域状况下才能生效)。
web服务请求会携带如下信息内容:
服务端返回数据会携带如下信息内容:
Accept-Encoding
表明数据压缩类型Accept-Language
表明数据语言类型固然,web请求也能够自定义Content-type
来传输数据,通常在form
表单中比较经常使用,例如上传文件,会指定Content-type:multipart/form-data
,这样服务端就能接收上传的文件信息内容。
浏览器能识别的重定向code
码有两种,分别是301
和302
,二者在使用上会有必定的区别,大体以下:
301
重定向是永久重定向
,用户在访问资源的时候,浏览器默认是从缓存中获取以前指定的跳转信息;302
重定向能够随时取消,也就是用户在访问资源的时候,每次都会通过服务端而且在服务端经过跳转逻辑进行跳转;可使用一段代码来描述以上的不一样之处,基本代码以下:
// 302跳转测试代码
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const url = req.url
const html = fs.readFileSync('index.html', 'utf8')
console.log(url)
if(url === '/') {
res.writeHead(302,
{
Location: '/wq'
}
)
res.end('')
}
if(url === '/wq') {
res.writeHead(200,
{
'Content-Type': 'text/html',
}
)
res.end(html)
}
}).listen(3000)
console.log('server listen 3000')
复制代码
301
测试代码跟上面基本同样,只要将302
改为301
便可,根据以上代码测试,你会发现,每次在刷新页面的时候,若是是302
跳转,那么console.log(url)
每次都会打印/ 和 /wq
,若是是301
的话,只会打印/wq
,这就说明,302
的跳转是从服务端指定跳转的,而301
的跳转则是永久性的,除非清楚本地浏览器的缓存,要么没法改变。
配置内容安全策略涉及到添加Content-Security-Policy
,HTTP头部到一个页面,并配置相应的值,以控制用户代理(浏览器等)能够为该页面获取哪些资源。更多详细参考具体说明
经常使用示例以下:
// 一个网站管理者想要全部内容均来自站点的同一个源 (不包括其子域名)
'Content-Security-Policy': default-src 'self'
// 一个网站管理者容许内容来自信任的域名及其子域名 (域名没必要须与CSP设置所在的域名相同)
'Content-Security-Policy': default-src 'self' *.trusted.com
// 该服务器仅容许经过HTTPS方式并仅从onlinebanking.jumbobank.com域名来访问文档
'Content-Security-Policy': default-src https://onlinebanking.jumbobank.com
// 限制向百度请求
'Content-Security-Policy': connect-src http://baidu.com
// 经过设定report-uri来指定上报服务器地址
'Content-Security-Policy': default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi
复制代码
HTTP/2
是HTTP协议自1999年HTTP1.1发布后的首个更新,主要基于SPDY
协议。它由互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工做小组进行开发。该组织于2014年12月将HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。HTTP/2标准于2015年5月以RFC 7540正式发表。
大体总结有如下特性:
贴一张简图,能够更好的体现,更多详情能够参考:
为了能更好的体验实际场景中的效果,作了简单的测试,使用express
配合node
开启http2
来测试具体效果,在这以前,须要生成一个SSL
,生成代码以下:
// 直接在cmd中运行
openssl req -x509 -nodes -newkey rsa:2048 -keyout example.com.key -out example.com.crt
复制代码
编写基础的server.js
代码,基本以下:
const port = 3000
const spdy = require('spdy')
const express = require('express')
const path = require('path')
const fs = require('fs')
const resolve = file => path.resolve(__dirname, file)
const app = express()
const serve = (path, cache) => express.static(resolve(path), {
maxAge: cache ? 1000 * 60 * 60 * 24 * 30 : 0
})
app.use('/html', serve('./html', true))
app.get('/', (req, res) => {
res.status(200)
res.send('hello world')
})
app.get('/timg.jpeg', (req, res) => {
const img = fs.readFileSync('/html/timg.jpeg')
res.writeHead(200,
{"Content-Type": 'image/jpeg'
});
res.send(img)
})
const options = {
key: fs.readFileSync(__dirname + '/example.com.key'),
cert: fs.readFileSync(__dirname + '/example.com.crt')
}
spdy
.createServer(options, app)
.listen(port, (error) => {
if (error) {
console.error(error)
return process.exit(1)
} else {
console.log('Listening on port: ' + port + '.')
}
})
复制代码
根目录的静态文件定义fetch
方法来请求100
张图片,这样来测试请求的信道以及加载时长,基本代码以下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>http2测试</title>
</head>
<body>
<script> for(let i = 0; i < 100; i++) { fetch('//localhost:3000/html/timg.jpeg') } </script>
</body>
</html>
复制代码
按照以上代码运行,而且对比HTTP/1.1
的效果以下(图1是HTTP/1.1,图2是HTTP2):
从图1和图2就能看出,图2中Connection ID
只有一个,而图1中Connection ID
会随着请求的增长而增长,每增长一个信道,就会建立一次TCP
连接,也就是须要通过三次握手,而且还受限浏览器自己的并发数(其中谷歌浏览器的最大并发数是6个),因此才会出现等待的状况;从Size
那一列能看出,HTTP2
中的请求数据也会小(由于自己数据就小,因此不明显),这样可以减小带宽;从最终的Finsh
时间能看出,一样是100张图片的请求,HTTP2
耗时更少。
以上内容主要是和HTTP
相关的内容,因为比较简单,就不提供测试代码压缩包,基本从示例中直接复制粘贴就能本地测试运行,若是有什么不正确的地方,欢迎提Issues
。