⽹络编程 http https http2 websocket

HTTP协议

// 观察HTTP协议
curl -v http://www.baidu.com
  • HTTP协议详解
  • 创建接⼝,api.js
// http/api.js
const http = require("http");
      const fs = require("fs");
      http
        .createServer((req, res) => {
          const { method, url } = req;
          if (method == "GET" && url == "/") {
            fs.readFile("./index.html", (err, data) => {
              res.setHeader("Content-Type", "text/html");
              res.end(data);
            });
          } else if (method == "GET" && url == "/api/users") {
            res.setHeader("Content-Type", "application/json");
            res.end(JSON.stringify([{ name: "tom", age: 20 }]));
          }
        })
        .listen(3000);
  • 请求接⼝
// index.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    (async () => {
        const res = await axios.get("/api/users")
        console.log('data', res.data)
        document.writeln(`Response : ${JSON.stringify(res.data)}`)
    })()
</script>
  • 埋点更容易
const img = new Image()
img.src='/api/users?abc=123'

协议 端⼝ host

  • 跨域:浏览器同源策略引起的接⼝调⽤问题
// proxy.js
const express = require('express')
const app = express()
app.use(express.static(__dirname + '/'))
module.exports = app
// index.js
 const api = require('./api')
 const proxy = require('./proxy')
 api.listen(4000)
 proxy.listen(3000)

或者通过baseURL⽅式
axios.defaults.baseURL = 'http://localhost:4000'

  • 浏览器抛出跨域错误


    origin.jpg
  • 常⽤解决⽅案:
  1. JSONP(JSON with Padding),前端+后端⽅案,绕过跨域

前端构造script标签请求指定URL(由script标签发出的GET请求不受同源策略限制),服务器返回⼀个函数执⾏语句,该函数名称通常由查询参callback的值决定,函数的参数为服务器返回的json数据。该函数在前端执⾏后即可获取数据。

  1. 代理服务器

请求同源服务器,通过该服务器转发请求⾄⽬标服务器,得到结果再转发给前端。
前端开发中测试服务器的代理功能就是采⽤的该解决⽅案,但是最终发布上线时如果web应⽤和
接⼝服务器不在⼀起仍会跨域。

  1. CORS(Cross Origin Resource Share) - 跨域资源共享,后端⽅案,解决跨域
    预检请求

原理:cors是w3c规范,真正意义上解决跨域问题。它需要服务器对请求进⾏检查并对响应头做相应处理,从⽽允许跨域请求。
具体实现:

  • 响应简单请求: 动词为get/post/head,没有⾃定义请求头,Content-Type是application/x-wwwform-urlencoded,multipart/form-data或text/plain之⼀,通过添加以下响应头解决:

res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000')
该案例中可以通过添加⾃定义的x-token请求头使请求变为preflight请求

// index.html
axios.defaults.baseURL = 'http://localhost:3000';
axios.get("/users", {headers:{'X-Token':'jilei'}})
  • 响应preflight请求,需要响应浏览器发出的options请求(预检请求),并根据情况设置响应头:
else if (method == "OPTIONS" && url == "/api/users") {
 res.writeHead(200, {
       "Access-Control-Allow-Origin": "http://localhost:3000",
       "Access-Control-Allow-Headers": "X-Token,Content-Type",
      "Access-Control-Allow-Methods": "PUT"
});
      res.end();
}

则服务器需要允许x-token,若请求为post,还传递了参数:

// index.html
axios.post("http://localhost:3000/users", {foo:'bar'}, {headers:{'XToken':'jilei'}})
// http-server.js
else if ((method == "GET" || method == "POST") && url == "/users") {}

则服务器还需要允许content-type请求头

  • 如果要携带cookie信息,则请求变为credential请求:
// index.js
// 预检options中和/users接⼝中均需添加
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 设置cookie
res.setHeader('Set-Cookie', 'cookie1=va222;')
// index.html
// 观察cookie存在
console.log('cookie',req.headers.cookie)
// ajax服务
axios.defaults.withCredentials = true

Proxy代理模式

var express = require('express');
const proxy = require('http-proxy-middleware')
const app = express()
app.use(express.static(__dirname + '/'))
app.use('/api', proxy({ target: 'http://localhost:4000', changeOrigin: false}));
module.exports = app

对⽐⼀下nginx 与webpack devserver

// vue.config.js
 module.exports = {
        devServer: {
            disableHostCheck: true,
            compress: true,
            port: 5000,
            proxy: {
                '/api/': {
                    target: 'http://localhost:4000',
                    changeOrigin: true,
                },
            },
        },
    }

nginx

server {
        listen 80;
        # server_name www.josephxia.com;
        location / {
            root /var/www/html;
        index index.html index.htm;
        try_files $uri $uri / /index.html;
    }
    location / api {
        proxy_pass http://127.0.0.1:3000;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X - Real - IP $remote_addr;
        proxy_set_header X - Forwarded - For $proxy_add_x_forwarded_for;
    }
 }

Bodyparser

  • application/x-www-form-urlencoded
<form action="/api/save" method="post">
 <input type="text" name="abc" value="123">
 <input type="submit" value="save">
</form>
// api.js
    else if (method === "POST" && url === "/api/save") {
        let reqData = [];
        let size = 0;
        req.on('data', data => {
            console.log('>>>req on', data);
            reqData.push(data);
            size += data.length;
        });
        req.on('end', function () {
            console.log('end')
            const data = Buffer.concat(reqData, size);
            console.log('data:', size, data.toString())
            res.end(`formdata:${data.toString()}`)
        });
    }
  • application/json
await axios.post("/api/save", {
     a: 1,
     b: 2
 })
// 模拟application/x-www-form-urlencoded
await axios.post("/api/save", 'a=1&b=3', {
 headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
   },
})

上传⽂件

// Stream pipe
request.pipe(fis)
response.end()
// Buffer connect
request.on('data',data => {
   chunk.push(data)
   size += data.length
    console.log('data:',data ,size)
})
request.on('end',() => {
   console.log('end...')
   const buffer = Buffer.concat(chunk,size)
   size = 0
   fs.writeFileSync(outputFile,buffer)
   response.end()
})
// 流事件写⼊
request.on('data', data => {
 console.log('data:',data)
 fis.write(data)
})
request.on('end', () => {
 fis.end()
 response.end()
})

实战⼀个爬⾍

原理:服务端模拟客户端发送请求到⽬标服务器获取⻚⾯内容并解析,获取其中关注部分的数据。

// spider.js
    const originRequest = require("request");
    const cheerio = require("cheerio");
    const iconv = require("iconv-lite");
    function request(url, callback) {
        const options = {
            url: url,
            encoding: null
        };
        originRequest(url, options, callback);
    }
    for (let i = 100553; i < 100563; i++) {
        const url = `https://www.dy2018.com/i/${i}.html`;
        request(url, function (err, res, body) {
            const html = iconv.decode(body, "gb2312");
            const $ = cheerio.load(html);
            console.log($(".title_all h1").text());
        });
    }

实现⼀个即时通讯IM

  • Socket实现
    原理:Net模块提供⼀个异步API能够创建基于流的TCP服务器,客户端与服务器建⽴连接后,服务器可以获得⼀个全双⼯Socket对象,服务器可以保存Socket对象列表,在接收某客户端消息时,推送给其他客户端。
// socket.js
    const net = require('net')
    const chatServer = net.createServer()
    const clientList = []
    chatServer.on('connection', client => {
        client.write('Hi!\n')
        clientList.push(client)
        client.on('data', data => {
            console.log('receive:', data.toString())
            clientList.forEach(v => {
                v.write(data)
            })
        })
    })
    chatServer.listen(9000)

通过Telnet连接服务器

telnet localhost 9000

  • Http实现
    原理:客户端通过ajax⽅式发送数据给http服务器,服务器缓存消息,其他客户端通过轮询⽅式查询最新数据并更新列表。
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app">
        <input v-model="message">
        <button v-on:click="send">发送</button>
        <button v-on:click="clear">清空</button>
        <div v-for="item in list">{{item}}</div>
    </div>
    <script>
        const host = 'http://localhost:3000'
        var app = new Vue({
            el: '#app',
            data: {
                list: [],
                message: 'Hello Vue!'
            },
            methods: {
                send: async function () {
                    let res = await axios.post(host + '/send', {
                        message: this.message
                    })
                    this.list = res.data
                },
                clear: async function () {
                    let res = await axios.post(host + '/clear')
                    this.list = res.data
                }
            },
            mounted: function () {
                setInterval(async () => {
                    const res = await axios.get(host + '/list')
                    this.list = res.data
                }, 1000);
            }
        });
    </script>
</body>
</html>
const express = require('express')
    const app = express()
    const bodyParser = require('body-parser');
    const path = require('path')
    app.use(bodyParser.json());
    const list = ['ccc', 'ddd']
    app.get('/', (req, res) => {
        res.sendFile(path.resolve('./index.html'))
    })
    app.get('/list', (req, res) => {
        res.end(JSON.stringify(list))
    })
    app.post('/send', (req, res) => {
        list.push(req.body.message)
        res.end(JSON.stringify(list))
    })
    app.post('/clear', (req, res) => {
        list.length = 0
        res.end(JSON.stringify(list))
    })
    app.listen(3000);
  • Socket.IO实现
  • 安装: npm install --save socket.io
  • 两部分:nodejs模块,客户端js
// 服务端:chat-socketio.js
    var app = require('express')();
    var http = require('http').Server(app);
    var io = require('socket.io')(http);
    app.get('/', function (req, res) {
        res.sendFile(__dirname + '/index.html');
    });
    io.on('connection', function (socket) {
        console.log('a user connected');
        //响应某⽤户发送消息
        socket.on('chat message', function (msg) {
            console.log('chat message:' + msg);

            // ⼴播给所有⼈
            io.emit('chat message', msg);
            // ⼴播给除了发送者外所有⼈
            // socket.broadcast.emit('chat message', msg)
        });
        socket.on('disconnect', function () {
            console.log('user disconnected');
        });
    });
    http.listen(3000, function () {
        console.log('listening on *:3000');
    });
// 客户端:index.html
<!DOCTYPE html>
<html>

<head>
    <title>Socket.IO chat</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font: 13px Helvetica, Arial;
        }

        form {
            background: #000;
            padding: 3px;
            position: fixed;
            bottom: 0;
            width: 100%;
        }

        form input {
            border: 0;
            padding: 10px;
            width: 90%;
            margin-right: 0.5%;
        }

        form button {
            width: 9%;
            background: rgb(130, 224, 255);
            border: none;
            padding: 10px;
        }

        #messages {
            list-style-type: none;
            margin: 0;
            padding: 0;
        }

        #messages li {
            padding: 5px 10px;
        }

        #messages li:nth-child(odd) {
            background: #eee;
        }
    </style>
</head>

<body>
    <ul id="messages"></ul>
    <form action="">
        <input id="m" autocomplete="off" /><button>Send</button>
    </form>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js">
    </script>
    <script src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"></script>
    <script>
        $(function () {
            var socket = io();
            $("form").submit(function (e) {
                e.preventDefault(); // 避免表单提交⾏为
                socket.emit("chat message", $("#m").val());
                $("#m").val("");
                return false;
            });

            socket.on("chat message", function (msg) {
                $("#messages").append($("<li>").text(msg));
            });
        });
    </script>
</body>

</html>

Socket.IO库特点:
源于HTML5标准
⽀持优雅降级
WebSocket
WebSocket over FLash
XHR Polling
XHR Multipart Streaming
Forever Iframe
JSONP Polling

Https

  • 创建证书
# 创建私钥
openssl genrsa -out privatekey.pem 1024
# 创建证书签名请求
openssl req -new -key privatekey.pem -out certrequest.csr
# 获取证书,线上证书需要经过证书授证中⼼签名的⽂件;下⾯只创建⼀个学习使⽤证书
openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out
certificate.pem
# 创建pfx⽂件
openssl pkcs12 -export -in certificate.pem -inkey privatekey.pem -out
certificate.pfx

Http2

  • 多路复⽤ - 雪碧图、多域名CDN、接⼝合并
    官⽅演示 - https://http2.akamai.com/demo
  • 多路复⽤允许同时通过单⼀的 HTTP/2 连接发起多重的请求-响应消息;⽽HTTP/1.1协议中,
    浏览器客户端在同⼀时间,针对同⼀域名下的请求有⼀定数量限制。超过限制数⽬的请求会
    被阻塞**
  • ⾸部压缩
    http/1.x 的 header 由于 cookie 和 user agent很容易膨胀,⽽且每次都要重复发送。
    http/2使⽤ encoder 来减少需要传输的 header ⼤⼩,通讯双⽅各⾃ cache⼀份
    header fields 表,既避免了重复 header 的传输,⼜减⼩了需要传输的⼤⼩。⾼效的压
    缩算法可以很⼤的压缩 header,减少发送包的数量从⽽降低延迟
  • 服务端推送
    在 HTTP/2 中,服务器可以对客户端的⼀个请求发送多个响应。举个例⼦,如果⼀个请
    求请求的是index.html,服务器很可能会同时响应index.html、logo.jpg 以及 css 和 js
    ⽂件,因为它知道客户端会⽤到这些东⻄。这相当于在⼀个 HTML ⽂档内集合了所有的
    资源

资料来自:开课吧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容