热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

NodeJS创建HTTP、HTTPS服务器与客户端

来源|https:blog.csdn.netligang2585116articledetails72827781超文本传输协议(HTTP,HyperT

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('') res.write(content); res.write(''); // 结束本次请求 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


推荐阅读
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的步骤和方法
    本文介绍了在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的详细步骤和方法。首先需要下载最新的Java SE Development Kit 9发行版,然后按照给出的Shell命令行方式进行安装。详细的步骤和方法请参考正文内容。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
  • springboot项目引入jquery浏览器报404错误的解决办法
    本文介绍了在springboot项目中引入jquery时,可能会出现浏览器报404错误的问题,并提供了解决办法。问题可能是由于将jquery.js文件复制粘贴到错误的目录导致的,解决办法是将文件复制粘贴到正确的目录下。如果问题仍然存在,可能是其他原因导致的。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 如何在php文件中添加图片?
    本文详细解答了如何在php文件中添加图片的问题,包括插入图片的代码、使用PHPword在载入模板中插入图片的方法,以及使用gd库生成不同类型的图像文件的示例。同时还介绍了如何生成一个正方形文件的步骤。希望对大家有所帮助。 ... [详细]
  • Centos7搭建ELK(Elasticsearch、Logstash、Kibana)教程及注意事项
    本文介绍了在Centos7上搭建ELK(Elasticsearch、Logstash、Kibana)的详细步骤,包括下载安装包、安装Elasticsearch、创建用户、修改配置文件等。同时提供了使用华为镜像站下载安装包的方法,并强调了保证版本一致的重要性。 ... [详细]
author-avatar
在海那边A-P
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有