Node.js 路由

在 Node.js 中,路由是处理 HTTP 请求的关键部分,它决定了如何根据不同的 URL 和 HTTP 方法(如 GET、POST、PUT、DELETE 等)来分发请求。

路由通常用于构建 Web 应用程序,特别是 RESTful API。

Node.js 本身并没有内置的路由机制,但可以通过中间件库(如 Express)来实现。

路由通常涉及以下几个方面:

  • URL 匹配:根据请求的 URL 来匹配路由规则。
  • HTTP 方法匹配:根据请求的 HTTP 方法(GET、POST、PUT、DELETE 等)来匹配路由规则。
  • 请求处理:一旦匹配到合适的路由规则,就调用相应的处理函数来处理请求。

Node.js 中,我们可以通过 http 模块创建一个简单的路由,如下实例:

实例

const http = require('http');

// 创建服务器并定义路由
const server = http.createServer((req, res) => {
  const { url, method } = req;

  if (url === '/' && method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Home Page');
  } else if (url === '/about' && method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('About Page');
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('404 Not Found');
  }
});

server.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

浏览器访问 http://localhost:3000 显示如下:

浏览器访问 http://localhost:3000/about 显示如下:

如果访问其他 URL 地址,则会直接显示 404 Not Found

更多 HTTP 请求可以参考:HTTP 教程


请求参数

一个完整 URL 的 http://localhost:8888/start?foo=bar&hello=world 包含主机、路径和查询字符串。

为了解析这些数据,我们可以使用 URL 对象和 querystring 模块。

const myUrl = new URL("http://localhost:8888/start?foo=bar&hello=world");

// 提取路径名
console.log(myUrl.pathname); // 输出: /start

// 提取查询参数
console.log(myUrl.searchParams.get("foo"));   // 输出: bar
console.log(myUrl.searchParams.get("hello")); // 输出: world

myUrl.pathname
          |
          |
        -----
http://localhost:8888/start?foo=bar&hello=world
                                ---       -----
                                 |          |
                                 |          |
           myUrl.searchParams.get("foo")    |
                                            |
                                     myUrl.searchParams.get("hello")

当然我们也可以用 querystring 模块来解析 POST 请求体中的参数,相关内容后面的 Node.js GET/POST请求 会介绍。

我们需要的所有请求数据都会包含在 request 对象中,该对象作为 onRequest() 回调函数的第一个参数传递。

现在我们来给 http 模块的 onRequest() 函数加上一些逻辑,用来找出浏览器请求的 URL 路径:

server.js 文件代码:

var http = require("http"); var url = require("url"); function start() { function onRequest(request, response) { // 使用 URL 构造函数解析请求路径 const pathname = new URL(request.url, `http://${request.headers.host}`).pathname; console.log(`Request for ${pathname} received.`); // 打印请求路径 // 设置响应头和响应内容 response.writeHead(200, { "Content-Type": "text/plain" }); // 设置状态码和内容类型 response.write("Hello World"); // 向客户端发送响应内容 response.end(); // 结束响应 } // 创建服务器并监听指定端口 http.createServer(onRequest).listen(8888); console.log("Server has started."); // 打印服务器启动消息 } // 导出 start 函数供其他模块使用 module.exports.start = start;

好了,我们的应用现在可以通过请求的 URL 路径来区别不同请求了--这使我们得以使用路由(还未完成)来将请求以 URL 路径为基准映射到处理程序上。

在我们所要构建的应用中,这意味着来自 /start 和 /upload 的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。

现在我们可以来编写路由了,建立一个名为 router.js 的文件,添加以下内容:

router.js 文件代码:

function route(pathname) { console.log("About to route a request for " + pathname); } // 导出了 route 函数 exports.route = route;

router.js 处理路由逻辑,定义并导出 route 函数,用于在服务器收到请求时处理不同路径。

我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块。

首先,我们来扩展一下服务器的 start() 函数,以便将路由函数作为参数传递过去,server.js 文件代码如下

server.js 文件代码:

// server.js const http = require("http"); // 引入 Node.js 的 http 模块,用于创建服务器 const { URL } = require("url"); // 从 url 模块引入 URL 构造函数 // 定义并导出 start 函数,用于启动服务器 function start(route) { // 定义 onRequest 函数,处理每个请求 function onRequest(request, response) { // 使用 URL 构造函数解析请求路径 const pathname = new URL(request.url, `http://${request.headers.host}`).pathname; console.log(`Request for ${pathname} received.`); // 打印请求路径 route(pathname); // 调用路由函数处理路径 // 设置响应头和响应内容 response.writeHead(200, { "Content-Type": "text/plain" }); response.write("Hello World"); response.end(); } // 创建服务器并监听指定端口 http.createServer(onRequest).listen(8888); console.log("Server has started."); } // 导出 start 函数供其他模块使用 module.exports.start = start;

server.js 定义了服务器的启动逻辑,并在接收到请求时调用路由函数。

同时,我们会相应扩展 index.js,使得路由函数可以被注入到服务器中:

index.js 文件代码:

var server = require("./server"); var router = require("./router"); server.start(router.route);

index.js 是程序的入口文件,负责启动服务器并将路由模块传入服务器模块中。

现在启动应用(node index.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的 HTTP 服务器已经在使用路由模块了,并会将请求的路径传递给路由:

$ node index.js
Server has started.

在浏览器中访问 http://localhost:8888/,服务器应会在控制台打印路径相关的路由消息,并返回"Hello World"响应:

后台终端会显示访问信息:

Request for / received.
About to route a request for /
Request for /favicon.ico received.
About to route a request for /favicon.ico

使用 Express 进行路由

Express 是一个流行的 Node.js 框架,它提供了强大的路由功能。

更多关于 Express 内容可以参考:Node.js Express 框架

安装 Express

首先,确保你已经安装了 Express,如果还没有安装,可以使用 npm 来安装:

npm install express

基本路由

以下是一个简单的 Express 应用程序,展示了如何设置基本的路由:

实例

const express = require('express');
const app = express();
const port = 3000;

// 定义一个 GET 路由
app.get('/', (req, res) => {
    res.send('Hello, World!');
});

// 定义一个 POST 路由
app.post('/submit', (req, res) => {
    res.send('Form submitted!');
});

// 启动服务器
app.listen(port, () => {
    console.log(`Server is running on http://localhost:${port}`);
});

动态路由

动态路由允许你使用参数化的 URL。

例如,你可以定义一个路由来处理 /users/:id,其中 :id 是一个动态参数。

app.get('/users/:id', (req, res) => {
    const userId = req.params.id;
    res.send(`User ID: ${userId}`);
});

路由参数

Express 允许你从 URL 中提取参数,并通过 req.params 对象访问这些参数。

实例

app.get('/users/:id', (req, res) => {
    const userId = req.params.id;
    res.send(`User ID: ${userId}`);
});

app.get('/search/:query', (req, res) => {
    const query = req.params.query;
    res.send(`Search query: ${query}`);
});

查询参数

查询参数是 URL 中的键值对,通常用于传递额外的信息。你可以通过 req.query 对象访问查询参数。

app.get('/search', (req, res) => {
    const query = req.query.q;
    res.send(`Search query: ${query}`);
});

路由中间件

路由中间件是在处理请求之前或之后执行的函数。你可以使用中间件来处理诸如身份验证、日志记录等任务。

// 日志记录中间件
const logger = (req, res, next) => {
    console.log(`Request Type: ${req.method} ${req.url}`);
    next();
};

// 使用中间件
app.use(logger);

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

路由分组

为了更好地组织代码,你可以使用路由分组。Express 提供了 express.Router 对象,可以用来创建模块化的、可挂载的路由处理程序。

实例

const express = require('express');
const app = express();
const port = 3000;

// 创建一个路由器实例
const userRouter = express.Router();

// 定义用户相关的路由
userRouter.get('/', (req, res) => {
    res.send('List of users');
});

userRouter.get('/:id', (req, res) => {
    const userId = req.params.id;
    res.send(`User ID: ${userId}`);
});

// 挂载用户路由器
app.use('/users', userRouter);

// 启动服务器
app.listen(port, () => {
    console.log(`Server is running on http://localhost:${port}`);
});

高级路由技巧

1、错误处理: 你可以定义错误处理中间件来捕获和处理路由中的错误。

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});

2、路由优先级:路由的定义顺序决定了它们的优先级,先定义的路由会先被匹配。

3、路由限制: 你可以使用中间件来限制某些路由的访问,例如仅允许认证用户访问。

实例

const authMiddleware = (req, res, next) => {
    if (req.headers.authorization) {
        next();
    } else {
        res.status(401).send('Unauthorized');
    }
};

app.get('/admin', authMiddleware, (req, res) => {
    res.send('Admin page');
});