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 相关文章推荐
Ubuntu中搭建Nodejs开发环境过程分享
Jun 01 NodeJs
详谈Angular路由与Nodejs路由的区别
Mar 05 NodeJs
Nodejs 获取时间加手机标识的32位标识实现代码
Mar 07 NodeJs
详解HTTPS 的原理和 NodeJS 的实现
Jul 04 NodeJs
nodejs对express中next函数的一些理解
Sep 08 NodeJs
NodeJs form-data格式传输文件的方法
Dec 13 NodeJs
nodejs调取微信收货地址的方法
Dec 20 NodeJs
nodejs中密码加密处理操作详解
Mar 20 NodeJs
关于Mac下安装nodejs、npm和cnpm的教程
Apr 11 NodeJs
nodejs遍历文件夹下并操作HTML/CSS/JS/PNG/JPG的方法
Nov 01 NodeJs
详解从NodeJS搭建中间层再谈前后端分离
Nov 13 NodeJs
NodeJs 模仿SIP话机注册的方法
Jun 21 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
上海无线电三厂简史修改版
2021/03/01 无线电
php计算税后工资的方法
2015/07/28 PHP
php语言中使用json的技巧及json的实现代码详解
2015/10/27 PHP
php上传图片并压缩的实现方法
2015/12/22 PHP
php的debug相关函数用法示例
2016/07/11 PHP
laravel使用Faker数据填充的实现方法
2019/04/12 PHP
JavaScript 无符号右移赋值操作
2009/04/17 Javascript
javascript 写类方式之一
2009/07/05 Javascript
JQuery1.6 使用方法三
2011/11/23 Javascript
JSON 数据格式介绍
2012/01/13 Javascript
YUI模块开发原理详解
2013/11/18 Javascript
node.js中的path.extname方法使用说明
2014/12/09 Javascript
jQuery抛物线运动实现方法(附完整demo源码下载)
2016/01/08 Javascript
动态的9*9乘法表效果的实现代码
2016/05/16 Javascript
jQuery中通过ajax调用webservice传递数组参数的问题实例详解
2016/05/20 Javascript
vue.js中$watch的用法示例
2016/10/04 Javascript
微信小程序 教程之条件渲染
2016/10/18 Javascript
基于vuejs+webpack的日期选择插件
2020/05/21 Javascript
Ionic + Angular.js实现图片轮播的方法示例
2017/05/21 Javascript
JavaScript中运算符规则和隐式类型转换示例详解
2017/09/06 Javascript
详解Vue项目编译后部署在非网站根目录的解决方案
2018/04/26 Javascript
vue单页应用在页面刷新时保留状态数据的方法
2018/09/21 Javascript
vue项目启动出现cannot GET /服务错误的解决方法
2020/04/26 Javascript
[32:17]完美世界DOTA2联赛循环赛LBZS vs Forest第二场 10月30日
2020/10/31 DOTA
在Python中使用cookielib和urllib2配合PyQuery抓取网页信息
2015/04/25 Python
python中利用xml.dom模块解析xml的方法教程
2017/05/24 Python
Python反爬虫技术之防止IP地址被封杀的讲解
2019/01/09 Python
Django 多表关联 存储 使用方法详解 ManyToManyField save
2019/08/09 Python
在Python中使用MongoEngine操作数据库教程实例
2019/12/03 Python
CSS3 @font-face属性使用指南
2014/12/12 HTML / CSS
面向游戏玩家和书呆子的极客订阅盒:Loot Crate
2020/11/25 全球购物
酒店实习个人鉴定
2013/12/07 职场文书
周鸿祎:教你写创业计划书
2013/12/30 职场文书
竞选班长演讲稿
2013/12/30 职场文书
优秀士兵先进事迹
2014/02/06 职场文书
sql server 累计求和实现代码
2022/02/28 SQL Server