NodeJS搭建HTTP服务器的实现步骤


Posted in NodeJs onOctober 12, 2018

前言

在 NodeJS 中用来创建服务的模块是 http 核心模块,本篇就来介绍关于使用 http 模块搭建 HTTP 服务器和客户端的方法,以及模块的基本 API。

HTTP 服务器

1、创建 HTTP 服务器

在 NodeJS 中,创建 HTTP 服务器可以与 net 模块创建 TCP 服务器对比,创建服务器有也两种方式。

方式 1:

const http = require("http");

const server = http.createServer(function(req, res) {
  // ......
});

server.listen(3000);

方式 2:

const http = require("http");

const server = http.createServer();

server.on("request", function(req, res) {
  // ......
});

server.listen(3000);

在 createServer 的回调和 request 事件的回调函数中有两个参数,req(请求)、res(响应),基于 socket,这两个对象都是 Duplex 类型的可读可写流。

http 模块是基于 net 模块实现的,所以 net 模块原有的事件在 http 中依然存在。

const http = require("http");

const server = http.createServer();

// net 模块事件
server.on("connection", function(socket) {
  console.log("连接成功");
});

server.listen(3000);

2、获取请求信息

在请求对象 req 中存在请求的方法、请求的 url(包含参数,即查询字符串)、当前的 HTTP 协议版本和请求头等信息。

const http = require("http");

const server = http.createServer();

server.on("request", function(req, res) {
  console.log(req.method); // 获取请求方法
  console.log(req.url); // 获取请求路径(包含查询字符串)
  console.log(req.httpVersion); // 获取 HTTP 协议版本
  console.log(req.headers); // 获取请求头(对象)

  // 获取请求体的内容
  let arr = [];

  req.on("data", function(data) {
    arr.push(data);
  });

  req.on("end", function() {
    console.log(Buffer.concat(arr).toString());
  });
});

server.listen(3000, function() {
  console.log("server start 3000");
});

通过 req 对应的属性可以拿到请求行和请求首部的信息,请求体内的内容通过流操作来获取,其中 url 中存在多个有用的参数,我们自己处理会很麻烦,可以通过 NodeJS 的核心模块 url 进行解析。

const url = require("url");
let str = "http://user:pass@www.pandashen.com:8080/src/index.html?a=1&b=2#hash";

// parse 方法帮助我们解析 url 路径
let obj = url.parse(str, true);

console.log(obj);

// {
//   protocol: 'http:',
//   slashes: true,
//   auth: 'user:pas',
//   host: 'www.pandashen.com:8080',
//   port: '8080',
//   hostname: 'www.pandashen.com',
//   hash: '#hash',
//   search: '?a=1&b=2',
//   query: '{ a: '1', b: '2' }',
//   pathname: '/src/index.html'
//   path: '/src/index.html?a=1&b=2',
//   href: 'http://user:pass@www.pandashen.com:8080/src/index.html?a=1&b=2#hash' }

在被解析路径返回的对象中有几个属性被经常使用:

  • host:主机(域名 + 端口号);
  • hostname:主机名;
  • query:请求参数(查询字符串或参数对象);
  • pathname:资源路径(根据不同的路径返回不同的资源)。

我们使用 url 的 parse 方法来帮我们解析请求路径,在真实的服务器中传入的第一个参数为 req.url,第二个参数不传时,query 会被解析成 a=1&b=2 的形式,第二个参数传入 true,query 属性的查询字符串会被解析成对象的形式。

url 模块中,将查询字符串 a=1&b=2 转换为对象 { a: '1', b: '2' } 的实现方式其实是使用正则替换实现的。

模拟查询字符串转换对象的核心逻辑:

let str = "a=1&b=2&c=3";
let obj = {};

str.replace(/([^=&]+)=([^=&]+)/g, function() {
  obj[arguments[1]] = arguments[2];
});

console.log(obj); // { a: '1', b: '2', c: '3' }

在上面代码的 replace 方法的回调函数中参数集合的第一项为匹配到的字符串,第二项为第一个分组的值,第三项为第二个分组的值,依次类推,倒数第二项为分组匹配的索引,最后一项为原字符串。

3、设置响应信息

我们可以通过 req 来获取请求信息,自然也可以通过 res 来设置响应信息返回给客户端。

const http = require("http");

const server = http.createServer();

server.on("request", function(req, res) {
  // 设置响应头(过去的用法),不能多次调用,见到要认识
  res.writeHead(200, { "Content-Type": "text", a: "hello world" });

  // 设置响应头(现在的用法,常用),可以多次调用,每次设置一个响应头
  res.setHeader("Content-Type", "text");

  // 设置状态码,不设置默认为 200
  res.statusCode = 200;

  // 不发送 Date(日期)响应头
  res.sendDate = false;

  // 返回内容
  res.write("hello world"); // 不会关闭连接
  res.end("hello world"); // 将内容返回后关闭连接
});

server.listen(3000, function() {
  console.log("server start 3000");
});

返回给客户端的信息主要分为两部分,分别为响应头和返回给浏览器的内容,在不设置响应头的情况下,默认会设置响应头 Content-Length 和 Date ,代表当前返回给客户端的内容长度和日期。

返回给浏览器的内容可以通过 res 的 write 方法和 end 方法进行发送,write 方法不会断开连接(通常在响应后需要断开与客户端的连接),end 方法会断开连接,在 end 方法存在参数时,会在内部调用 write 将参数内容返回给客户端,并断开连接。

HTTP 客户端

在 net 模块中可以通过 net.createConnection 来创建客户端,并发送请求到服务端,在 http 模块同样可以创建客户端,并向 http 服务器发送请求。

// 客户端:client.js
const http = require("http");

// 发送请求的配置
let config = {
  host: "localhost",
  port: 3000,
  method: "get",
  headers: {
    a: 1
  }
};

// 创建客户端
let client = http.request(config, function(res) {
  // 接收服务端返回的数据
  let arr = [];

  res.on("data", function(data) {
    arr.push(data);
  });

  res.on("end", function() {
    console.log(Buffer.concat(arr).toString());
  });
});

// 发送请求
client.end();

在 http 模块中通过 request 方法创建客户端,该方法第一个参数为发送请求的配置,包含请求地址、端口号、请求方法以及请求头等,第二个参数为回调函数,在请求被响应后执行,回调函数的参数为服务器的响应对象 res,创建的客户端通过 end 方法将请求发出与服务端进行通信。

使用 NodeJS 实现的 “爬虫” 其实就可以通过 http 模块创建的客户端来实现,客户端帮我们向我们要抓取数据的地址发送请求,并拿到响应的数据进行解析。

同时使用 HTTP 客户端和服务器

我们使用自己创建的客户端访问自己的服务端,并体会请求响应的过程,就是用上面 client.js 作为客户端,启动 server.js 后再启动 client.js 查看效果。

// 服务器:server.js
const http = require("http");

http.createServer(function(req, res) {
  console.log("The request came");

  // 获取客户端请求信息
  console.log(req.method);
  console.log(req.headers);

  // 返回数据
  res.write("hello world");
}).listen(3000, function() {
  console.log("server start 3000");
});

简易爬虫

我们结合 http 模块创建的服务端和客户端实现一个简易版的 “爬虫” 去抓取百度新闻页所有 li 标签内的文章标题。

// 简易爬虫:crawl.js
const http = require("http");

// 创建服务器
const server = http.createServer();

// 监听请求
server.on("request", function(req, res) {
  let client = http.request(
    {
      host: "news.baidu.com",
      method: "get",
      port: 80
    },
    function(r) {
      // 接收百度新闻返回的数据
      let arr = [];

      r.on("data", function(data) {
        arr.push(data);
      });

      r.on("end", function() {
        // 处理数据
        let result = Buffer.concat(arr).toString();
        let matches = result.match(/<li class="bold-item">([\s\S*?])<\/li>/gm);

        // 设置返回给浏览器的文档类型和编码格式
        res.setHeader("Content-Type", "text/html;charset=utf8");

        // 响应浏览器
        res.end(matches.join(""));
      });
    }
  );

  client.end();
});

server.listen(3000);

上面的正则匹配中 ([\s\S*?]) 代表匹配 <li class="bold-item"> 到 <\/li> 之间所有内容(多个字符、非贪婪模式),gm 代表全局并多行匹配。

上面爬取百度新闻数据的过程中,我们自己的 Node 服务器扮演了一个 “中间层” 的角色,我们通过浏览器访问自己的服务器 localhost:3000 触发 request 事件,执行了回调,在回调中创建客户端向 news.baidu.com 发送了请求,并在客户端的回调中处理了响应(百度新闻页返回的数据),将处理后的内容通过我们自己 Node 服务器的 res 对象返回给了浏览器。

总结

相信在读过本篇文章之后对搭建一个 Node 服务应该已经有了思路,为未来通过 Node 服务实现复杂的业务场景及数据的处理打下了一个基础,希望初学 Node 的小伙伴在看了这篇文章后能有所收获。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
NodeJS学习笔记之FS文件模块
Jan 13 NodeJs
Nodejs学习item【入门手上】
May 05 NodeJs
nodejs搭建本地http服务器教程
Mar 13 NodeJs
理解nodejs的stream和pipe机制的原理和实现
Aug 12 NodeJs
详解nodeJs文件系统(fs)与流(stream)
Jan 24 NodeJs
nodejs简单读写excel内容的方法示例
Mar 16 NodeJs
解决Nodejs全局安装模块后找不到命令的问题
May 15 NodeJs
Nodejs Express 通过log4js写日志到Logstash(ELK)
Aug 30 NodeJs
基于nodejs的雪碧图制作工具的示例代码
Nov 05 NodeJs
typescript nodejs 依赖注入实现方法代码详解
Jul 21 NodeJs
详解利用nodejs对本地json文件进行增删改查
Sep 20 NodeJs
nodejs实现百度舆情接口应用示例
Feb 07 NodeJs
NodeJS服务器实现gzip压缩的示例代码
Oct 12 #NodeJs
nodejs aes 加解密实例
Oct 10 #NodeJs
nodejs读取本地中文json文件出现乱码解决方法
Oct 10 #NodeJs
nodejs require js文件入口,在package.json中指定默认入口main方法
Oct 10 #NodeJs
nodejs更新package.json中的dependencies依赖到最新版本的方法
Oct 10 #NodeJs
nodejs中用npm初始化来创建package.json的实例讲解
Oct 10 #NodeJs
nodejs初始化init的示例代码
Oct 10 #NodeJs
You might like
PHP中判断变量为空的几种方法分享
2013/08/26 PHP
php实现推荐功能的简单实例
2019/09/29 PHP
javascript对象的使用和属性操作示例详解
2014/03/02 Javascript
node.js中的emitter.on方法使用说明
2014/12/10 Javascript
向JavaScript的数组中添加元素的方法小结
2015/10/24 Javascript
jquery实现图片放大镜功能
2015/11/23 Javascript
js实现新年倒计时效果
2015/12/10 Javascript
JS实现颜色梯度与渐变效果完整实例
2016/12/30 Javascript
老生常谈js-react组件生命周期
2017/05/02 Javascript
AngularJS select设置默认值的实现方法
2017/08/25 Javascript
Vue2.0 实现单选互斥的方法
2018/04/13 Javascript
详解VUE自定义组件中用.sync修饰符与v-model的区别
2018/06/26 Javascript
vue利用v-for嵌套输出多层对象,分别输出到个表的方法
2018/09/07 Javascript
Vue2.5学习笔记之如何在项目中使用和配置Vue
2018/09/26 Javascript
layui 选择列表,打勾,点击确定返回数据的例子
2019/09/02 Javascript
使用pkg打包ThinkJS项目的方法步骤
2019/12/30 Javascript
React中Ref 的使用方法详解
2020/04/28 Javascript
[45:10]NB vs Liquid Supermajor小组赛 A组胜者组决赛 BO3 第二场 6.2
2018/06/04 DOTA
python以环状形式组合排列图片并输出的方法
2015/03/17 Python
Python常用的文件及文件路径、目录操作方法汇总介绍
2015/05/21 Python
在阿里云服务器上配置CentOS+Nginx+Python+Flask环境
2016/06/18 Python
Python函数式编程
2017/07/20 Python
华为校园招聘上机笔试题 扑克牌大小(python)
2020/04/22 Python
python爬虫学习笔记之pyquery模块基本用法详解
2020/04/09 Python
如何利用python生成MD5并去重
2020/12/07 Python
python实现代码审查自动回复消息
2021/02/01 Python
Python使用paramiko连接远程服务器执行Shell命令的实现
2021/03/04 Python
html5 CSS过度-webkit-transition使用介绍
2013/07/02 HTML / CSS
行政文员岗位职责
2013/11/08 职场文书
写给学生的新学期寄语
2014/01/18 职场文书
个人租房协议书样本
2014/10/01 职场文书
初中数学教学随笔
2015/08/15 职场文书
2016春季幼儿园大班开学寄语
2015/12/03 职场文书
详解Html5项目适配系统深色模式方案总结
2021/04/14 HTML / CSS
浅谈JS的原型和原型链
2021/06/04 Javascript
python和anaconda的区别
2022/05/06 Python