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

2018-10-12 47 PandaShen

前言

在 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中使用monk访问mongodb
Jul 06 37
Nodejs学习笔记之Stream模块
Jan 13 42
Nodejs初级阶段之express
Nov 23 45
Nodejs抓取html页面内容(推荐)
Aug 11 37
NodeJS远程代码执行
Aug 28 40
深入解析nodejs HTTP服务
Jul 25 48
在Debian(Raspberry Pi)树莓派上安装NodeJS的教程详解
Sep 19 37
手机访问当前页面