跳至主要内容

Node.js从零开始——HTTP 服务器

其实 Node.js 最初的目的,就是实现一个完全可以由 JavaScript 来进行开发的服务器端,所以归根到底,它的后端能力之一就是实现一个 HTTP 服务器,这里我们来看看它。


1 搭建 HTTP 服务器

其实前面我们已经看过了一个例子,不过这里再来看一个 HTTP web 服务器的例子:

const http = require('http');

const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('你好世界\n');
})

server.listen(port, () => {
  console.log(`服务器运行在 http://${hostname}:${port}/`);
});


简要分析一下:

  • 这里引入了 ref="nodejs.cn/api/http.html">http 模块:使用该模块来创建 HTTP 服务器
  • 服务器被设置为在指定的 3000 端口上进行监听, 当服务器就绪时,则 listen 回调函数会被调用
  • 传入的回调函数会在每次接收到请求时被执行, 每当接收到新的请求时,"http://nodejs.cn/api/http.html#http_event_request">request 事件会被调用,并提供两个对象:一个请求(http.IncomingMessage 对象)和一个响应(http.ServerResponse 对象)
  • request 提供了请求的详细信息, 通过它可以访问请求头和请求的数据,response 用于构造要返回给客户端的数据;在此示例中:
res.statusCode = 200;


设置 statusCode 属性为 200,以表明响应成功;还设置了 Content-Type 响应头:

res.setHeader('Content-Type', 'text/plain')


最后结束并关闭响应,将内容作为参数添加到 end()

res.end('你好世界\n')

如上,一个简单的 HTTP 服务器已经完成了,只要运行对应代码即可启动;接下来我们可以看看它如何发送 HTTP 请求。

2 使用 Node.js 发送 HTTP 请求

2.1 执行 GET 请求

const https = require('https');
const options = {
  hostname: 'nodejs.cn',
  port: 443,
  path: '/todos',
  method: 'GET'
};

const req = https.request(options, res => {
  console.log(`状态码: ${res.statusCode}`);

  res.on('data', d => {
    process.stdout.write(d);
  });
});

req.on('error', error => {
  console.error(error);
});

req.end();

2.2 执行 POST 请求

const https = require('https');

const data = JSON.stringify({
  todo: '做点事情'
});

const options = {
  hostname: 'nodejs.cn',
  port: 443,
  path: '/todos',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': data.length
  }
};

const req = https.request(options, res => {
  console.log(`状态码: ${res.statusCode}`);

  res.on('data', d => {
    process.stdout.write(d);
  });
});

req.on('error', error => {
  console.error(error);
});

req.write(data);
req.end();

2.3 PUT 和 DELETE

PUTDELETE 请求使用相同的 POST 请求格式,只需更改 options.method 的值即可。

3 使用 Axios 发送 HTTP POST 请求

Node.js 中,有多种方式可以执行 HTTP POST 请求,具体取决于要使用的抽象级别。

使用 Node.js 执行 HTTP 请求的最简单的方式是使用 f="github.com/axios/axios">Axios 库(多嘴说一句,Vue.js 推荐使用的也是这个库):

const axios = require('axios');

axios
  .post('http://nodejs.cn/todos', {
    todo: '做点事情'
  })
  .then(res => {
    console.log(`状态码: ${res.statusCode}`);
    console.log(res);
  })
  .catch(error => {
    console.error(error);
  });

Axios 是第三方的库,所以需要通过包管理器先进行安装。

当然我们上面也使用了 Node.js 自带的模块来完成该操作,很明显要比 Axios 冗长不少,所以我个人也的确倾向于使用 Axios

4 使用 Node.js 获取 HTTP 请求的正文数据

这是在请求正文中提取以 JSON 格式发送的数据的方式。

如果使用的是 Express,则非常简单:使用 body-parser Node.js 模块。

例如,获取此请求的正文:

const axios = require('axios');

axios.post('http://nodejs.cn/todos', {
  todo: '做点事情'
});

这是对应的服务器端代码:

const bodyParser = require('body-parser');

app.use(
  bodyParser.urlencoded({
    extended: true
  });
);

app.use(bodyParser.json());

app.post('/todos', (req, res) => {
  console.log(req.body.todo);
});

如果不使用 Express 并想在普通的 Node.js 中执行此操作,则需要做多一点的工作,因为 Express 抽象(或者说简化)了很多工作。

要理解的关键是,当使用 http.createServer() 初始化 HTTP 服务器时,服务器会在获得所有 HTTP 请求头(而不是请求正文时)时调用回调。

在连接回调中传入的 request 对象是一个工作流;因此,必须监听要处理的主体内容,并且其是按数据块处理的。

首先,通过监听流的 data 事件来获取数据,然后在数据结束时调用一次工作流的 end 事件:

const server = http.createServer((req, res) => {
  // 可以访问 HTTP 请求头
  req.on('data', chunk => {
    console.log(`可用的数据块: ${chunk}`);
  });
  req.on('end', () => {
    //数据结束
  });
});

因此,若要访问数据(假设期望接收到字符串),则必须将其放入数组中:

const server = http.createServer((req, res) => {
  let data = [];
  req.on('data', chunk => {
    data.push(chunk);
  });
  req.on('end', () => {
    JSON.parse(data).todo; // '做点事情'
  });
});

5 Node.js http 模块

HTTP 核心模块是 Node.js 网络的关键模块。

可以使用以下代码引入:

const http = require('http');

该模块提供了一些属性、方法、以及类。

5.1 属性

(1)http.METHODS

此属性列出了所有支持的 HTTP 方法:

(2)http.STATUS_CODES

此属性列出了所有的 HTTP 状态代码及其描述:

(3)http.globalAgent

指向 Agent 对象的全局实例,该实例是 http.Agent 类的实例。

用于管理 HTTP 客户端连接的持久性和复用,它是 Node.js HTTP 网络的关键组件。

稍后会在 http.Agent 类的说明中提供更多描述。

5.2 方法

(1)http.createServer()

返回 http.Server 类的新实例。

用法:

const server = http.createServer((req, res) => {
  //使用此回调处理每个单独的请求
})

(2)http.request()

发送 HTTP 请求到服务器,并创建 http.ClientRequest 类的实例。

(3)http.get()

类似于 http.request(),但会自动地设置 HTTP 方法为 GET,并自动地调用 req.end()

5.3 类

HTTP 模块提供了 5 个类:

  • http.Agent
  • http.ClientRequest
  • http.Server
  • http.ServerResponse
  • http.IncomingMessage

(1)http.Agent

Node.js 会创建 http.Agent 类的全局实例,以管理 HTTP 客户端连接的持久性和复用,这是 Node.js HTTP 网络的关键组成部分。

该对象会确保对服务器的每个请求进行排队并且单个 socket 被复用。

它还维护了一个 socket 池,是其高效性能的关键。

(2)http.ClientRequest

http.request()http.get() 被调用时,会创建 http.ClientRequest 对象。

当响应被接收时,则会使用响应(http.IncomingMessage 实例作为参数)来调用 response 事件。

返回的响应数据可以通过以下两种方式读取:

  • 可以调用 response.read() 方法
  • response 事件处理函数中,可以为 data 事件设置事件监听器,以便可以监听流入的数据

(3)http.Server

当使用 http.createServer() 创建新的服务器时,通常会实例化并返回此类。

拥有服务器对象后,就可以访问其方法:

  • close() 停止服务器不再接受新的连接
  • listen() 启动 HTTP 服务器并监听连接。

(4)http.ServerResponse

http.Server 创建,并作为第二个参数传给它触发的 request 事件。

通常在代码中用作 res

const server = http.createServer((req, res) => {
  //res 是一个 http.ServerResponse 对象
});

在事件处理函数中总是会调用的方法是 end(),它会关闭响应,当消息完成时则服务器可以将其发送给客户端。 必须在每个响应上调用它。

以下这些方法用于与 HTTP 消息头进行交互:

  • getHeaderNames() 获取已设置的 HTTP 消息头名称的列表
  • getHeaders() 获取已设置的 HTTP 消息头的副本
  • setHeader('headername', value) 设置 HTTP 消息头的值
  • getHeader('headername') 获取已设置的 HTTP 消息头
  • removeHeader('headername') 删除已设置的 HTTP 消息头
  • hasHeader('headername') 如果响应已设置该消息头,则返回 true
  • headersSent() 如果消息头已被发送给客户端,则返回 true

在处理消息头之后,可以通过调用 response.writeHead()(该方法接受 statusCode 作为第一个参数,可选的状态消息和消息头对象)将它们发送给客户端。

若要在响应正文中发送数据给客户端,则使用 write(), 它会发送缓冲的数据到 HTTP 响应流。

如果消息头还未被发送,则使用 response.writeHead() 会先发送消息头,其中包含在请求中已被设置的状态码和消息,可以通过设置 statusCodestatusMessage 属性的值进行编辑:

response.statusCode = 500;
response.statusMessage = '内部服务器错误';

(5)http.IncomingMessage

http.IncomingMessage 对象可通过以下方式创建:

  • http.Server,当监听 request 事件时
  • http.ClientRequest,当监听 response 事件时

它可以用来访问响应:

  • 使用 statusCodestatusMessage 方法来访问状态
  • 使用 headers 方法或 rawHeaders 来访问消息头
  • 使用 method 方法来访问 HTTP 方法
  • 使用 httpVersion 方法来访问 HTTP 版本
  • 使用 url 方法来访问 URL
  • 使用 socket 方法来访问底层的 socket

因为 http.IncomingMessage 实现了可读流接口,因此数据可以使用流访问。


HTTP 服务器的部分其实不算非常复杂,毕竟我们需要实现的业务逻辑才是关键的。



from 知乎专栏-全栈开发从零开始 https://ift.tt/3dbsySc
via IFTTT

评论

此博客中的热门博文

Web API从零开始——SVG

SVG 是我基本没有用过的知识块,所以这里也是边分享边学习,尽量在我自己理解的基础上来分享。 1 概念 SVG 是一种基于 XML 语法的图像格式,全称是可缩放矢量图(Scalable Vector Graphics);其他图像格式都是基于像素处理的, SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。 SVG 文件可以直接插入网页,成为 DOM 的一部分,然后用 JavaScript 和 CSS 进行操作。 上面是 SVG 代码直接插入网页的例子。 SVG 代码也可以写在一个独立文件中,然后用 、 、 、 等标签插入网页: < img src = "circle.svg" > < object id = "object" data = "circle.svg" type = "image/svg+xml" ></</span> object > < embed id = "embed" src = "icon.svg" type = "image/svg+xml" > < iframe id = "iframe" src = "icon.svg" ></</span> iframe > CSS 也可以使用 SVG 文件: . logo { background : url ( icon.svg ); } SVG 文件还可以转为 BASE64 编码,然后作为 Data URI 写入网页: < img src = "data:image/svg+xml;base64,[data]" > 2 语法 2.1 标签 我们可以把 SVG 代码都放在顶层标签 之中,下面是一个例子: < svg width = "100%" height = "100%" > ...

JavaScript从零开始——入门(1)

  正好这段时间在学习JavaScript全栈,顺便把自己到目前为止的知识重新整理一下,做个自己能够理解的学习路径出来。 先从入门开始,老生常谈一下5W1H——有很大一部分,尤其是历史是引用自网道的JavaScript教程,特此声明: 入门篇 ​ wangdoc.com What——什么是JavaScript? 各个网站、书籍、教程都写了无数次JavaScript的定义,最简单的一种说法就是: JavaScript是一种轻量级的脚本语言。 其实这个定义到2020的今天,有一点点不太一样了,毕竟它已经发展到一个前人无法想象的地步了,如今的JavaScript不再是只能用来编写控制其他大型应用程序的“脚本”,它还可以用来构建服务器(如最常用的Web服务器等),构造移动应用(主流的iOS或者Android),甚至可以直接编写PC端桌面应用(Windows,Mactintosh以及Linux),已经是不折不扣的“全栈”语言了。 当然其实它所提供的核心语法并不算很多,只能用来做一些数学和逻辑运算,也不提供任何与I/O相关的API,而是要靠宿主环境(host)提供,所以它其实算是 嵌入式(embedded)语言 ,现如今这诸多用途的开发,都是各大JavaScript框架的功劳:譬如服务器构建,用到了著名的Node.js;移动应用上常见的是React Native,PhoneGap/Cordova甚至jQuery Mobile;桌面应用的框架则主要是electron.js。 最后,从语法角度看,它是一种 “对象模型”语言 ,各种宿主环境通过这个模型,描述自己的功能和操作接口,从而通过JavaScript来控制对应功能;但JavaScript并不是纯粹的“面向对象的”语言,因为它还支持其他的编程范式——如函数式编程——这就导致几乎任何问题,它都可能有多种解决的方法。 Where——JavaScript可以用在哪里? 如前所述,JavaScript的核心语法其实很精简,只包括了: 基本的语法构造 :操作符、控制结构、语句等; 标准库 :Array(数组)、Date(日期时间)、Math(数学库)等等。 除此之外的所有其他特性,都是由宿主所提供的API,仅仅是通过JavaScript进行调用。 如浏览器,它额外提供了如下三大类API: 浏览器控制类: 操作浏览器 DOM(文档对...

JavaScript从零开始——数据类型(7)

  还记得之前提到过,目前在 JavaScript 里面,一共有7种数据类型;今天就来介绍这第7种,也是最新的数据类型—— Symbol 。 这个类型目前并没有合适的中文翻译,因为叫做标志或者标识都有其局限性,主要是感觉不够准确,故此这里就直接使用了它的英文名称。 1 概念 ES 5  的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法( mixin  模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是  ES 6  引入 Symbol 的原因。 ES 6  引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它是  JavaScript  语言的第七种数据类型,前六种是: undefined 、 null 、布尔值( Boolean )、字符串( String )、数值( Number )、对象( Object )。 Symbol 值通过 Symbol 函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的  Symbol  类型。凡是属性名属于  Symbol  类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 上面代码中,变量 s 就是一个独一无二的值。 typeof 运算符的结果,表明变量 s 是  Symbol  数据类型,而不是字符串之类的其他类型。 注意, Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的  Symbol  是一个原始类型的值,不是对象。也就是说,由于  Symbol  值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。 Symbol 函数可以接受一个字符串作为参数,表示对  Symbol  实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 上面代码中, s1 和 s2 是两个  Symbol  值。如果不加参数,它们在控制台的输出都是 Symbol() ...