Node.js高级编程读书笔记 - 3 网络编程

时间:2021-11-16 07:18:24

Outline

  • 3.4 构建TCP服务器
  • 3.5 构建HTTP服务器
  • 3.6 构建TCP客户端
  • 3.7 创建HTTP请求
  • 3.8 使用UDP
  • 3.9 用TLS/SSL保证服务器的安全性
  • 3.10 用HTTPS保证HTTP服务器的安全性

3.4 构建TCP服务器

TCP服务器对象通过require("net").createServer()创建,它是一个事件发射器(event emitter),发射事件包括:listening, connection, close, error

TCP server socket的生命周期这里不再阐述,可以去看APUE、UNP。

a sample: tcp_chat_server.js

var net = require("net");

var server = net.createServer();
var port = 4001;

// keep track of all client connections
var sockets = [];

// event: listening
server.on("listening", function(){
  console.log("Server is listening on port", port);
})

// event: client socket connect
server.on("connection", function(socket){
  console.log("get a new connection");

  sockets.push(socket);
  // read data
  socket.on("data", function(data){
    console.log("get data: ", data.toString());

    // multicast the data to all the other clients
    sockets.forEach(function(clientSocket){
      if(clientSocket !== socket){
    clientSocket.write(data);
      }
    });
  });

  // handle client connection closed event
  socket.on("close", function(){
    console.log("connection closed");
    var index = sockets.indexOf(socket);
    sockets.splice(index, 1);
  });
});

// event: server errors
server.on("error", function(error){
  console.log("Server error: ", error.message);
});

// event: server closed
server.on("close", function(){
  console.log("Server closed");
})

// listen to port
server.listen(port);

socket对象和一些socket选项

server.on("connection", function(socket){...生成的socket对象也是一个事件发射器,其发射事件包括: data, close, error, timeout等。同时socket对象也是可读可写的流对象。

设置socket选项

(1) timeout

socket.setTimeout(60000, function(){
    socket.end("idle timeout, bye");
});

(2) keepAlive

socket.setKeepAlive(true, 10000);// 10s

(3) nodelay

socket.setNoDelay(true);// switch off Nagle algorithm

3.5 构建HTTP服务器

请求信息查看

http_request_information.js

/**
renderer all information of HTTP requests with request body
*/
var http = require("http");
var util = require("util");

var port = 8888;

http.createServer(function(request, response){
  var result = "";
  result += "url=" + request.url + "\n";
  result += "method=" + request.method + "\n";
  // inspect object's attributes
  result += "headers=" + util.inspect(request.headers) + "\n";
  result += "...HEADER END...";

  // process request body
  request.on("data", function(data){
    result += data.toString();

    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write(result);
    response.end("...BODY END...");
  });

}).listen(port, function(){
  console.log("listening on port: ", port);
});

响应:
Node.js高级编程读书笔记 - 3 网络编程

一个静态文件访问HTTP服务器

HTTP分块编码允许服务器持续向客户断发送数据,而不需要指定发送数据的大小,即响应头部总是Transfer-Encoding: chuncked,除非指定了Content-Length。

static_file_server.js

/**
demonstration of HTTP Server: providing static file resources
*/
var http = require("http");
var path = require("path");
var fs = require("fs");

var port = 8888;
var server = http.createServer();

// handle HTTP requests
// callback parameters: http.IncomingMessage, http.ServerResponse
server.on("request", function(request, response){
  var file = path.normalize("."+request.url);
  console.log("request file: " + file);

  // generate server error response
  function generateServerError(error){
    console.log("Error: ", error);
    response.writeHead(500);
    response.end("Internal Server Error");
  }

fs.exists(file, function(exists){
  if(exists){
    fs.stat(file, function(error, stat){
      if(error){
    generateServerError(error);
      }

      if(stat.isDirectory()){
    response.writeHead(403);
    response.end("Directory Access Forbidden");
      } else{
    var readStream = fs.createReadStream(file);
    readStream.on("error", generateServerError);

    response.writeHead(200);
    readStream.pipe(response);
      }
    });
  } else{
    response.writeHead(404);
    response.end("Not Found.");
  }
});

});

// binding port
server.listen(port, function(){
  console.log("listening on port: ", port);
});

响应:
Node.js高级编程读书笔记 - 3 网络编程

头部信息:
Node.js高级编程读书笔记 - 3 网络编程

3.6 构建TCP客户端

TCP客户端对象connection通过require("net").createConnection(<port>, [<host>])创建,它也是一个事件发射器和可读可写流。发射事件包括:connecterrordataclose等。

一些方法

// 关闭连接
connection.end("Bye", "utf8");

// 向stdout输出服务器响应
connection.pipe(process.stdout, {end: false});

tcp_retry_client.js

/**
demonstration of periodly retried TCP client
*/

var net = require("net");
var port = 4001;
// a flag represent client want to quit
var quitFlag = false;

var connection;
var tryTimeout = 3000; //3s
var hasRetriedTimes = 0;
var maxRetryTimes = 10;

//open current process's stdin, prepare to write to connection
process.stdin.resume();
// listen on previous stdin's 'data' event
process.stdin.on("data", function(data){
  if(data.toString().trim().toLowerCase() === "quit"){
    quitFlag = true;
    console.log("quitting...");
    connection.end();//close the connection
  } else{
    connection.write(data);// write to connection
  }
});

// the logic of retry to connection
(function connect(){
  function retryConnect(){
    if(hasRetriedTimes >= maxRetryTimes){
      throw new Error("Has retried "+ maxRetryTimes + " time, giving up.");
    }
    hasRetriedTimes += 1;
    setTimeout(connect, tryTimeout);
  }

  // create the connection: localhost
  connection = net.createConnection(port);
  // listen to connection's events
  connection.on("connect", function(){
    hasRetriedTimes = 0;
    console.log("try connect to server");
  });
  connection.on("error", function(error){
    console.log("Error occurs when connect to server: ", error);
  });
  connection.on("close", function(){
    if(!quitFlag){
      console.log("connection lost, try to reconnect");
      retryConnect();
    }
  });

// write server response to stdout
connection.pipe(process.stdout, {end: false});

}());//call it immediately

3.7 创建HTTP请求

built-in http.request() and shortcut methods

http_request.js: usage of http.request

/**
demonstration of http.request()
*/

var http = require("http");

var options = {
  host: "www.google.com",
  port: 80,
  method: "POST",
  path: "/upload",
  headers: {}
};

var request = http.request(options, function(response){
  console.log(response.statusCode);// or use uitl.inspect()
  console.log(response.httpVersion);
  console.log(response.headers);

  // 1 parse body as string
  // response.setEncoding("utf8");
  // response.on("data", function(data){
  //   console.log(data);
  // });
// 2 parse body as stream
var aWriteStream = require("fs").createWriteStream("./local.txt");
response.pipe(aWriteStream);

}).on("error", function(error){
  console.log("Error: ", e);
});

// write request body
request.write("data1\n");
request.write("data2\n");
request.end();// must call this

http_get.js: usage of shortcut methods

/**
demonstration of http.get()
*/
var http = require("http");

var options = {
  host: "www.google.com",
  port: 80,
  method: "GET",
  path: "/index.html",
  headers: {}
};

// http.get(): call response.end() automatically
http.get(options, function(response){
  console.log(response.statusCode);// status code
  console.log(response.headers);// headers

  // access body
  response.setEncoding("utf8");
  response.on("data", function(data){
    console.log("Body="+data);
  });
  //console.log(response);
}).on("error", function(error){
  console.log("Error: ", e);
});

http_agent.js: maintain uderling socket pool

/**
demonstration of http.Agent to maintain socket pool
*/

var http = require("http");

// agent options
var agentOptions = {
  maxSockets: 10// override the default 5
}

var options = {
  host: "www.google.com",
  port: 80,
  method: "POST",
  path: "/upload",
  headers: {},
  //agent: false// false means donot use the socket pool
  agent: new http.Agent(agentOptions)// define the specific agent
};

var request = http.request(options, function(response){
  console.log(response.statusCode);// or use uitl.inspect()
  console.log(response.httpVersion);
  console.log(response.headers);

  response.setEncoding("utf8");
  response.on("data", function(data){
    console.log(data);
  });

}).on("error", function(error){
  console.log("Error: ", e);
}).end();

使用request模块

安装

npm install request

http_server.js: the HTTP server used to test with request

/**
a server used to test with `request` module
*/

var port = 4001;

require("http").createServer(function(request, response){

  function echo(){
    response.writeHead(200, {"Content-Type": "text/plain", "Cookie": "a=4"});
    response.end(JSON.stringify({// JSON is a built-in Object
      url: request.url,
      method: request.method,
      headers: request.headers
    }));
  }

  console.log(require('util').inspect(request.headers, { depth: null }));

  // dispatch url handlers
  switch (request.url) {
    case "/redirect":
      console.log("incoming[1]: /redirect");
      response.writeHead("301", {"Location": "/", "Cookie": "a=1"});
      response.end();
      break;

    case "/print/body":
      console.log("incoming[2]: /print/body");
      response.writeHead("200", {"Cookie": "a=2"});
      request.setEncoding("utf8");
      var body = "";
      request.on("data", function(data){
    body += data;
      });
      request.on("end", function(){
    response.end(JSON.stringify(body));
      });
      break;

    case "/images/peace.jpg":
      console.log("incoming[3]: "+request.url);
      response.writeHead("200", {"Cookie": "a=3"});
      require("fs").createReadStream("./images/peace.jpg").pipe(response);
      break;

    default:
      console.log("incoming[4]: "+request.url);
      echo();
      break;
  }

}).listen(port, function(){
  console.log("listening on: "+port);
});

request_simple.js: request module simple usage

/**
demonstration of `request` module simple usage
*/
var request = require("request");
var util = require("util");

//var url = "http://localhost:4001/abc/index.html";
var url = "http://localhost:4001/redirect";

/*
some shortcut method: get, post, put, del
*/
request(url, function(error, response, body){
  if(error) throw error;
  console.log(util.inspect({
    error: error,
    response: {
      statusCode: response.statusCode
    },
    body: JSON.parse(body)
  }));
});

request_options: request modules' options usage

/**
demonstration of `request` options usage
*/

var request = require("request");
var util = require("util");

var body = {
  a: 1,
  b: 2
}

var options = {
  url: "http://localhost:4001/print/body",
  method: "GET",
  headers: {
    "My-Header": "myHeaderValue"// customed header
  },
  //form:  body // form data usage or using:
  json: body // json wrapped request body
};

request(options, function(error, response, body){
  if(error) throw error;

  console.log(util.inspect({
    error: error,
    response: {
      statusCode: response.statusCode,
      headers: response.headers
    },
    body: JSON.parse(body)
  }, { depth: null }));
})

request_stream.js: request module with stream usage

/**
demonstration of stream transfer in `request` module
*/

var fs = require("fs");
var request = require("request");

// 1 download image
var writeStream = fs.createWriteStream("./images/download.jpg");
request.get("http://localhost:4001/images/peace.jpg").pipe(writeStream);

// 2 upload image from download
var source = request.get("http://localhost:4001/images/peace.jpg");
var target = request.post("http://localhost:4001/images/peace.jpg");
source.pipe(target);

request_cookie.js: request with cookie usage(tough-cookie implementation)

/**
deminstartion of `request`'s cookie usage,
REF: https://github.com/request/request, and https://www.npmjs.com/package/tough-cookie
*/
var request = require("request");
var util = require("util");

// 1 use cookie globally, default is false
//request = request.defaults({jar: true});

// 2 use cookie in all requests

var url = "http://localhost:4001/echo";
request(url, function(error, response, body){
  if(error) throw error;

  //get the cookie from HTTP server
  var j = request.jar();
  // var cookie = request.cookie('key1=value1');
  var cookie = response.headers.cookie;
  if(cookie === null){
    cookie="key1=value1";
  }
  j.setCookie(cookie, url);
  request = request.defaults({jar:j})

  request("http://localhost:4001/print/body");
});

3.8 使用UDP

UDP服务端/客户端对象通过require("dgram").createSocket("udp4")创建,服务端对象需要绑定bind(port[, host])

udp_server.js

/**
demonstration of a Echo UDP Server
*/

var dgram = require("dgram");
var serverSocket = dgram.createSocket("udp4");
var host = "127.0.0.1";
var port = 4002;

serverSocket.on("message", function(message, peerInfo){
  // get peer connection informations: host and port
  console.log("get message [%s] from peer: %s,%d", message, peerInfo.address, peerInfo.port);

  //echo
  serverSocket.send(message, 0, message.length, peerInfo.port, peerInfo.address);
});

// binding to port
serverSocket.bind(port, host);
// listen to 'listening' event
serverSocket.on("listening", function(){
  console.log("Listening to port: ", port);
});

udp_client.js

#!/usr/bin/node
/**
demonstration of a TCP client, with command line usage:
$ ./udp_client.js <host> <port>
*/
var dgram = require("dgram");

// read the command line arguments
var host = process.argv[2];//CAUTION real parameter start with index 2
var port = parseInt(process.argv[3], 10);

// create the client udp socket
var clientSocket = dgram.createSocket("udp4");

process.stdin.resume();//prepare to read the stdin
process.stdin.on("data", function(data){
  clientSocket.send(data, 0, data.length, port, host);
});

clientSocket.on("message", function(message){
  console.log("get message: %s", message.toString());
});

console.log("send data to %s:%d with entring something: ", host, port);

多播

书上说的模糊不清,这里参考了两个文档,基本上可以说明多播的使用:NodeJS UDP Multicast How toUDP/Datagram Sockets

udp_multicast_server.js

/**
demonstration of a UDP Server, providing multicast features
*/

var dgram = require("dgram");
var serverSocket = dgram.createSocket("udp4");
var multicastAddress = "230.185.192.108";
var port = 4002;
var destinationPort = 4003;

serverSocket.on("listening", function () {
  var address = serverSocket.address();
  console.log("server listening " + address.address + ":" + address.port);
});

// https://nodejs.org/api/dgram.html#dgram_udp_datagram_sockets
// always set to async since v0.10
serverSocket.bind(port, function(){
  serverSocket.setBroadcast(true);
  serverSocket.setMulticastTTL(128);
  serverSocket.addMembership(multicastAddress); // set multicast memberships
});

// ideas ref from http://*.com/questions/14130560/nodejs-udp-multicast-how-to
setInterval(broadcastNew, 3000);
function broadcastNew() {
    var message = new Buffer("Hello, " + Math.random()*10);
    serverSocket.send(message, 0, message.length,
      destinationPort,//destination port
      multicastAddress);
    console.log("Sent " + message.toString() + " to the wire...");
    //server.close();
}

udp_multicast_client.js

#!/usr/bin/node
/**
demonstration of a TCP client, with command line usage:
$ ./udp_client.js 127.0.0.1 4003
and providing multicast features
*/
var dgram = require("dgram");
var multicastAddress = "230.185.192.108";

// read the command line arguments
var host = process.argv[2];//CAUTION real parameter start with index 2
var port = parseInt(process.argv[3], 10);

// create the client udp socket
var clientSocket = dgram.createSocket("udp4");

clientSocket.on('listening', function () {
    var address = clientSocket.address();
    console.log('UDP Client listening on ' + address.address + ":" + address.port);
    clientSocket.setBroadcast(true)
    clientSocket.setMulticastTTL(128);
    clientSocket.addMembership(multicastAddress, host);//~
});

clientSocket.bind(port);

clientSocket.on("message", function(message){
  console.log("get message: %s", message.toString());
});

3.9 用TLS/SSL保证服务器的安全性

TODO

3.10 用HTTPS保证HTTP服务器的安全性

TODO