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实现PHP的print_r函数代码
Mar 14 NodeJs
Nodejs sublime text 3安装与配置
Jun 19 NodeJs
详谈nodejs异步编程
Dec 04 NodeJs
详解nodejs 文本操作模块-fs模块(四)
Dec 22 NodeJs
nodejs和php实现图片访问实时处理
Jan 05 NodeJs
Express与NodeJs创建服务器的两种方法
Feb 06 NodeJs
NodeJS学习笔记之Module的简介
Mar 24 NodeJs
mac下的nodejs环境安装的步骤
May 24 NodeJs
nodejs使用express获取get和post传值及session验证的方法
Nov 09 NodeJs
Nodejs连接mysql并实现增、删、改、查操作的方法详解
Jan 04 NodeJs
nodejs二进制与Buffer的介绍与使用
Jul 11 NodeJs
nodeJS与MySQL实现分页数据以及倒序数据
Jun 05 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
第1次亲密接触PHP5(1)
2006/10/09 PHP
如何写php程序?
2006/12/08 PHP
Thinkphp的volist标签嵌套循环使用教程
2014/07/08 PHP
Laravel使用memcached缓存对文章增删改查进行优化的方法
2016/10/08 PHP
php微信公众号开发之校园图书馆
2018/10/20 PHP
Laravel 模型关联基础教程详解
2019/09/17 PHP
用Javascript做flash做的事..才完成的一个类.Auntion Action var 0.1
2007/02/23 Javascript
js计算两个时间之间天数差的实例代码
2013/11/19 Javascript
ExtJS判断IE浏览器类型的方法
2014/02/10 Javascript
3种Jquery限制文本框只能输入数字字母的方法
2014/12/03 Javascript
浅谈 javascript 事件处理
2015/01/04 Javascript
Bootstrap每天必学之媒体对象
2015/11/30 Javascript
Three.js学习之文字形状及自定义形状
2016/08/01 Javascript
jquery实现企业定位式导航效果
2018/01/01 jQuery
详解Node.js 中使用 ECDSA 签名遇到的坑
2018/11/26 Javascript
bootstrap tooltips在 angularJS中的使用方法
2019/04/10 Javascript
Angular2使用SVG自定义图表(条形图、折线图)组件示例
2019/05/10 Javascript
ES6 Iterator遍历器原理,应用场景及相关常用知识拓展详解
2020/02/15 Javascript
原生js实现瀑布流效果
2020/03/09 Javascript
vue $mount 和 el的区别说明
2020/09/11 Javascript
在Python的Flask中使用WTForms表单框架的基础教程
2016/06/07 Python
python3 pandas 读取MySQL数据和插入的实例
2018/04/20 Python
Python3.6简单的操作Mysql数据库的三个实例
2018/10/17 Python
python读取word文档,插入mysql数据库的示例代码
2018/11/07 Python
Python用61行代码实现图片像素化的示例代码
2018/12/10 Python
python 经典数字滤波实例
2019/12/16 Python
Django对接支付宝实现支付宝充值金币功能示例
2019/12/17 Python
keras 两种训练模型方式详解fit和fit_generator(节省内存)
2020/07/03 Python
CSS3弹性布局内容对齐(justify-content)属性使用详解
2017/07/31 HTML / CSS
介绍一下linux的文件权限
2014/07/20 面试题
财务会计专业毕业生自荐信
2013/10/02 职场文书
总经理任命书
2014/03/29 职场文书
岗位职责范本大全
2015/02/26 职场文书
2015年银行个人工作总结
2015/05/14 职场文书
常用的文件对应的MIME类型汇总
2022/04/26 HTML / CSS
Python中的socket网络模块介绍
2022/07/23 Python