【HTTP协议与网络编程】HTTP原理与实践

HTTP原理与实践

一. HTTP协议基础与发展历史

1. 五层网络模型介绍

传输层、应用层为高层,网络层、数据链路层和物理层为低层

名称作用
应用层为应用软件提供了很多服务,建立于TCP协议之上,屏蔽网络传输细节
传输层向用户提供可靠的端到端服务,传输层向高层屏蔽了下层数据通信的细节
网络层为数据在结点之间传输创建逻辑链路
数据链路层在通信的实体间建立数据链路连接
物理层定义物理设备如何传输数据

2. HTTP协议的发展历史

版本描述
HTTP/0.9只有GET请求,没有HEADER等描述数据的信息,服务器发送完毕就关闭TCP连接
HTTP/1.0追加了很多请求方法,增加了status code和header,支持多字符集,多部分发送
HTTP/1.1支持持久连接,支持pipeline、增加HOST和其他命令
HTTP/2所有数据二进制传输,同连接里发送多个请求不用按顺序来,头信息压缩和推送功能

3. HTTP的三次握手

HTTP的传输数据的通道是TCP建立的

在http1.0中,http请求创建的TCP连接是非持久的,就是服务器响应之后就关闭了,代表没发送一个请求都要创建一次连接。

http1.1中,这个连接可以通过一种方式让它一直保持连接,直到要发送的请求都发送完毕再关闭连接

http三次握手:

  • 首先客户端请求连接发送到服务端,标志位SYN=1,Seq=X
  • 服务端接收到请求之后,开启TCP端口并返回给客户端,标志位SYN=1,ACK=X+1,Seq=Y
  • 客户端收到响应之后,在发送具体数据ACK=Y+1,Seq=Z
  • 三次握手用来规避网络传输的延迟造成的服务器开销问题

4. URI、URL和URN

  • URI(Uniform Resource Identifier / 统一资源标志符)

    • URI其实是URL和URN统一的一个定义,也就是URI包含了URL和URN
    • URI是为了定位某一特定的资源设计的,用来唯一标识互联网上的信息资源
  • URL(Uniform Resource Locator / 统一资源定位器)

    比如链接:http://user:pass@host.com:80/path?query=string#hash

    http://部分:代表用哪个协议,不同协议解析方式不一样

    user:pass@部分: 代表用户认证,现在基本不用

    host.com部分:用来定位服务器在网上的位置,也可以是ip

    :80部分:是端口,不同的端口提供不同的服务。正式时一般不带端口,而是通过域名来访问

    path部分:是路由

    query=strin:搜索参数

    #hash:用来锚点定位

  • URN永久统一资源定位符

    在资源位置改变之后还能找到

    目前没有成熟的使用方案

5. HTTP报文格式

  • 请求报文:

    分为两部分起始行、首部

    起始行:包含一个请求方法,要请求资源的地址一个URL和HTTP版本如GET /test/hi.txt HTTP/1.0

  • 响应报文:

    分为三部分起始行、首部和主体

    起始行: http版本、code代表请求状态和具体的含义 如HTTP/1.0 200 OK

  • HTTP方法:用来定义对资源的操作

    常用的GET、POST

  • HTTP CODE

    定义服务器对请求的处理结果

    各个区间的CODE有各自的语义

    好的HTTP服务可以通过CODE判断结果

6. 创建一个最简单的web服务

const http = require('http');

http.createServer(function(req,res){
    
    res.end('hello world');
}).listen(8088);

console.log('服务器已开启,端口号:8088');

二. HTTP各种特性总览

1. 认识HTTP客户端

  • 只要是实现了发送了一个标准的HTTP请求的工具,那就是HTTP的客户端

  • 最简单的http客户端就是我们的浏览器,其他的还有curl、爬虫也是HTTP的客户端

2. CORS跨域请求的限制与解决

  • 模拟跨域请求的例子:

    • 第一个端口
    const http = require('http');
    const fs = require('fs');
    
    http.createServer(function(req,res){
       const html = fs.readFileSync('test.html','utf8')
       res.writeHead(200,{
           'Content-Type':'Text/html'
       })
       res.end(html);
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    
    • 第二个端口
    const http = require('http');
    
    http.createServer(function (req, res) {
       res.end('hello world');
    }).listen(8066);
    
    console.log('服务器已开启,端口号:8066');
    
    • 页面
    <!DOCTYPE html>
    <html lang="en">
    <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>Document</title>
    </head>
    <body>
       <script>
           var xhr = new XMLHttpRequest();
           xhr.open('GET','http://127.0.0.1:8066/')
           xhr.send()
       </script>
    </body>
    </html>
    

    上面这样就实现了一个跨域问题,8088端口提供的HTML去请求8066端口的内容,会直接报错

  • 解决跨域问题

    1. 在第二个端口设置‘Access-Control-Allow-Origin’
    const http = require('http');
    
    http.createServer(function (req, res) {
      res.writeHead(200,{
          'Access-Control-Allow-Origin':'*'
      })
      res.end('hello world');
    }).listen(8066);
    
    console.log('服务器已开启,端口号:8066');
    
    • 值为‘*’ 表示允许任何域名都可以访问服务,我们也可以限制跨域的域名
    const http = require('http');
    
    http.createServer(function (req, res) {
        res.writeHead(200,{
            'Access-Control-Allow-Origin':'http://127.0.0.1:8088/'
        })
        res.end('hello world');
    }).listen(8066);
    
    console.log('服务器已开启,端口号:8066');
    
    1. jsonp:仅修改页面的script设置即可
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script src="http://127.0.0.1:8066/"></script>
    </body>
    </html>
    

    利用script标签允许跨域的机制来实现跨域访问

3. CORS跨域限制以及预请求验证

  • 修改一下页面结构,使用一个自定义的头信息
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        fetch('http://127.0.0.1:8066/',{
            metthod:'POST',
            headers:{
                'X-Test-Cors':'123'
            }
        })
    </script>
</body>
</html>

这样会直接报错,因为跨域默认不允许使用自定义头

  • 跨域的限制

    • 跨域允许的方法: GET\HEAD\POST
  • Content-Type的值只能是下列值之一:

    • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
    • 只能人为设置下列首部字段: Accept、Accept-Language、Content-Language、Content-Type
  • XMLHttpRequestUpload对象均没有注册任何事件监听器

  • 请求中没有使用ReadableStream对象

  • 预请求

    • 端口二设置:

      const http = require('http');
      
      http.createServer(function (req, res) {
          res.writeHead(200,{
              'Access-Control-Allow-Origin':'*',
              'Access-Control-Allow-Headers':'X-Test-Cors',
              'Access-Control-Allow-Methods': 'POST,PUT,Delete',
              'Access-Control-Max-Age': '10000'
          })
          res.end('hello world');
      }).listen(8066);
      
      console.log('服务器已开启,端口号:8066');
      

      Access-Control-Allow-Headers: 设置允许的头部字段

      Access-Control-Allow-Methods:设置允许的请求方法

      Access-Control-Max-Age:设置生效时长

    • 设置预请求后可以发现多了一个请求,这个多出来的就是预请求

    • 设置预请求后再运行就不会报错了,因为允许使用X-Test-Cors这个自定义头部字段了

4. 缓存头Cache-Control的含义和使用

  • 特性

    1. 可缓存性:

    public: 公有,也就是都可以对返回内容的缓存的操作
    private: 私有,代表只有发起请求的浏览器才可以进行缓存
    no-cache: 任何一个节点都不可以直接进行缓存, 经过验证后才可以使用

    1. 到期

    max-age = : 多少秒以后过期,再重新请求
    s-maxage = : 会代替max-age, 但是只有代理服务器里面才会生效,既浏览器端还是会读取max-age的时间
    max-stale=: 如果返回过期之后,但是只要在max-stale的时间内,他还是可以使用缓存,而不需要去原服务器里面请求一个新的内容,这个设置只有发起端才有用

    1. 重新验证(不常用)

    must-revalidate: 在设置max-age,重新发送请求的时候,再重新验证这个内容是否已经真的过期了
    proxy-revalidate: 跟must一样的意思,但是是在代理服务器上用的

    1. 其他

    no-store: 本地和代理服务器都不可以使用缓存,彻底的不能用
    no-transform: 主要用在proxy服务器那边,意思是不要随便改动返回的内容

    1. 注意:这些头只是声明性的作用,没有强制约束力
  • 例子:

    const http = require('http');
    const fs = require('fs');
    
    http.createServer(function(req,res){
        if(req.url === '/'){
            const html = fs.readFileSync('test.html','utf8')
            res.writeHead(200,{
                'Content-Type':'Text/html'
            })
            res.end(html);
        }
    
        if(req.url === '/script.js'){
            res.writeHead(200,{
                'Content-Type':'text/javascript',
                'Cache-Control':'max-age=200, public'
    
            })
            res.end("console.log('script loaded')");
        }
        
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    
    • 页面代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <script src="/script.js"></script>
    </body>
    </html>
    

    如果设置了缓存那么会有一个问题,客户端缓存之后要是服务器更新了文件。客户端显示的还会是更新前的内容

    要想解决这个问题一般会使用hash码,这个码是根据文件内容生成的码,如果内容不变,那么hash码也不会变。这样就可以验证服务器端有没有更新文件

5. 缓存验证Last-Modified和Etag的使用

  • Last-Modified

    配合If-Modified-Since或If-Unmodified-Since使用

    过程:客户端请求资源时服务器端会返回Last-Modified头,上面指定了一个时间。客户端下次再请求时会带上这个头,通过If-Modified-Since或If-Unmodified-Since,通常为If-Modified-Since,验证资源是否更新

  • Etag

    更严格的验证,使用数据签名。资源的内容会生成一个唯一的签名,如果资源的内容更改了那么签名也会变。游览器下次发请求会带上这个签名和服务器端的签名做对比,判断是否要更新缓存

    配合If-Match或If-None-Match使用

  • 例子:

    const http = require('http');
    const fs = require('fs');
    
    http.createServer(function(req,res){
        if(req.url === '/'){
            const html = fs.readFileSync('test.html','utf8')
            res.writeHead(200,{
                'Content-Type':'Text/html'
            })
            res.end(html);
        }
    
        if(req.url === '/script.js'){
    
            const etag = req.headers['if-none-match']
            if(etag === '666'){
                res.writeHead(304,{ 
                    'Content-Type':'text/javascript',
                    'Cache-Control':'max-age=20000000, no-cache',
                    'Last-Modified':'hello',
                    'Etag':'666'
                })
                res.end('')
            }else{
                res.writeHead(200,{
                    'Content-Type':'text/javascript',
                    'Cache-Control':'max-age=20000000, no-cache',
                    'Last-Modified':'hello',
                    'Etag':'666'
                })
                res.end("console.log('script loaded')");
            }
            
        }
        
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    
    • 页面

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      <body>
          <script src="/script.js"></script>
      </body>
      </html>
      

6. cookie和session

  • cookie

    • 通过Set-Cookie设置的内容就叫cookie,游览器下次请求会自动携带cookie

    • cookie是以键值对的形式设置,可以设置多个.

    • cookie是有时效的,默认关闭就删除。可以设置max-age来设置有效时间

    • cookie的属性

      • max-age和expires:设置过期时间
      • secure:只在https的时候发送
      • HttpOnly:无法通过document.cookie访问(防止被攻击)
    • 使用案例:

      const http = require('http');
      const fs = require('fs');
      
      http.createServer(function(req,res){
          const html = fs.readFileSync('text.html','utf8')
          res.writeHead(200,{
              'Content-Type':'text/html',
              'Set-Cookie':['id=123;HttpOnly','user=admin','password=6666;max-age=20']
          })
          res.end(html);
      }).listen(8088);
      
      console.log('服务器已开启,端口号:8088');
      

7. HTTP长连接

  • 长连接不会马上关闭,Connection:Keep-Alive代表是长连接

    • Connection如果值为close,一个请求之后连接就会关闭,不会复用
  • 例子:

    const http = require('http');
    const fs = require('fs');
    
    http.createServer(function(req,res){
        const html = fs.readFileSync('t1.html','utf8')
        const img = fs.readFileSync('1.jpg')
       if (req.url === '/'){
        res.writeHead(200, {
          'Content-Type': 'text/html'
        })
        res.end(html);
      } else {
        res.writeHead(200, {
          'Content-Type': 'image/jpg'
        })
        res.end(img);
      }
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <img src="/1.jpg" alt="">
        <img src="/2.jpg" alt="">
        <img src="/3.jpg" alt="">
        <img src="/4.jpg" alt="">
        <img src="/5.jpg" alt="">
        <img src="/6.jpg" alt="">
        <img src="/7.jpg" alt="">
    </body>
    </html>
    

8. 数据协商

  • 数据协商概念:客户端给服务端发送一个请求的时,客户端会声明我想要的数据,数据格式以及数据相关的一些限制,服务端会根据请求做出一个判断,服务端可能会有很多不同类型的数据返回,可以根据头信息进行区分

  • 请求声明Accept:

    Accept: 声明要怎么样的数据,声明数据类型
    Accept-Encoding: 代表数据是怎么样的编码方式
    Accept-Language: 判断返回的信息是什么语言
    User-Agent: 表示浏览器的一些相关的信息

  • 服务端Content:

    Content-Type: 对应Accept,Accept可以接收很多种数据格式,Content-Type会在里面选择一种数据格式返回,在返回的时候声明返回的数据格式
    Content-Encoding: 对应Accept-Encoding,告诉客户端,用了什么样的压缩数据的方式
    Content-Language: 根据请求返回对应的语言

  • 例子:

    const http = require('http');
    const fs = require('fs');
    
    http.createServer(function(req,res){
      const html = fs.readFileSync('test.html', 'utf8');
      res.writeHead(200,{
        'Content-Type': 'text/html',
        'X-Content-Type-Options': 'nosniff'
      })
      res.end(html);
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    
  • 压缩格式

    const http = require('http');
    const fs = require('fs');
    const zlib = require('zlib')
    http.createServer(function(req,res){
      const html = fs.readFileSync('test.html');
      res.writeHead(200,{
        'Content-Type': 'text/html',
        'Content-Encoding': 'gzip'
      })
      res.end(zlib.gzipSync(html));
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    

9. Redirect

在发送一个请求时如果请求地址的内容地址变动了,这时服务器端应该告诉客户端新的地址在那

  • 例子:
const http = require('http');
const fs = require('fs');

http.createServer(function (req, res) {
    if(req.url === '/'){
        res.writeHead(302, {
            'Location': '/new'
        })
        res.end('');
    }

    if(req.url === '/new'){
        res.writeHead(200, {
            'Content-Type': 'Text/html'
        })
        res.end('this is content');
    }
}).listen(8088);

console.log('服务器已开启,端口号:8088');

这样访问http://127.0.0.1:8088/时,会自动跳转到/new

302是临时跳转,301是永久跳转

301要慎重使用,它

10. CSP

CSP(Content-Security-Policy): 内容安全策略:限制资源获取和报告资源获取越权

限制方式:

  • default-src限制全局 跟链接请求有关的东西,限制他的作用范围

  • 制定资源类型

    • content-src
       img-src
       style-src
       script-src
       frame-src
       font-src
       media-src
       manifest-src
      

  • 例子:

    const http = require('http');
    const fs = require('fs');
    
    http.createServer(function (req, res) {
        
    
        if(req.url === '/'){
            const html = fs.readFileSync('csp.html', 'utf8')
            res.writeHead(200, {
                'Content-Type': 'Text/html',
                'Content-Security-Policy': 'script-src \'self\'
            })
            res.end(html);
        }else{
            res.writeHead(200, {
                'Content-Type': 'appliction/javascript',
            })
            res.end('loaded script');
        }
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    

    只能用某个网站域名下的js

    'Content-Security-Policy': 'default-src \'self\' http://baidu.js'
    

    只现在script,不限制连接

    'Content-Security-Policy': 'script-src \'self\'
    

    限制表单提交

    'Content-Security-Policy': 'form-action \'self\''
    

    可以让游览器主动发送请求汇报

    'Content-Security-Policy': 'script-src \'self\'; report-uri /report';
    

    可以通过meta标签在页面中直接设置,效果一样

    <meta http-equiv='Content-Security-Policy' content='script-src "self"; form-action "self"; report-uri /report';>
    

三. Nginx代理以及面向未来的HTTP

1. NGINX安装和基础代理配置

  • 是一个纯粹的HTTP的服务,可以一台机器配置好几个服务

  • 下载地址: http://nginx.org/en/download.html

  • 运行cmd进入安装目录,在控制台nginx.exe来开启

  • 配置

     server {
            listen       80; 
            server_name  test.com;
            location / {
                proxy_pass http://127.0.0.1:8088;
                proxy_set_header Host $host;
            }
     }
    

2. Nginx代理配置和代理缓存的用处

  • 配置缓存

    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:8088;
            proxy_set_header Host $host;
        }
    }
    
    const http = require('http');
    const fs = require('fs');
    
    const wait = (seconds) =>{
        return new Promise((resolve, reject) =>{
            setTimeout(()=>{
                resolve()
            },seconds * 1000)
        })
    }
    
    http.createServer(function (req, res) {
    
    
        if (req.url === '/') {
            const html = fs.readFileSync('cache.html', 'utf8')
            res.writeHead(200, {
                'Content-Type': 'Text/html'
            })
            res.end(html);
        } 
        if(req.url === '/data') {
            res.writeHead(200, {
                'Cache-Control': 'max-age=2, s-maxage=20, private'
            })
            wait(2).then(()=> res.end('success'))
        }
    }).listen(8088);
    
    console.log('服务器已开启,端口号:8088');
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <h1>This is content, and data is:<span id="data"></span></h1>
        <script>
            fetch('/data').then(function(resp){
                return resp.text()
            }).then(function(text){
                document.getElementById('data').innerText = text
            })
        </script>
    </body>
    </html>
    

3. HTTPS解析

  • 加密
    • 私钥:只放在服务器上
    • 公钥:有私钥才能解密

4. 使用Nginx部署HTTPS服务

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


server{
    listen      80 default_server;
    listen      [::]:80 default_server;
    server_name test.com;
    return 302 http://$server_name$request_uri;
}


server{
    listen 443;
    server_name test.com;

    ssl on;
    ssl_certificate_key ../certs/localhost-privkey.pem;
    ssl_certificate ../certs/localhost-cert.pem;

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

5. HTTP2的优势和Nginx配置HTTP2的简单使用

优势:

  • 信道复用
  • 分帧传输
  • Server Push
proxy_cache_path cache levels=1:2 keys_zone=my_cache:10m;


server{
    listen      80 default_server;
    listen      [::]:80 default_server;
    server_name test.com;
    return 302 http://$server_name$request_uri;
}


server{
    listen 443 http2;
    server_name test.com;
    http2_push_preload on;

    ssl on;
    ssl_certificate_key ../certs/localhost-privkey.pem;
    ssl_certificate ../certs/localhost-cert.pem;

    location/{
        proxy_cache my_cache;
        proxy_pass http://127.0.0.1:8088;
        proxy_set_header Host $host;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值