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);
});
响应:
一个静态文件访问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);
});
响应:
头部信息:
3.6 构建TCP客户端
TCP客户端对象connection
通过require("net").createConnection(<port>, [<host>])
创建,它也是一个事件发射器和可读可写流。发射事件包括:connect
、error
、data
、close
等。
一些方法:
// 关闭连接
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 to、UDP/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