05-网络服务

470 阅读10分钟

网络服务-http.req

属性/方法/事件 分类

http.IncomingMessage的属性/方法/事件 不是特别多,按照是否客户端/服务端 特有的,下面进行简单归类。可以看到

* 服务端处特有:url
* 客户端处特有:statusCode、statusMessage

| 类型 |     名称      | 服务端 | 客户端 |
| :--- | :-----------: | :----: | :----: |
| 事件 |    aborted    |   ✓    |   ✓    |
| 事件 |     close     |   ✓    |   ✓    |
| 属性 |    headers    |   ✓    |   ✓    |
| 属性 |  rawHeaders   |   ✓    |   ✓    |
| 属性 |  statusCode   |   ✕    |   ✓    |
| 属性 | statusMessage |   ✕    |   ✓    |
| 属性 |  httpVersion  |   ✓    |   ✓    |
| 属性 |      url      |   ✓    |   ✕    |
| 属性 |    socket     |   ✓    |   ✓    |
| 方法 |  .destroy()   |   ✓    |   ✓    |
| 方法 | .setTimeout() |   ✓    |   ✓    |

adorted close 服务端

// abort请求时候, 服务端req的aborted close 事件都会触发,
// 请求正常完成时 服务端req的close 事件不会触发
const server = http.createServer((req, res) => {
  req.on("aborted", function () {
    console.log("客户端请求aborted");
  });
  req.on("close", function () {
    console.log("客户端请求close"); // 正常响应数据时不会触发这个
  });
  res.end("ok");
});

const server = http.createServer((req, res) => {
  req.on("close", function () {
    console.log("close");
  });
  req.socket.on("close", function () {
    // 正常响应数据的时候这个可以触发
    console.log("socket close");
  });
  res.end("ok");
});

adorted close 客户端

和服务端是一样的情况

  const server = http.createServer((req, res) => {
    console.log('1. 服务端接收到客户端请求');
    res.flushHeaders()
    res.setTimeout(100)
  })
  server.on('error', function () { })

  server.listen(3000, function () {
    const client = http.get('http://127.0.0.1:3000/aborted', function (res) {
      console.log('2. 客户端收到服务端响应');
      res.on('aborted', function () {
        console.log('3. 客户端 abouted触发');
      })
      res.on('close', function () {
        console.log('4. 客户端close 触发');
      })
    })
  })

  1. 服务端接收到客户端请求
  2. 客户端收到服务端响应
  3. 客户端 abouted触发
  4. 客户端close 触发

destroy

req 的 error 不会捕捉到 destory req.socket 的 error 会捕捉到 destory

    console.log('服务端 收到客户端请求');
    res.destroy(new Error('测试 发送 destory'))

    res.on('error', function (error) {
      console.log('服务端 req error', error.message);
    })
    res.socket.on('error', function (error) {
      console.log('服务端 req socket error', error.message);
    })
  })
  server.on('error', function (error) {
    console.log('服务端 server error', error.message);
  })
  server.listen(3000, function () {
    const client = http.get('http://127.0.0.1:3000/aborted', function (res) {
      console.log();
    })
    client.on('error', function (err) {
      console.log('客户端 client error', err.message);

    })
  })

  服务端 收到客户端请求
  服务端 req socket error 测试 发送 destory
  客户端 client error socket hang up

不常用属性

rawHeaders  未解析前的request header
trailers  在分块传输中用到, 标识 trailer 后的 header 可分块传输
rawTrailers:

网络服务-http.res

设置状态码 状态描述信息

const server = http.createServer((req, res) => {
  // 可以
  res.writeHead(200, "ok");

  // 也可以
  res.statusCode = 200;
  res.statusMessage = "ok";

  // 差异
  // 1. res.writeHead() 可以提供额外的功能, 比如设置响应头
  // 2. 当响应头部发送出去后, res.statusCode/res.statusMessage, 会被设置成已发送出去的 状态代码 / 状态描述信息
});

设置响应头

const server = http.createServer((req, res) => {
  // res 提供了 res.writeHead 和 res.setHeader 来实现响应头的设置

  // 方法一
  res.writeHead(200, "ok", {
    "Content-Type": "text/html",
  });

  // 方法二
  res.setHeader("Content-Type", "text-plain");

  // 差异:
  // 1. res.writeHead 不仅仅可以设置 header
  // 2. 已经通过 res.setHeader 设置了 header, 当通过 res.writeHead 设置同名header, res.writeHead 的设置会覆盖之前的设置. 但是无法先 writeHead 后 setHeader
  res.end("ok");
});

其他响应头操作

// 增
res.setHeader("Content-Type", "text/plain");
// 删
res.removeHeader("Content-Type");
// 改
res.setHeader("Content-Type", "text/plain");
res.setHeader("Content-Type", "text/html"); // 覆盖
// 查
res.getHeader("content-type");

// res.getHeader 不同之处在于, name用的是小写, 返回值没做任何处理
res.setHeader("Content-Type", "TEXT/HTML");
console.log(res.getHeader("content-type")); // TEXT/HTML

res.setHeader("Content-Type", "text/plain");
console.log(res.getHeader("content-type")); // text/plain

// 此外还有不常用的
// res.headersSent: header 是否已发送
// res.sendDate: 默认为 true, 但是为 true 的时候, 会在 response header 里自动设置 Date 首部

response.write 和 response.end

  主要用到 res.write() 以及 res.end 两个方法

  res.write(chunk, [, encoding][, callback])
    1. chunk 响应主体的内容, 可以是string, 也可以是buffer, 当为string是, encoding参数用来指名编码方式
    2. encoding 编码方式, 默认是utf-8
    3. callback 当响应体flushed时触发,

    注意事项
    1. 如果 res.write 被调用时, res,writeHead 还没被调用过, 那么就会把 header flush 出去
    2. res.write 可以被调用多次
    3. 当 res.write(chunk) 第一次被调用时候, node 会将 header 信息以及 chunk 发送到客户端, 第二次调用 res.write(chunk) node 会认为假定数据将被流式传输并单独发送新数据。

  res.end([data][, encoding][, callback])
    res.end 的作用就是告诉 node, 响应的 header 和 body 都已经返回了, 响应结束, 用法类似语法糖, 类似如下两个调用的组合, callback 当响应传递结束后出发
    res.write(data, encoding)
    res.end()

chunk 数据

// 如果是text/html
http
  .createServer((req, res) => {
    res.setHeader("Content-Type", "text/html; charsetutf-8");
    res.write("hello");
    setTimeout(() => {
      res.write("world");
      res.end();
    }, 2000);
  })
  .listen(3000);

// 如果是text/plain
http
  .createServer((req, res) => {
    res.writeHead(200, {
      "Content-Type": "text/plain; charset=utf-8",
      "X-Content-Type-Options": "nosniff",
    });
    res.write("hello");
    setTimeout(() => {
      res.write("world");
      res.end();
    }, 2000);
  })
  .listen(3100);

// 失败的情况
http
  .createServer((req, res) => {
    res.writeHead(200, "ok", {
      "Content-Type": "text/html",
    });
    res.write("hello");
    setTimeout(() => {
      res.write("world");
      res.end();
    }, 1000);
  })
  .listen(3101);

超时处理

respose.setTimeout(msecs, callback);

http
  .createServer((req, res) => {
    res.setHeader("Content-Type", "text/html; charsetutf-8");
    res.write("hello");
    res.setTimeout(3000, function (res) {
      console.log(res);
    });
    // res.end()//不返回数据模拟超时
  })
  .listen(3000);

事件 close finish

  close: respose.end() 被调用前, 连接就断开了, 此时会触发这个事件
  finish: 响应 header body 都已经发送出去, 到客户端是否实际收到数据为止. 此事件执行后将无其他事件触发

其他不常用属性和方法

  response.finished 一开始是 false, 响应结束后设置为 true
  response.sendDate 默认是 true, 是否自动设置 Date 头部
  response.headersSent 只读属性, 响应头部是否已发送
  response.writeContinue 发送HTTP1.1 100 Continue 消息给客户端, 提示说服务端愿意接受客户端的请求, 请继续发送请求正文

网络服务-http.client

简单的 GET 请求

const options = {
  protocol: "http:",
  hostname: "id.qq.com",
  port: "80",
  path: "/",
  method: "GET",
};
const client = http.request(options, function (res) {
  let data = "";
  res.setEncoding("utf-8");
  res.on("data", (chunk) => {
    data += chunk;
  });
  res.on("end", () => {
    console.log(data);
  });
});
client.end();

// 简写
http.get("http://id.qq.com", function (res) {
  let data = "";
  res.setEncoding("utf-8");
  res.on("data", function (chunk) {
    data += chunk;
  });
  res.on("end", function () {
    console.log(data);
  });
});

简单的 POST 请求

// 1. method 指定为 POST
// 2. headers 里面声明 content-type 为 application/x-www-form-urlencoded
// 3. 数据发送前, 用 querystring.stringify(obj) 对传输的对象进行格式化

const createClientPostRequest = function () {
  const options = {
    method: "POST",
    protocol: "http:",
    hostname: "127.0.0.1",
    port: "3000",
    path: "/post",
    headers: {
      connection: "keep-alive",
      "content-type": "application/x-www-urlencoded",
    },
  };
  // 发送给服务端的数据
  const postBody = {
    nick: "abc",
  };

  // 创建客户端请求
  const client = http.request(options, function (res) {
    // 最终输出 Server got client data nick=abc
    res.pipe(process.stdout);
  });
  client.write(querystring.stringify(postBody));
  client.end();
};
const server = http.createServer(function (req, res) {
  res.write("server go client data");
  req.pipe(res);
});
server.listen(3000, createClientPostRequest);

各种事件

// 官方文档内, http.RequestClient 相关的事件共有7个. 和http协议密切相关的有三个, 分别是connect, continue, upgrade, 其他四个分别是: abort, aborted, socker, response
// 其他: abort, aborted, socket, response
// 与http协议相同 connect, continue, upgrade

respose 事件

// 当接收到服务端的响应是触发, 其实跟http.get(url, obj)中的回调是一样的,
function server3(params) {
  const url = ' http:id.qq.com'
  const client = http.get(url, function (res) {
    console.log('1 response event');
  })
  client.on('response', function (res) {
    console.log('2 response event');

  })
  client.end()
}

1 response event
2 response event

网络服务-http.server

获取请求方信息

// HTTP版本 http method , http Headers, url
const server = http.createServer((req, res) => {
  console.log("客户请求url", req.url);
  console.log("http版本", req.httpVersion);
  console.log("http请求方法", req.method);
  res.end("OK");
});
server.listen(3000);

// http://127.0.0.1:3000/hello
// 客户请求url / hello
// http版本 1.1
// http请求方法 POST

获取 get 请求参数

const server = http.createServer((req, res) => {
  const urlObj = url.parse(req.url);
  const query = urlObj.query;
  const queryObj = querystring.parse(query);
  console.log(JSON.stringify(queryObj));
  res.end("ok");
});
server.listen(3000);

// http://127.0.0.1:3000/hello?local=en
// {"local":"en"}

获取 post 请求参数

const server = http.createServer((req, res) => {
  res.setHeader("Content-type", "text/html;charset=utf8");
  console.log("req", req);
  let body = "";
  req.on("data", (thunk) => {
    body += thunk;
  });
  req.on("end", () => {
    console.log("post body is", body);
    res.end("ok");
  });
});
server.listen(3000);
// {"aaa":111}
// post body is {"aaa":111}

事件

// 事件包含 checkContinue  checkExpectation  clientError  close  connect  request   upgrade

error

const server = http.createServer(() => {});
const server1 = http.createServer(() => {});
server.on("error", (e) => {
  console.log("出错了", e.message);
});
server1.listen(3000, () => {
  server.listen(3000);
});
// http://127.0.0.1:3000/hello?local=en
// 出错了 listen EADDRINUSE: address already in use :::3000

connect 和 connection

// 两者差别非常的大, 虽然看起来很相似
// connect: 当客户端的HTTP method 为 connect 时触发
// connection 当 TCP 连接建立时触发, 大部分时候可以忽略这个事件(目前模块自己用到而已).此外, 可以通过 req.connection 来获取这个socket(从nodejs源码来看, req.socket req.connection 都指向了这个socket).此外socket上的 readable事件不会触发

// 大部分时候都不会用到, 除非你要开发http代理, 当客户发起connect请求是触发, (绕过了 requestListener)

const server = http.createServer((req, res) => {
  res.end("OK");
  req.on("connect", (req, socket, head) => {
    console.log("触发了connect事件");
    socket.end();
  });
});

// 监听connect事件,有http connect请求时触发
server.on("connect", (req, clientSocket, head) => {
  console.log("会进来这里");
});
server.listen(3000, "127.0.0.1", () => {
  // 发起http connect请求
  const request = http.request({
    port: 3000,
    // 连接的代理服务器地址
    host: "127.0.0.1",
    method: "CONNECT",
  });
  request.end();
  request.on("connect", (res, socket, head) => {
    // 发送真正的请求
    console.log("23231");
    // 发送真正的请求
    socket.write("");
    socket.on("data", (chunk) => {});
    socket.on("end", () => {});
  });
});

request keep-alive

// 当有新的连接到来的时候触发, 那么和 connection 有什么区别呢?
// keep-alive 在持久化的链接的情况下, 多个 request 可能对应的是一个 connection

let requestIndex = 0;
let connectionIndex = 0;
const server = http.createServer((req, res) => {
  res.end("OK啦");
});
server.on("request", (req, res) => {
  requestIndex++;
  console.log("当前是 request 请求", requestIndex);
});
server.on("connection", (req, res) => {
  connectionIndex++;
  console.log("当前是 connection 请求", connectionIndex);
});
server.listen(3000);

// 不在请求头加 connection keep-alive
// 当前是 connection 请求 1
// 当前是 request 请求 1
// 当前是 connection 请求 2
// 当前是 request 请求 2

// 在请求头加 connection keep-alive
// 当前是 connection 请求 1
// 当前是 request 请求 1
// 当前是 request 请求 2
// 当前是 request 请求 3

不常用接口

server.close([callback])
关闭服务器, 其实就是(new net.Server()).close(), 停止接受新的连接.
已经连接上的请求会继续处理, 当所有的链接结束的时候, server正式关闭, 并抛出close事件.
一般提供了callback, 就不同监听close, 监听了close 就不用添加callback

server.listen()
其实除了 server.listen(PORT) 这种监听的方式之外, 还有几种其他的不同的监听方式
server.listen(handle[, callback]) 监听本地文件描述符(fd)(window不支持), 或者server, 或者 socket
server.listen(path[, callback]) 监听本地socket, 创建一个 UNIX socket server
server.listen([port][, hostname][, backlog][, callback])

网络超时 server.setTimeout(msecs, callback)
设置网络连接的超时时间, 当超过msecs没有相应的时候, 网络就会自动断开
如果传了callback, name当timeout发生时, 就会timeout的socket作为参数传给callback
注意, 一般情况下超时的socket会自动销毁.但是当传入callback后, 就需要手动end或者destroy这个socket

不常用属性

server.listening: 是否在监听连接
server.timeout: 设置超时的时间(毫秒), 修改这个值, 只会对新建立的链接产生影响, 此外, 将 timeout 设置为0, 就会禁用自动超时行为
server.maxHeadersCount: 客户端最多传送的header数量, 默认是1000, 如果设置为0, 则没有限制,

网络服务-https

模块概览
主要包含两个部分的内容
1. 通过客户端, 服务端的例子, 对https模块进行入门讲解
2. 如何访问安全证书不受信任的网站

  客户端例子
// 跟http模块的用法非常的相似, 只不过是请求的地址是https协议的而已, 代码如下
  https.get('https://www.qq.com/', res => {
    console.log('status code', res.statusCode);
    console.log('headers', JSON.stringify(res.headers));
    res.on('data', data => {
      process.stdout.write(data)
    })
  }).on('error', err => {
    console.log(err.message);
  })

生成证书

// 创建目录存放证书
// mkdir cert
// cd cert

// 生成私钥
// openssl genrsa -out chyingp-key.pem 2048

// 生成证书签名请求(csr是 certificate signing request 的意思)
// openssl req -new \
// -sha256
// -key chyingp-key.key.pem \
// -out chyingp-csr.pem \
// -subj "/C=CN/ST=Guandong/L=Shenzhen/O=YH Inc/CN=www.chyingp.com"

// 生成证书
// openssl x509 \
// -req -in chyingp - csr.pem \
// -signkey chyingp - key.pem \
// -out chyingp - cert.pem

HTTPS 服务端

const option = {
  key: fs.readFileSync("./cert/chyingp-key.pem"),
  cert: fs.readFileSync("./cert/chyingp-cert.pem"),
};
const server = https.createServer(option, (req, res) => {
  res.end("这是来自HTTPS服务器");
});
server.listen(3000);

访问安全证书不受信任的网站

处理方式
1. 停止访问
2. 无视警告, 继续访问
3. 导入CA根证书

触发安全限制

https
  .get("https://kyfw.12306.cn/otn/regist/init", (res) => {
    res.on("data", (data) => {
      process.stdout.write(data);
    });
  })
  .on("error", (err) => {
    console.log(err);
  });
// Error: unable to verify the first certificate
//     at TLSSocket.onConnectSecure (node:_tls_wrap:1532:34)
//     at TLSSocket.emit (node:events:527:28)
//     at TLSSocket._finishInit (node:_tls_wrap:946:8)
//     at TLSWrap.ssl.onhandshakedone (node:_tls_wrap:727:12) {
//   code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
// }
// 触发错误机制, 提示安全证书不可靠, 拒接继续访问
// 处理方法
// 1. 不建议: 忽略安全警告, 继续访问
// 2. 建议: 将12306的CA加入授信列表

方法一: 忽略安全警告,继续访问

const option = {
  hostname: "kyfw.12306.cn",
  path: "/otn/leftTicket/init",
  rejectUnauthorized: false, //忽略安全警告
};
const req = https.get(option, (res) => {
  res.pipe(process.stdout);
});
req.on("error", (err) => {
  console.log(err.code);
});

方法二: 将 12306 的 CA 加入受信列表

// 1.下载12306的CA证书
// 2.将der格式的CA证书转成pem格式
// 3.修稿node https 的配置

// 1. 在12306官网下, 提供了CA证书的下载, 将他保存到本地并命名为 arca.cer
// 2. https初始化的时候, 提供了ca这个配置项,可以将12306的CA证书添加进去,当你访问12306的时候,client就会用ca配置项内的ca证书,对当前的证书进行校验,于是就校验通过了
// 2. 需要注意的是,ca配置项只支持pem格式,而从12306官网下载的是der格式的,需要转换格式才可以使用,
// 3. 修改node https的配置即可
const fs = require("fs");
const ca = fs.readFileSync("./srca.cer.pem");
const option = {
  hostname: "kyfw.12306.cn",
  path: "/otn/leftTicket/init",
  ca: [ca],
};
const req = https.get(option, (res) => {
  res.pipe(process.stdout);