http://blog.csdn.net/ligang2585116/article/details/72827781
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。其属于下图七层网路协议的“应用层”。
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();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事件。
请求对象的属性 | 说明 | method请求的方法Get、Post、Put、Deleteurl客户端发送请求时使用的URL参数字符串;通常用来判断请求页面headers请求头对象httpVersionHTTP1.0或者HTTP1.1trailers客户端发送的trailers对象socket服务器用于监听客户端请求的socket对象Get请求
server.on('request', (request, response) => { if(request.url !== '/favicon.ico') { var params = url.parse(req.url, true).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'}));});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
转换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); let obj = querystring.parse(str);console.log(obj);
url模块:转换完整URL字符串
url.parse(urlStr, [parseQueryString])
- parseQueryString:如果为true,将查询字符通过querystring转换为对象;默认false。
url.resolve(from, to);
将二者结合成一个路径,from、to既可以是相对路径也可以是绝对路径。
url.resolve('http://ligangblog.com/javascript/', 'a?a=1'); url.resolve('http://ligangblog.com/javascript/', '/a?a=1');
注意:具体合并规则,请查看《Node权威指南》— 8.1HTTP服务器。
属性 | 含义 | href原URL字符串protocol协议slashes在协议与路径中间是否使用“//”分隔符hostURL完整地址及端口号,可能是一个IP地址hostnameURL完整地址,可能是一个IP地址port端口号pathURL字符串中的路径,包含查询字符串pathnameURL字符串中的路径,不包含查询字符串search查询字符串,包含起始字符“?”query查询字符串,不包含起始字符“?”hashhash值,包含起始字符“#”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' }*/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
发送服务器端响应流
response.writeHead(statusCode, [reasonPhrase], [headers]);response.setHeader(name, value);
响应头中包含的一些常用字段:
字段 | 说明 | content-type用于指定内容类型location用于将客户端重定向到另一个URL地址content-disposition用于指定一个被下载的文件名content-length用于指定服务器端响应内容的字节数set-cookie用于在客户端创建一个cookiecontent-encoding用于指定服务器端响应内容的编码方式Cache-Control用于开启缓存机制Expires用于指定缓存过期时间Tag用于指定当服务器响应内容没有变换时不重新下载数据示例:
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);
示例:
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()
来结束响应。
响应超时会触发timeout
事件;response.end()
方法调用之前,如果连接中断,会触发close
事件。
response.setTimeout(2 * 60 * 1000, () => { console.error('请求超时!'); });response.setTimout(2 * 60 * 1000);response.on('timeout', () => { console.error('请求超时!');});response.on('close', () => { console.error('连接中断!');});
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}`); }});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
HTTP客户端
Node.js可以轻松向任何网站发送请求并读取网站的响应数据。
var req = http.request(options, callback);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会自动调用。
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}`); }});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
代理服务器
import http from 'http';import url from 'url';const server = http.createServer(async (req, res) => { 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); 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); });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;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
注意:
- POST请求必须指定headers信息,否则会报错
socket hang up
- 获取到options后需要重新指定其method
options.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服务器时配置的各种选项,下面只描述几个必要选项:
属性名 | 说明 | pff用于指定从pfx文件读取出的私钥、公钥以及证书(指定该属性后,无需再指定key、cert、ca)key用于指定后缀名为pem的文件,读出私钥cert用于指定后缀名为pem的文件,读出公钥ca用于指定一组证书,默认值为几个著名的证书授证中心HTTPS客户端
const options = { hostname: 'localhost', port: 1443, path: '/', method: 'post', key: fs.readFileSync('privatekey.pem'), cert: fs.readFileSync('certificate.pem'), rejectUnhauthorized: false, agent: false }, 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, };options.agent = new https.Agent(options);var req = https.request(options);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
说明: 普通的 HTTPS 服务中,服务端不验证客户端的证书(但是需要携带证书),中间人可以作为客户端与服务端成功完成 TLS 握手; 但是中间人没有证书私钥,无论如何也无法伪造成服务端跟客户端建立 TLS 连接。 当然如果你拥有证书私钥,代理证书对应的 HTTPS 网站当然就没问题了,所以这里的私钥和公钥只是格式书写,没有太大意义,只要将请求回来的数据原原本本交给浏览器来解析就算完成任务。
关于代理的两篇文章:HTTP 代理原理及实现(一)、HTTP 代理原理及实现(二)