NodeJS 搭建 HTTP 服务器

news/2024/7/19 11:16:21 标签: 爬虫, 网络

在这里插入图片描述

原文出自:https://www.pandashen.com

前言

在 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:资源路径(根据不同的路径返回不同的资源)。

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

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-LengthDate ,代表当前返回给客户端的内容长度和日期。

返回给浏览器的内容可以通过 reswrite 方法和 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 的小伙伴在看了这篇文章后能有所收获。


http://www.niftyadmin.cn/n/630100.html

相关文章

找不到文件或程序集名称_想研究新冠CT找不到数据集?UCSD、Petuum开源COVIDCT 数据集...

机器之心发布机器之心编辑部CT 图像在对新冠肺炎提供准确、快速、廉价的筛查和检测方面很有前景&#xff0c;但很多研究者都苦于找不到合适的数据集。在本文中&#xff0c;来自加州大学圣地亚哥分校、Petuum 的研究者构建了一个开源的 COVID-CT 数据集&#xff0c;其中包含 275…

五邑大学C语言2017,五邑大学c语言试卷.doc-资源下载在线文库www.lddoc.cn

五邑大学 c语言试卷.doc第5页 共5页 一选择题 1关于C程序的叙述&#xff0c;错误的说法是 。A C程序总是从主函数开始执行B C程序中定义的第一个函数是主函数C 在主函数中可以调用其他函数D 一个C程序可以包括多个函数2C语言的基本数据类型包括 。A char B structc string D ty…

如何协助 MySQL 实现 Oracle 高级分析函数

Oracle 支持一些独特的语法和函数&#xff0c;在移植到 MySQL 上时或多或少给程序员造成了困扰&#xff0c;下面我们针对 Oracle 的一些特殊用法举例并讲解如何用集算器来完成同样功能。这些方法当然也不限于针对 MySQL&#xff0c;对于所有其它数据库也能支持。 1、 递…

win7mysql5.7.25安装教程_win7下MySql 5.7安装配置方法图文教程

上学的时候经常使用MySql&#xff0c;当时也没想其他&#xff0c;主要是MySql对电脑的要求比较低&#xff0c;负载比较小。工作后一直在使用Oracle&#xff0c;现在因为项目的需要&#xff0c;重新安装MySql&#xff0c;发现变化有点多。本经验适用于安装MySql最新版本数据库。…

3v stm32 供电 晶振起振_32.768kHz晶振不起振的终极解决方法?

32.768kHz晶振不起振的终极解决方法32.768kHz晶振是市面上使用最为广泛的一类晶振。爱普生/EPSON目前提供三类32.768kHz晶振产品以满足客户不同需要&#xff0c;分别是&#xff1a;32.768kHz有源晶振(OSC),32.768kHz无源晶振(X’tal)和内置32.768kHz晶体谐振器的实时时钟模块(R…

迪杰斯特拉算法c语言要点,C语言迪杰斯特拉实现最短路径算法要点.doc

C语言迪杰斯特拉实现最短路径算法要点.doc数据结构课程设计报告----旅游咨询系统设计目录一、需求分析- 2 -二、系统分析- 2 -三、概要设计- 3 -一、系统划分- 3 -二、邻接矩阵建立流程图&#xff1a;- 3 -三、迪杰斯特拉算法流图- 5 -四、详细设计- 6 -五、调试分析- 9 -一、运…

python0到10的for循环整数_python 整型、字符串常用方法、for循环

整型--int 定义:用于比较和计算 python2和python3: python2:python2中油int(整型)和long(长整型):1231312L+ 进制转换: 十进制转二进制:正除2,获取余数,余数从上往下进行整合 # 123456(十进制) # 余数 # 3 1 # 1 1 # 0 # bin 求数字的二进制 # 0b -- 二进制 # print(bi…

mysql 5.7 sql mode_MySQL 5.7.9版本sql_mode=only_full_group_by问题

用到GROUP BY 语句查询时com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column col_user_6.a.START_TIME which is not functionally dependent on columns in GROUP BY cl…