Web前端开发工程师必须了解的HTTP知识

前言

官方解释,HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。通俗来讲,就是规定服务端和客户端通讯的一种规则。更多的是基于浏览器环境下使用,那么从你浏览器输入地址开始到最终页面的呈现,到底通过了哪些过程呢?废话很少说,先贴一张图,以下: javascript

如上图,就是http请求发起到返回的完证过程,本觉得本身对http的了解还算能够,可是乍一看图仍是很蒙的,好比说若是让你优化http过程,你该从何下手呢?我想大部分仍是比较关心requestresponse,以及数据返回以后的DOMContentLoadload,至于http中的一些配置并非很清楚,其实经过优化配置,一样可以加速网页的打开速度,所以,我大体总结一些关于http中会常用的配置项,以及这些使用通常会在哪些场景中使用到。html

注意:上图中的domContentLoadedEventEnd表明DOMContentLoaded事件完成的时间节点,也就是jQuery中的domready时间。load表明的是onload事件触发和结束的时间节点。前端

HTTP之跨域相关内容

基本内容

这里主要介绍JSONPCORS跨域,现实场景中,以上两种使用居多,因此其余跨域方案不作详细介绍。形成跨域的主要缘由主要是浏览器自己的同源策略引发的。java

JSONP可以实现跨域主要是由于浏览器上容许标签上经过src/href加载外链路径,可是JSONP只支持GET请求,同时由于浏览器中url长度的限制,所以JSONP能传输的数据大小也有必定的限制。node

CORS跨域可以支持的全部ajax的方法,固然,目前是支持ie9+,低版本暂时不支持,随时互联网的发展,相信低版本的浏览器会逐渐被淘汰。在只用CORS只须要服务端可以开启容许跨域的头设置便可,也就是Access-Control-Allow-Origingit

跨域大体的流程图以下: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')
复制代码

CORS跨域限制

  • 默认容许的方法有GETHEADPOST
  • 默认容许的Content-Type有text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  • HTTP的头信息包含AcceptAccept-LanguageContent-LanguageLast-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能够配置其余的方法,例如PUTDELETE等。

细心的同窗可能会发现,根据以上代码的确能够经过预请求,可是若是再次刷新网页,会发现仍然还会存在预请求,对于第一次预请求已经经过了,为何一样的请求还会再发送一次呢?其实这里能够作一个优化,减小预请求的发送。

经过设置Access-Control-Max-Age来肯定预请求的有效时间,只要在有效时间内,就不会再次发送预请求了。

HTTP之Cache-Control

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个主要部分:

一、本地缓存,其实也就是本地资源的缓存。 二、服务器缓存,包含验证缓存和非验证缓存。

HTTP之cookie

  • 经过Set-Cookie来设置cookie,下次请求的时候,会自动带上以前设置的cookie(同域状况下),取值类型是String/Array。
  • 经过max-ageExpires设置过时时间。
  • 经过Secure来设置只能在https的时候发送。
  • 经过HttpOnly来设置没法经过document.cookie访问。
  • 经过domain=来设置该cookie是否在同域下共享。

以上内容基本能汇总成一张图解释,以下:

HTTP之keep-alive

目前开发web网页,全部请求默认都是Connection: keep-alive,除非服务端手动去关闭配置(Connection: close),使用keep-alive能够复用以前的请求的信道,这样减小tcp三次握手的时间(同域状况下才能生效)。

HTTP之数据协商

web服务请求会携带如下信息内容:

  • Accept 想要的数据类型
  • Accept-Encoding 限制服务端数据的压缩方式(gzip、deflate、br)
  • Accept-Language 服务端返回数据的语言类型
  • User-Agent浏览器头信息内容

服务端返回数据会携带如下信息内容:

  • Content-type 返回的数据类型
  • Content-Encoding 对应的是 Accept-Encoding 表明数据压缩类型
  • Content-Language 对应的是 Accept-Language 表明数据语言类型

固然,web请求也能够自定义Content-type来传输数据,通常在form表单中比较经常使用,例如上传文件,会指定Content-type:multipart/form-data,这样服务端就能接收上传的文件信息内容。

HTTP之重定向

浏览器能识别的重定向code码有两种,分别是301302,二者在使用上会有必定的区别,大体以下:

  • 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的跳转则是永久性的,除非清楚本地浏览器的缓存,要么没法改变。

HTTP以内容安全策略

配置内容安全策略涉及到添加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之HTTP2

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正式发表。

大体总结有如下特性:

  • 二进制分帧
  • 多路复用:同域名下全部通讯都在单个链接上完成;单个链接能够承载任意数量的双向数据流;数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间能够乱序发送,由于根据帧首部的流标识能够从新组装;
  • 服务器推送:服务端能够在发送页面HTML时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。
  • 头部压缩:HTTP/2对消息头采用HPACK(专为http2头部设计的压缩格式)进行压缩传输,可以节省消息头占用的网络的流量。而HTTP/1.x每次请求,都会携带大量冗余头信息,浪费了不少带宽资源。

贴一张简图,能够更好的体现,更多详情能够参考

为了能更好的体验实际场景中的效果,作了简单的测试,使用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

相关文章
相关标签/搜索