百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程文章 > 正文

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

qiyuwang 2024-10-31 15:51 17 浏览 0 评论

超文本传输协议(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();
// 服务器关闭时会触发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事件。



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服务器。




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);

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



示例:


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()来结束响应。

响应超时会触发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服务器时配置的各种选项,下面只描述几个必要选项:




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 网站当然就没问题了,所以这里的私钥和公钥只是格式书写,没有太大意义,只要将请求回来的数据原原本本交给浏览器来解析就算完成任务。

相关推荐

第二十六章 Bat文件(bat文件百科)

第二十六章Bat文件bat文件是dos下的批处理文件。批处理文件是无格式的文本文件,它包含一条或多条命令。它的文件扩展名为.bat或.cmd。在命令提示下输入批处理文件的名称,或者双击该批处理...

自媒体良器:音频文件批处理,FFmpeg一行搞定!果断收藏

引言FFMpeg是众多多媒体应用程序的核心,但该程序本身不具备一次转换多个文件的能力。但也拦不住,FFMpeg本身是命令行程序,可编写脚本,借助Bash轻松快速地构建自动化程序。...

批处理:创建和删除文件夹命令(批处理命令 删除文件夹)

上一期,我们学习了文件夹的查看命令:tree和dir,以及文件夹切换命令:cd,今天,我继续给大家讲解下批处理的目录(文件夹)操作命令:创建和删除。一.md:创建目录(文件夹)可以在当前文件夹下创建...

利用VBA创建新的工作簿(vba 创建工作簿)

【分享成果,随喜正能量】我告诫自己:你的话说得太多,你听别人倾诉得太多,你咖啡喝得太多,你在陌生的房间里坐的时间太长,你的睡眠质量太差,你醒着的时间太长,你平庸的事想得太多,你希望过多,你安慰自己太频...

实用小工具,注册表文件快速转换.bat文件

关于注册表注册表是Windows操作系统中的一个核心数据库,它用于存储系统和应用程序的设置信息。这个数据库文件控制着Windows操作系统的外观和如何响应外来事件的工作方式。注册表包含了各种参数,直接...

如何给自己的电脑做一个截图工具?现场教会你具体流程和步骤

做自媒体,经常要用到一些图片,有时候图片上有水印去不掉,这时候要是有一个能随意截图的工具该有多少呀!今天教授让大家自己设计一个截图工具。下面就是流程和步骤:1、在电脑页面的任意位置,点击鼠标右键,新建...

Windows使用批处理文件更改目录下的文件名(含序号)

从网上下载的图片,默认的名字是由许多字母和数字组成的比较长的一个字符串,不便于人记忆,使用起来比较麻烦。如下图所示:本文的目的是写一个批处理文件,将该目录中的所有扩展名为jpeg的文件改名为日期加序号...

学习VB编程第35天,如何调用bat批处理

今天学习了刘金玉老师零基础VB教程的第43期,学习的主要内容是如何调用bat批处理登录。一、什么是批处理1.什么是命令提示符(cmd)?在Windows开始菜单运行(windows+r键也可调出)中输...

利用bat让文件在指定时间自动进行备份

在很多时候,我们需要备份电脑中的一些文件,特别是企业服务器的备份,可能是有一些文件每天都会有更新,或者定期更新,而文件又是相对比较重要的,需要我们定时去备份。可有时候文件比较大,备份时间比较长,或者在...

BAT 批处理脚本教程(批处理脚本编写教程)

BAT批处理脚本教程第一章批处理基础第一节常用批处理内部命令简介批处理定义:顾名思义,批处理文件是将一系列命令按一定的顺序集合为一个可执行的文本文件,其扩展名为BAT或者CMD。这些命令统称批处...

Windows系统bat批处理常用命令(一)

一、批处理中常用的命令:@命令:加在每个命令行的最前面,表示运行时不显示这一行的命令行。eg:@echooff//不显示后续命令行及当前命令行...

编写bat文件在windows上自动执行cmd命令

windows电脑上直接新建txt记事本文件,编写好要执行的命令后,将后缀修改成.bat,然后直接点击文件就可以执行里面的命令了#打开命令窗口startcmd#/k执行完命令不关...

不会C语言写bat脚本代码批量修改文件名,DeepSeek帮你解决!

哈喽大家好,我是小飞。今天给大家讲一下DeepSeek做了一个批量修改文件夹的bat脚本。·首先先让它这样修改一下,因为之前已经做过一个,然后它没有改,现在让它修改一下,把它修改一个很智能的BAT脚本...

BAT批处理命令之ren/rename(与Excel合作批量修改文件名)

写在前面一鸽就是半年...

使用批处理文件——autoexec.bat(批处理more)

1、dblspace.sys磁盘压缩。  drvspace.sys、dblspace和drvspace是Microsoft公司推出的磁盘压缩工具,可以将磁盘的空间增加许多,但由于它依靠对文件进行压...

取消回复欢迎 发表评论: