美文网首页
HTTP特性总览

HTTP特性总览

作者: 落痕無情 | 来源:发表于2018-08-05 22:34 被阅读0次

本文对HTTP协议的相关特性以及客户端-服务端请求-响应过程中的涉及的问题进行略为深入的讨论,也是参考慕课网课程《HTTP协议原理+实践》进行整理记录的一篇学习笔记。

相关的示例代码:HTTP特性演示

CORS跨域请求

CORS跨域是由浏览器安全策略引起的,常用jsonp解决,也可以在服务端添加相应的允许跨域的header Access-Control-Allow-Origin,这样就可以直接使用ajax直接发起跨域请求。

注意:

  • 不管服务端有没有设置header,浏览器都会正常发送跨域请求,服务端也会接受返回内容,如果没有设置 Access-Control-Allow-Origin,浏览器在解析内容后会自动拦截。这是浏览器的跨域限制问题,用curl工具就没关系。

  • 浏览器允许在html中使用 <script src=""> 标签来实现跨域请求,这也是jsonp的而基本原理。

但是,也不是所有情况都可以仅仅通过设置一个header来解决问题,CORS跨域还有一些限制以及预请求验证功能。

CORS中的限制:

  • 允许方法:

    • GET
    • HEAD
    • POST
  • 允许Content-Type:

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 其它限制

    • 自定义请求头限制

其它情况下,必须发起预请求[OPTIONS方法]验证后才能正常发送,否则会报错。另外,可以设置 Access-Control-Max-Age 来限制预请求的时限,在这个时间内发起任何请求无需发起预请求的验证步骤。

演示场景:

使用浏览器从 localhost:8888localhost:8887 发起GET跨域请求,同时在服务端作了一定的预请求限制方案:

client

<script>
  fetch('http://localhost:8887', {
    method: 'POST',
    // 自定义header
    headers: {
      'X-Test-Cors': '123'
    }
  })
</script>

server

response.writeHead(200, {
    'Access-Control-Allow-Origin': 'http://127.0.0.1:8888',
    'Access-Control-Allow-Headers': 'X-Test-Cors',
    'Access-Control-Allow-Methods': 'POST, PUT, DELETE',
    'Access-Control-Max-Age': '1000'
  })

补充:关于 Fetch API

缓存头Cache-Control

可缓存性:

  • public: 任何地方都可以缓存

  • private: 只有发起请求的浏览器可以缓存

  • no-cache: 浏览器不允许缓存,但是代理服务器可以缓存

  • no-store: 任何地方都不能可以缓存

设置缓存过期时间:

  • max-age=<seconds>: 浏览器缓存时间

  • s-maxage=<seconds>: 代理服务器缓存时间

演示场景:

在一个html文件中通过script标签去引用一个js文件,当客户端请求这个js文件路径时,服务端返回一段js代码,并对文件进行缓存。

if (request.url === '/script.js') {
    response.writeHead(200, {
      'Content-Type': 'text/javascript',
      // 设置缓存头,value可以有多个,用逗号分隔
      'Cache-Control': 'max-age=20'
    })
    response.end('console.log("script loaded")')
  }

第一次访问从服务器加载,在有效时间内再次访问会从直接缓存中读取,如果这期间修改了文件内容,也不会去加载更新的内容。这是由于设置了Cache-Control之后不会去经过服务端的验证。一般情况下,我们希望浏览器去缓存一些静态资源文件,提高页面加载速度,但是也要考虑资源更新的问题。

注意:如果同时设置了 max-age 和 no-cache 之后,每一次浏览器发起请求,还是会先去服务端进行资源验证,验证之后如果确定这个资源可以使用缓存,才会去本地读取缓存,并不是直接从本地缓存中读取。

前端如何解决浏览器长缓存问题?

  • 在打包静态资源的时候在对文件名加上一串哈希码,hash是通过文件内容进行计算的,如果内容有变化文件名也会随之改变,浏览器就会当成新的文件去请求。

资源验证

  • Last-Modified 配合 If-Modified-Since 使用

  • Etag 配合 If-None-Match 使用

如果对一个可缓存的文件设置了这两个Headers,那么在有效缓存实践内再次访问,客户端发起的请求头信息中就会新增两个Header,即 If-Modified-SinceIf-None-Match,它们的值分别为第一次访问时返回的 Last-ModifiedEtag 的值,作用是向服务端验证这两个缓存是否有效。

但是这样设置仍然会从服务器上重新加载一次文件,而我们希望的是服务端告诉浏览器直接去读取本地缓存而已,这时候就需要去判断 If-None-Match 的值是否为 Etag 的值。

if (request.url === '/script.js') {
  const etag = request.headers['if-none-match']
  if (etag === '777') {
    response.writeHead(304, {
      'Content-Type': 'text/javascript',
      'Cache-Control': 'max-age=2000000, no-cache',
      'Last-Modified': '123',
      'Etag': '777'
    })
    response.end()
  } else {
    response.writeHead(200, {
      'Content-Type': 'text/javascript',
      'Cache-Control': 'max-age=2000000, no-cache',
      'Last-Modified': '123',
      'Etag': '777'
    })
    response.end('console.log("script loaded twice")')
  }
}

注:304状态码的语义是 Not Modified

nginx代理缓存配置

nginx是一个单纯的http服务器,一般可以用于负载均衡代理缓存

示例:

proxy_cache_path cache levels=1:2 keys_zone=my_cache:10m;

server {
  listen       80;
  server_name  test.com;

  location / {
    proxy_cache my_cache;
    proxy_pass http://127.0.0.1:8888;
    proxy_set_header Host $host;
  }
}

levels=1:2 在指cache目录下创建相应层级的子目录存放缓存文件,而不是全部堆积在根目录下。my_cache 是自定义缓存名,10m是允许最大缓存大小为10MB。

如上,访问 test.com 后Nginx服务器会代理到访问 http://127.0.0.1:8888 启动的服务,同时会开启页面缓存,这份缓存是放在服务端的cache目录下,通过响应头 Cache-Controls-maxage 属性来控制过期时间。

Cookie

要使用cookie只要在服务端的响应头中添加 Set-Cookie 属性,里面可以设置多个cookie,以键值对的形式存在。cookie保存在浏览器中,下一次客户端访问同一个域名的服务器时,请求头中会自动带上之前获取到的cookie。

Cookie属性:

  • max-ageexpires 设置过期时间

  • Secure 只在HTTPS的时候发送。

  • 设置了 HttpOnly 就无法通过 document.cookie 访问。

实例:

if (request.url === '/') {
  const html = fs.readFileSync('test.html', 'utf8')
  response.writeHead(200, {
    'Content-Type': 'text/html',
    'Set-Cookie': ['id=123; max-age=2', 'abc=456; domain=test.com']
  })
  response.end(html)
}

cookie只有在设置的域名下才可以访问,但也可以通过 domain 指定所有的二级域名也可以共享cookie。

HTTP长连接

Http 1.1 引入了长连接,请求头和响应头中都有 Connection: keep-alive

浏览器在访问Web服务时会并发地创建一定数量的TCP连接(Chrome是6个),然后在这些TCP连接的基础上去发起http请求的三次握手,无论加载多少资源文件,都会去复用前面创建的TCP连接,但是是有先后顺序的。我们可以在Chrome调试工具中打开 Connection ID 项来查看是否复用了同一个TCP连接。

Http 2 引入了信道复用,在TCP连接上可以并发地去发送http请求,也就是说连接网站时只需要1个TCP连接,减少了大量开销,整体访问速度会有很大提升。

数据协商

概念:在客户端发送给服务端一个请求时,客户端会声明这个请求的数据格式和相关限制,服务端后根据客户端的请求来区分应该返回怎样的数据。

数据协商分为请求返回两部分。

请求 - Accept

设置 定义
Accept 指定数据类型,根据 MIME Types 声明可接受的服务端返回的数据格式。
Accept-Encoding 指定数据传输的编码方式,限制服务端如何进行数据压缩。
Accept-Language 指定希望展示的语言
User-Agent 表示浏览器的相关信息(如区分移动端或PC端浏览器……)

Accept只是客户端希望服务端返回的方式(通常会列举很多种),实际上服务端不一定返回要求的格式。

返回 - Content

设置 定义
Content-Type 声明服务端实际返回的数据格式
Content-Encoding 声明压缩方式,如gzip
Content-Language 是否返回了相关的语言
const html = fs.readFileSync('test.html')
response.writeHead(200, {
  'Content-Type': 'text/html',
  // 'X-Content-Options': 'nosniff'
  'Content-Encoding': 'gzip'
})
response.end(require('zlib').gzipSync(html))

zlib压缩后传输的数据(包含内容和头信息)大小会变小,只是为了减少数据传输时的开销,但是解压出来的body数据的大小还是不变的。

Html表单允许发送的 Content Type

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

补充:用ajax发送带有文件的表单数据

var form = document.getElementById('form')
form.addEventListener('submit', function (e) {
  e.preventDefault()
  var formData = new FormData(form)
  fetch('/form', {
    method: 'POST',
    body: formData
  })
})

submit事件是绑定在 <form> 元素上的。

重定向

如果资源转移到了其他URL,则需要告诉客户端相应的目标路径并完成跳转:

if (request.url === '/') {
  response.writeHead(302, {  // or 301
    'Location': '/new'
  })
  response.end()
}
if (request.url === '/new') {
  response.writeHead(200, {
    'Content-Type': 'text/html',
  })
  response.end('<div>this is content</div>')
}

说明:

  • 浏览器默认302跳转,302是临时重定向,每次访问都会经过服务器的跳转过程到达新的URL。

  • 301是永久重定向,告诉浏览器下一次直接访问新的URL即可,实际上是放到了浏览器的缓存中直接读取,所以301不能反悔,因为用户浏览器的缓存是不可控的。

  • 301重定向将SEO评分从旧地址直接转移到新地址,302重定向会被认为作弊。

CSP

CSP的全称为Content-Security-Policy,即“内容安全策略”。

作用:

  • 限制资源获取
  • 报告资源获取越权

相关文章

网友评论

      本文标题:HTTP特性总览

      本文链接:https://www.haomeiwen.com/subject/ufuivftx.html