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的前后端分离的思考与实践(四)安全问题解决方案
Sep 26 NodeJs
nodejs中简单实现Javascript Promise机制的实例
Dec 06 NodeJs
Nodejs学习笔记之测试驱动
Apr 16 NodeJs
浅谈Nodejs观察者模式
Oct 13 NodeJs
nodejs加密Crypto的实例代码
Jul 07 NodeJs
nodeJs内存泄漏问题详解
Sep 05 NodeJs
NodeJS安装图文教程
Apr 19 NodeJs
Nodejs异步回调之异常处理实例分析
Jun 22 NodeJs
详解nodejs解压版安装和配置(带有搭建前端项目脚手架)
Dec 06 NodeJs
Nodejs 识别图片类型的方法
Aug 15 NodeJs
NodeJS有难度的面试题(能答对几个)
Oct 09 NodeJs
NodeJs使用webpack打包项目的方法详解
Feb 28 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实现网站插件机制的方法
2009/11/10 PHP
php封装一个异常的处理类
2017/06/08 PHP
JavaScript语言中的Literal Syntax特性分析
2007/03/08 Javascript
JavaScript使用技巧精萃[代码非常实用]
2008/11/21 Javascript
如何制作浮动广告 JavaScript制作浮动广告代码
2012/12/30 Javascript
Javascript对象属性方法汇总
2013/11/21 Javascript
jQuery中append()方法用法实例
2014/12/25 Javascript
浅谈javascript对象模型和function对象
2014/12/26 Javascript
js实现表单Radio切换效果的方法
2015/08/17 Javascript
js表单提交和submit提交的区别实例分析
2015/12/10 Javascript
jquery mobile 实现自定义confirm确认框效果的简单实例
2016/06/17 Javascript
Vue+webpack项目配置便于维护的目录结构教程详解
2018/10/14 Javascript
微信小程序wx.navigateTo中events属性实现页面间通信传值,数据同步
2019/07/13 Javascript
jquery实现的放大镜效果示例
2020/02/24 jQuery
vue v-for 点击当前行,获取当前行数据及event当前事件对象的操作
2020/09/10 Javascript
vue pages 多入口项目 + chainWebpack 全局引用缩写说明
2020/09/21 Javascript
vue 数据遍历筛选 过滤 排序的应用操作
2020/11/17 Javascript
js定时器出现第一次延迟的原因及解决方法
2021/01/04 Javascript
[12:36]《DOTA2》国服注册与激活指南全攻略
2013/04/28 DOTA
对于Python异常处理慎用“except:pass”建议
2015/04/02 Python
python中的代码编码格式转换问题
2015/06/10 Python
Python科学计算之NumPy入门教程
2017/01/15 Python
机器学习python实战之手写数字识别
2017/11/01 Python
Python通过Pygame绘制移动的矩形实例代码
2018/01/03 Python
django 使用 request 获取浏览器发送的参数示例代码
2018/06/11 Python
Pycharm保存不能自动同步到远程服务器的解决方法
2019/06/27 Python
Jupyter notebook设置背景主题,字体大小及自动补全代码的操作
2020/04/13 Python
Python3 用matplotlib绘制sigmoid函数的案例
2020/12/11 Python
详解px单位html5响应式方案
2018/03/08 HTML / CSS
瑞典灯具和照明网上商店:Lamp24.se
2018/03/17 全球购物
关键字final的用法
2013/10/02 面试题
计算机专业毕业生求职信分享
2013/12/24 职场文书
大学生个人自荐信样本
2014/03/02 职场文书
竞选班干部演讲稿100字
2014/08/20 职场文书
结婚保证书
2015/01/16 职场文书
指导老师鉴定意见
2015/06/05 职场文书