博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
NodeJS创建HTTP、HTTPS服务器与客户端
阅读量:4117 次
发布时间:2019-05-25

本文共 11172 字,大约阅读时间需要 37 分钟。

640?wx_fmt=png

来源 | https://blog.csdn.net/ligang2585116/article/details/72827781

超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。其属于下图七层网路协议的“应用层”。

640?wx_fmt=png

HTTP服务器

创建HTTP服务器

创建服务

方式一:回调方式

var server = http.createServer((request, response) => {	    // 接受客户端请求时触发	    ...	});	server.listen(10000, 'localhost', 511, () => {	   // 开始监听	   ...	});

方式二:事件监听方式

var server = http.createServer();	// 接受客户端请求时触发	server.on('request', (request, rsponse) => {	    ...	});	server.listen(10000, 'localhost', 511);	// 开始监听	server.on('listening', () => {	    ...	});

注意:

  • server.listen(port, [host], [backlog], [callback])中的backlog参数为整数,指定位于等待队列中客户端连接的最大数量,一旦超过这个长度,HTTP服务器将开始拒绝来自新客户端的连接,默认值为511。

  • 在HTTP请求服务器时,会发送两次请求。一次是用户发出请求,另一次是浏览器为页面在收藏夹中的显示图标(默认为favicon.ico)而自动发出的请求。

关闭服务器

server.close();	// 服务器关闭时会触发close事件	server.on('close', () => {...});

超时

server.setTimeout(60 * 1000, () => {	   console.log('超时了');	});	// 或者通过事件形式	server.setTimeout(60 * 1000);	server.on('timeout', () => {...});

注意:默认超时时间为2分钟

错误

server.on('error', (e) => {	    if(e.code === 'EADDRINUSE') {	        // 端口被占用	    }	});

获取客户端请求信息

当从客户端请求流中读取到数据时会触发data事件,当读取完客户端请求流中的数据时触发end事件。

640?wx_fmt=png

Get请求

server.on('request', (request, response) => {	    if(request.url !== '/favicon.ico') {	        /* Get请求 */	        var params = url.parse(req.url, true).query;	        // 或者	        // var params = querystring.parse(url.parse(request.url).query);	        // 根据参数做处理	        // ...	        // 结束请求	        response.end();	    }  	});

Post请求

server.on('request', (request, response) => {	    request.setEncoding('utf-8');	    if(request.url !== '/favicon.ico') {	        let result = '';	        request.on('data', (data) => {	            result += data;	        });	        request.on('end', () => {	            var params = JSON.parse(postData);	            console.log(`数据接收完毕:${result}`);	        });	        // 结束本次请求	        response.end();	    }	    // 结束本次请求	    response.end(JSON.stringify({status: 'success'}));	});

转换URL字符串与查询字符串

querystring模块:转换URL中的查询字符串(URL中?之后,#之前)

querystring.stringify(obj, [sep], [eq])	querystring.parse(str, [sep], [eq], [option])

sep:分割符,默认&

eq:分配字符,默认=

options:{maxKeys: number}指定转换后对象中的属性个数	let str = querystring.stringify({name: 'ligang', age: 27});	console.log(str); // name=ligang&age=27	let obj = querystring.parse(str);	console.log(obj); // { name: 'ligang', age: '27' }

url模块:转换完整URL字符串

url.parse(urlStr, [parseQueryString])

parseQueryString:如果为true,将查询字符通过querystring转换为对象;默认false。

url.resolve(from, to);

将二者结合成一个路径,from、to既可以是相对路径也可以是绝对路径。

// http://ligangblog.com/javascript/a?a=1	url.resolve('http://ligangblog.com/javascript/', 'a?a=1'); 	// http://ligangblog.com/a?a=1	url.resolve('http://ligangblog.com/javascript/', '/a?a=1');

注意:具体合并规则,请查看《Node权威指南》— 8.1HTTP服务器。

640?wx_fmt=png

var urlStr = 'http://ligangblog.com/javascript/?name=lg&uid=1#a/b/c';	console.log(url.parse(urlStr, true));	/*	Url {	    protocol: 'http:',	    slashes: true,	    auth: null,	    host: 'ligangblog.com',	    port: null,	    hostname: 'ligangblog.com',	    hash: '#a/b/c',	    search: '?name=lg&uid=1',	    query: { name: 'lg', uid: '1' },	    pathname: '/javascript/',	    path: '/javascript/?name=lg&uid=1',	    href: 'http://ligangblog.com/javascript/?name=lg&uid=1#a/b/c' 	}	*/

发送服务器端响应流

response.writeHead(statusCode, [reasonPhrase], [headers]);	// 或者	response.setHeader(name, value);

响应头中包含的一些常用字段:

640?wx_fmt=png

示例:

response.writeHead(200, {'Content-Type': 'text/plain', 	                         'Access-Control-Allow-Origin': 'http://localhost'});	// 或者	response.statusCode = 200;	response.setHeader('Content-Type', 'text/plain');	response.setHeader('Access-Control-Allow-Origin', 'http://localhost');

writeHead和setHeader区别:

writeHead:该方法被调用时发送响应头

setHeader:write方法第一次被调用时发送响应头

/* 获取响应头中的某个字段值 */	response.getHeader(name);	/* 删除一个响应字段值 */	response.removeHeader(name);	/* 该属性表示响应头是否已发送 */	response.headersSent;	/* 在响应数据的尾部增加一个头信息 */	response.addTrailers(headers);

示例:

// 必须再响应头中添加Trailer字段,并且其值设置为追加的响应头中所指定的字段名	response.write(200, {'Content-Type': 'text/plain', 'Trailer': 'Content-MD5'});	response.write('....');	response.addTrailers({'Content-MD5', '...'});	response.end();

特别说明:

当再快速网路且数据量很小的情况下,Node将数据直接发送到操作系统内核缓存区中,然后从该内核缓存区中取出数据发送给请求方;如果网速很慢或者数据量很大,Node通常会将数据缓存在内存中,在对方可以接受数据的情况下将内存中的数据通过操作系统内核缓存区发送给请求方。

response.write返回true,说明直接写到了操作系统内核缓存区中;返回false,说明暂时缓存的内存中。每次需要通过调用response.end()来结束响应。

640?wx_fmt=png

响应超时会触发timeout事件;response.end()方法调用之前,如果连接中断,会触发close事件。

/* 设置请求超时时间2分钟 */	response.setTimeout(2 * 60 * 1000, () => {	  console.error('请求超时!'); 	});	// 或者	response.setTimout(2 * 60 * 1000);	response.on('timeout', () => {	  console.error('请求超时!');	});	/* 连接中断 */	response.on('close', () => {	  console.error('连接中断!');	});

/**	 * HTTP服务端	 * Created by ligang on 17/5/28.	 */	import http from 'http';	var server = http.createServer();	// 接受客户端请求时触发	server.on('request', (request, response) => {	    if(request.url !== '/favicon.ico') {	        response.setTimeout(2 * 60 * 1000, () => {	           console.error('请求超时!');	        });	        response.on('close', () => {	            console.error('请求中断!');	        });	        let result = '';	        request.on('data', (data) => {	            result += data;	        });	        request.on('end', () => {	            console.log(`服务器数据接收完毕:${result}`);	            response.statusCode = 200;	            response.write('收到!');	            response.end(); // 结束本次请求	        });	    }	});	server.listen(10000, 'localhost', 511);	// 开始监听	server.on('listening', () => {	    console.log('开始监听');	});	server.on('error', (e) => {	    if(e.code === 'EADDRINUSE') {	        console.log('端口被占用');	    }else {	        console.log(`发生错误:${e.code}`);	    }	});

HTTP客户端

Node.js可以轻松向任何网站发送请求并读取网站的响应数据。

var req = http.request(options, callback);	// get请求	var req = http.get(options, callback);	// 向目标网站发送数据	req.write(chunk, [encoding]);	// 结束本次请求	req.end([chucnk], [encoding]);	// 中止本次请求	req.abort();

其中,options用于指定目标URL地址,如果该参数是一个字符串,将自动使用url模块中的parse方法转换为一个对象。注意:http.get()方法只能使用Get方式请求数据,且无需调用req.end()方法,Node.js会自动调用。

/**	 * HTTP客户端	 * Created by ligang on 17/5/30.	 */	import http from 'http';	const options = {	        hostname: 'localhost',	        port: 10000,	        path: '/',	        method: 'post'	    },	    req = http.request(options);	req.write('你好,服务器');	req.end();	req.on('response', (res) => {	    console.log(`状态码:${res.statusCode}`);	    let result = '';	    res.on('data', (data) => {	        result += data;	    });	    res.on('end', () => {	        console.log(`客户端接受到响应:${result}`);	    })	});	req.setTimeout(60* 1000, () => {	    console.log('超时了');	    req.abort();	});	req.on('error', (error) => {	    if(error.code === 'ECONNERSET') {	        console.log('socket端口超时');	    }else {	        console.log(`发送错误:${error.code}`);	    }	});

代理服务器

/**	 * HTTP代理	 * Created by ligang on 17/5/30.	 */	import http from 'http';	import url from 'url';	/**	 * 服务端	 */	const server = http.createServer(async (req, res) => {	    // req.setEncoding('utf-8');	    /* 超时 2分钟 */	    res.setTimeout(2 * 60 * 1000, () => {	        // ...	    });	    /* 连接中断 */	    res.on('close', () => {	        // ...	    });	    let options = {},	        result = "";	    options = await new Promise((resolve, reject) => {	        if(req.method === 'GET') {	            options = url.parse('http://localhost:10000' + req.url);	            resolve(options);	        }else if(req.method === 'POST') {	            req.on('data', (data) => {	                result += data;	            });	            req.on('end', () => {	                options = url.parse('http://localhost:10000' + req.url);	                // post请求必须制定	                options.headers = {	                    'content-type': 'application/json',	                };	                resolve(options);	            });	        }	    });	    options.method = req.method;	    let content = await clientHttp(options, result ? JSON.parse(result) : result);	    res.setHeader('Content-Type', 'text/html');	    res.write('<html><head><meta charset="UTF-8" /></head>')	    res.write(content);	    res.write('</html>');	    // 结束本次请求	    res.end();	});	server.listen(10010, 'localhost', 511);	/* 开始监听 */	server.on('listening', () => {	    // ...	});	/* 监听错误 */	server.on('error', (e) => {	    console.log(e.code);	    // ...	});	/**	 * 客户端	 * @param options 请求参数	 * @param data 请求数据	 */	async function clientHttp(options, data) {	    let output = new Promise((resolve, reject) => {	        let req = http.request(options, (res) => {	            let result = '';	            res.setEncoding('utf8');	            res.on('data', function (chunk) {	                result += chunk;	            });	            res.on('end', function () {	                resolve(result);	            });	        });	        req.setTimeout(60000, () => {	            console.error(`连接后台超时 ${options.href}`);	            reject();	            req.abort();	        });	        req.on('error', err => {	            console.error(`连接后台报错 ${err}`);	            if (err.code === 'ECONNRESET') {	                console.error(`socket超时 ${options.href}`);	            } else {	                console.error(`连接后台报错 ${err}`);	            }	            reject();	            req.abort();	        });	        // 存在请求数据,发送请求数据	        if (data) {	            req.write(JSON.stringify(data));	        }	        req.end();	    });	    return await output;	}

注意:

POST请求必须指定headers信息,否则会报错socket hang up

获取到options后需要重新指定其methodoptions.method = req.method;

HTTPS服务器

  • HTTPS使用https协议,默认端口号44;

  • HTTPS需要向证书授证中心申请证书;

  • HTTPS服务器与客户端之间传输是经过SSL安全加密后的密文数据;

创建公钥、私钥及证书

(1)创建私钥

openssl genrsa -out privatekey.pem 1024

(2)创建证书签名请求

openssl req -new -key privatekey.pem -out certrequest.csr

(3)获取证书,线上证书需要经过证书授证中心签名的文件;下面只创建一个学习使用证书

openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem

(4)创建pfx文件

openssl pkcs12 -export -in certificate.pem -inkey privatekey.pem -out certificate.pfx

HTTPS服务

创建HTTPS服务器同HTTP服务器大致相同,需要增加证书,创建HTTPS服务器时通过options参数设置。

import https from 'https';	import fs from 'fs';	var pk = fs.readFileSync('privatekey.pem'),	    pc = fs.readFileSync('certificate.pem');	var opts = {	    key: pk,	    cert: pc	};	var server = https.createServer(opts);

opts参数为一个对象,用于指定创建HTTPS服务器时配置的各种选项,下面只描述几个必要选项:

640?wx_fmt=png

HTTPS客户端

const options = {	        hostname: 'localhost',	        port: 1443,	        path: '/',	        method: 'post',	        key: fs.readFileSync('privatekey.pem'),	        cert: fs.readFileSync('certificate.pem'),	        rejectUnhauthorized: false,	        agent: false // 从连接池中指定挑选一个当前连接状态为关闭的https.Agent	    },	    req = https.request(options);	// 或者	const options = {	        hostname: 'localhost',	        port: 1443,	        path: '/',	        method: 'post',	        key: fs.readFileSync('privatekey.pem'),	        cert: fs.readFileSync('certificate.pem'),	        rejectUnhauthorized: false,	    };	// 显示指定https.Agent对象	options.agent = new https.Agent(options);	var req = https.request(options);

说明: 普通的 HTTPS 服务中,服务端不验证客户端的证书(但是需要携带证书),中间人可以作为客户端与服务端成功完成 TLS 握手; 但是中间人没有证书私钥,无论如何也无法伪造成服务端跟客户端建立 TLS 连接。

当然如果你拥有证书私钥,代理证书对应的 HTTPS 网站当然就没问题了,所以这里的私钥和公钥只是格式书写,没有太大意义,只要将请求回来的数据原原本本交给浏览器来解析就算完成任务。 

640?wx_fmt=jpeg

640?wx_fmt=jpeg

你可能感兴趣的文章
day-22 mysql_SQL 结构化查询语言
查看>>
MySQL关键字的些许问题
查看>>
浅谈HTML
查看>>
css基础
查看>>
HTML&CSS进阶
查看>>
Servlet进阶和JSP基础
查看>>
servlet中的cookie和session
查看>>
过滤器及JSP九大隐式对象
查看>>
软件(项目)的分层
查看>>
菜单树
查看>>
MySQL-分布式架构-MyCAT
查看>>
设计模式六大原则(6):开闭原则
查看>>
阿里面试总结--JAVA
查看>>
Servlet的生命周期
查看>>
JAVA八大经典书籍,你看过几本?
查看>>
《读书笔记》—–书单推荐
查看>>
【设计模式】—-(2)工厂方法模式(创建型)
查看>>
有return的情况下try catch finally的执行顺序(最有说服力的总结)
查看>>
String s1 = new String("abc"); String s2 = ("abc");
查看>>
JAVA数据类型
查看>>