# 网络编程 http https http2 websocket

# HTTP协议

// 观察HTTP协议
curl -v http://www.baidu.com
  • http协议详解 (opens new window)

  • 创建接口,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'
    

    浏览器抛出跨域错误

  • 常用解决方案:

    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-www-form-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:{'X-Token':'jilei'}})
// http-server.js
else if ((method == "GET" || method == "POST") && url == "/users") {}

如果要携带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

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;
     }
 }

# 正向代理和反向代理

https://www.zhihu.com/question/24723688

两者的区别在于代理的对象不一样:正向代理代理的对象是客户端,反向代理代理的对象是服务端

正向代理隐藏真实客户端,反向代理隐藏真实服务端

# 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
     })
    

# 实战⼀个爬虫

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

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

# 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');
});
<!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 文档内集合了所有的资源

Last Updated: 12/17/2021, 5:53:32 PM