zoukankan      html  css  js  c++  java
  • express系列(5):路由

    什么是路由?

    假设,现在你尝试通过 example.com/someone 访问某人的推特或者微博主页,你会发现该请求的 HTTP 内容大致如下:

    GET /someone http/1.1

    其中包含了 HTTP 请求使用的方法(GET),URI 信息(/someone) 以及 HTTP 协议版本 (1.1)。

    Express 中的路由就是负责将其中的 HTTP 方法和 URI 这对组合映射到对应的中间件。

    简单说就是, /about_me 的GET 请求会执行某个中间件而对于 /new_user 的 POST 请求则执行另一个中间件。

    下面我们通过一个简单示例来看看到底路由时如何工作的。

    路由的一个简单示例

    下面我们就对 example.com/someone 请求进行一个简单的实现,代码如下:

    var express = require("express");
    var app = express();
    
    app.get('/someone', function(request, response) {
        response.send(" Welcome to someone's homepage! ");
    });
    
    app.use(function(request, response) {
        response.status(404).send("Page not found!");
    });
    
    app.listen(3000);

    上面代码中有价值的是第三行:当你通过 HTTP 的 GET 方法对 /someone 发起请求时,程序会执行该中间件中的代码,其他请求则会被忽略并跳转到下一个中间件。

    路由的特性

    从工作原理来说:路由就是通过对 HTTP 方法和的 URI 的组合进行映射来实现对不同请求的分别处理。

    当然,除了上面那种最简单的使用方式之外,Express 的路由还有更多实用的使用技巧和方法。

    含参的通配路由

    在上面的使用方式中使用的是全等判断来进行路由匹配的。虽然对于 /someone 这类非常管用。

    但是对于形如 /users/1/users/2 这类 RESTful 路由就明显不那么友好了。

    因为如果将后者路由一一列出的话,不管是从工作量还是后期维护来说都是非常差开发体验。针对这种情况,我们可以使用 Express 中含参的通配路由来解决。

    该方法的工作原理就是,在路由中使用参数进行通配表示。而该参数所表示的具体数值会在变量 params 中获取到,下面是简单的代码示例:

    app.get("/users/:userid", function(req, res) {
        // 将userId转换为整型
        var userId = parseInt(req.params.userid, 10);
        // ...
    });
    这样 RESTful 风格的动态路由就完全可以通过这种含参的通配路由进行处理。那么无论是 /users/123 还是 /users/8 都会被映射到同一中间件。
    虽然 /users/ 或者 /users/123/posts 不会被匹配,但是 /users/cake/users/horse_ebooks 确会被匹配到。所以,如果实现更精准的路由匹配的话就需要使用其他方式了。

    使用正则表达式匹配路由

    针对上面的问题,我们可以使用正则来对路由进行更精准的匹配。

    假设现在我们只需要匹配 /users/123/users/456 这种通配参数为数字的动态路由的同时忽略其他路由格式,那么可以将代码改为:

    app.get(/^/users/(d+)$/, function(req, res) {
        var userId = parseInt(req.params[0], 10);
        // ...
    });

    通过正则表达式代码对通配参数作为了严格限定:该参数必须是数字类型。

    正则表达式可能阅读起来并不是很友好,但是它却可以实现对复杂路由匹配规则的准确定义。

    例如,你想匹配路由 /users/100-500 这类表示某个用户范围的列表页面,那么该正则如下:

    app.get(/^/users/(d+)-(d+)$/, function(req, res) {
    
        var startId = parseInt(req.params[0], 10);
    
        var endId = parseInt(req.params[1], 10);
        //
    });

    正则表达式可以让你的路由匹配定义更上一层楼。

    捕获查询参数

    另一种常用的动态传入 URL 参数的方法就是通过查询字符串。

    例如,当你使用谷歌搜索 javascript-themed burrito 时,你可以会发现对应的 URL 可能是 www.google.com/search?q=ja…

    如果 Google 是用 Express 进行实现的话,那么可以这样来获取用户传入的信息:

    app.get("/search", function(req, res) {
        // req.query.q == "javasript-themed burrito"
        // ...
    });
    需要注意的是:查询参数中存在其实存在着类型安全问题。
    例如:如果你访问 ?arg=something 那么 req.query.arg 就是一个字符串类型,但是如果访问的是 ?arg=something&arg=somethingelse 的话 req.query.arg 就变为了一个数组类型。
    简单来说:不要轻易的断定查询参数的类型。

    使用 Router 划分你的 app

    伴随着应用的扩张,程序中产生的路由也会越来越多。而对这些庞大的路由进行管理并不是一件轻松的事,不过好在 Express 4 新增了 Router (可以理解为路由器)特性。

    Router 的官方描述是:

    Router 是一个独立于中间件和路由的实例,你可以将 Router 看作是只能执行执行中间件和路由的应用。而 Express 程序本身就内置了一个 Router 实例。

    Router 的行为与中间件类型,它可以通过 .use() 来调用其他的 Router 实例。

    换句话就是,可以使用 Router 将应用划分为几个小的模块。虽然对于一些小型应用来说这样做可能是过度设计,但是一旦 app.js 中的路由扩张太快的话你就可以考虑使用 Router 进行模块拆分了。

    注意:程序越大 Router 发挥的作用就越明显。虽然这里我不会编写一个大型应用程序,但是你可以在你的脑海中对下面的示例功能进行无限扩张。

    var express = require("express");
    var path = require("path");
    
    // 引入 API  Router
    var apiRouter = require("./routes/api_router");
    
    var app = express();
    var staticPath = path.resolve(__dirname, "static");
    app.use(express.static(staticPath));
    // API  Router 文件的调用
    app.use("/api", apiRouter);
    app.listen(3000);

    如上所示,Router 的使用方式和之前的中间件非常类似。

    其实 Router 本质上就是中间件。在代码中我们将所有 /api 开头的 URL 全部转发到了 apiRouter 中了, 这意味着 /api/users/api/message 的处理都会在 apiRouter 中进行。

    下面就是 api_router.js 文件的一个简单代码示例:

    var express = require("express");
    var ALLOWED_IPS = [
        "127.0.0.1",
        "123.456.7.89"
    ];
    var api  = express.Router();
    api.use(function(req, res, next) {
        var userIsAllowed = ALLOWED_IPS.indexOf(req.ip) !== -1;
        if(!userIsAllowed) {
            res.status(401).send("Not authorized!");
        } else {
            next();
        }
    });
    api.get("/users", function(req, res) { /* ... */ });
    api.post("/users", function(req, res) { /* ... */ });
    api.get("/messages", function(req, res) { /* ... */ });
    api.post("/messages", function(req, res) { /* ... */ });
    module.exports = api;

    其实 Router 与 app.js 在功能上没有任何区别,都是处理中间件和路由。

    最大的不同在于:Router 只能已模块形式存在并不能独立运行。

    参照示例,你可以在自己的应用中按模块划分出更多的 Router 。

    静态文件

    除非应用是纯 API 服务,否则总可能需要发送静态文件。这些文件可能是静态图片 CSS 样式文件或者是静态 HTML 文件。

    静态文件中间件

    直接查看代码:

    var express = require("express");
    var path = require("path");
    var http = require("http");
    var app = express():
    // 设置你的静态文件路径
    var publicPath = pathresolve(dirname, "public");
    // 从静态文件夹中发送静态文件
    app.use(express.static(publicPath));
    app.use(function(request, response) {
        response.writeHead(200, { "Content-Type": "text/plain"});
        reponse.end("Looks like you didn't find a static file.");
    });
    http.createServer(app).listen(3000);

    修改静态文件的 URL

    通常情况下,我们会把站点的静态文件 URL 路径直接挂在域名后面,例如:jokes.edu 站点中的 jokes.txt 文件 URL 样式应该是 jokes.edu/jokes.txt

    你可可以按照自己的习惯给这些静态文件提供 URL 。

    例如,将一些无序但有趣的图片存放在文件夹 offensive 中并将其中图片的 URL 设置为 jokes.edu/offensive/p… 这种形式。那么该样式 URL 如何实现呢?

    在 Express 中,我们可以使用指定前缀的中间件来对静态文件 URL 进行自定义。所以上面问题的代码实现如下:

    // ... 
    var photoPath = path.resolve(__dirname, "offensive-photos-folder");
    app.use("/offensive", express.static(photoPath));
    // ...

    这样你所有静态文件的 URL 都可以实现自定义了,而不是粗暴的直接挂在域名后面了。其实除了静态中间件和前面 Router 外,其它中间件同样可以指定 URL 前缀。

    多个静态文件夹的路由

    真实项目中可能户存在多个静态文件夹,例如:一个存放 CSS 等公用文件的 public 文件夹,一个存放用户上传文件的 user_uploads 文件夹。

    epxress.static 本身作为中间件是可以在代码中多次调用的:

    // ...
    var publiscPath = path.resolve(__dirname, "public");
    var userUploadPath = path.resove(__dirname, "user_uploads");
    app.use(express.static(publicPath));
    app.use(express.static(userUploadsPath));
    // ...

    接下来,我们通过四个模拟场景看看上面代码是如何工作的:

    1. 用户请求的资源两个文件夹里都没有,则上面两个中间件都会被跳过执行。
    2. 用户请求的资源只在 public 里面则第一个中间件响应执行并返回。
    3. 用户请求的资源只在 user_uploads 里面则第一个中间件被跳过而第二个得道执行。
    4. 用户请求的资源在两个文件夹中都存在,则第一个中间件响应执行并返回,第二个不会得到执行。

    对于第四章情况,如果该资源是相同的还好说,但是一旦只是资源同名就存在明显错误了。为此,我们依旧可以使用 URL 前缀来应对:

    // ...
    app.use("/public", express.static(publicPath));
    app.use("/uploads", express.static(userUploadsPath));
    // ...

    这样对于同名文件 image.jpg Express 会将其分别映射到 /public/image.jpg 和 /uploads/image.jpg 。

    路由到静态文件映射

    在程序中有可能还存在对动态路由请求响应静态文件情形,例如,当用户访问 /users/123/profile_photo 路径时程序需要发送该用户的图片。

    静态中间件本身时无法处理该需求,不过好在 Express 可以使用与静态中间件类似的机制来处理这种情况。

    假设当有人发起 /users/:userid/profile_photo 请求时,我们都需要响应对应 userid 用户的图片。另外,假设程序中存在一个名为 getProfilePhotoPath 的函数,该函数可以根据 userid 获取图片的存储路径。那么该功能的实现代码如下:

    app.get("/users/:userid/profile_photo", function(req, res) {
        res.sendFile(getProfilePhotoPath(req.params.userid));
    });

    仅仅只需指定路由然后通过 sendFile 函数,我们就可以完成该路由对应文件的发送任务。

    在 Express 使用 HTTPS

    HTTPS 是在 HTTP 基础上添加了一个安全层,通常情况下该安全层被称为 TLS 或者 SSL 。

    虽然两个名字可以互换,但是 TSL 在技术上涵盖了 SSL。

    这里并不会介绍 HTTPS 复杂的 RSA 加密数学原理(欧拉函数)。

    简单来说 HTTPS 的加密过程就是:所有的客户端都使用服务端公开的公钥加密请求信息,然后服务端使用私钥对加密后内容进行解密。

    这样就能在某种程度上防止信息被窃听。另外,加密的公钥也被称为证书。客户端在拿到公钥证书后会向 Google 这样的证书颁发机构进行验证。

    注意:类似 Heroku 这样的虚拟主机商已经提供了 HTPPS 服务,所以这部分内容只在你需要自己实现 HTTPS 时才派得上用场。

    首先,我们通过 OpenSSL 生成自签名的公钥和私钥。Windows 系统可以使用去官网获取 OpenSSL 安装文件,Linux 可以使用保管理器进行安装,而 macOS 系统已经预装过了。

    通过 openssl version 验证系统是否成功安装了 OpenSSL, 确保安装后输入下面两个命令:

    openssl genrsa -out privatekey.pem 1024
    openssl req -new -key privatekey.pem -out request.pem

    第一个命令会生成名为 privatekey.pem 的私钥。第二个命令会让你输入一些信息,然后使用 privatekey.pem 生成签名的证书请求文件 request.pem

    然后你就可以去证书请求机构申请一个加密的公钥证书。虽然大部分证书都是收费的,但是你还是可以去 letsencrypt 申请免费版本证书。

    一旦获取了 SSL 证书文件,你就可以使用 Node 内置的 HTTPS 模块了,代码如下:

    var express = require("express");
    var https = require("https");
    var fs = require("fs");
    var app = express();
    // ... 定义你的app ...
    // 定义一个对象来保存证书和私钥
    var httpsOptions = {
        key: fs.fs.readFileSync("path/to/private/key.pem");
        cert: fs.fs.readFileSync("path/to/certificate.pem");
    }
    
    https.createServer(httpsOptions, app).listen(3000);

    除了配置私钥和公钥证书参数之外,其他部分与之前 HTTP 模块的使用时一致的。当然,如果你想同时支持 HTTP 和 HTTPS 协议的话也是可以的:

    var express = require("express");
    var http = require("http");
    var https = require("https");
    var fs = require("fs");
    var app = express();
    // ... 定义你的app ...
    var httpsOptions = {
        key: fs.readFileSync("path/to/private/key.pem"),
        cret: fs.readFileSync("path/to/certificate.pem")
    };
    http.createServer(app).listen(80);
    https.createServer(httpsOptions, app).listen(443);

    需要注意的是 HTTP 和 HTTPS 协议 同时开启时需要使用不同的端口号。

    总结

    在本章中,我们学到了:

    • 从概念上知道了什么是路由:进行 URL 和代码的映射的工具。
    • 简单的路由以及常用映射处理。
    • 获取路由中的参数。
    • Express 4 路由模块的新特性。
    • 将路由应用到中间件处理。
    • 如何在 Express 中使用 HTTPS。
  • 相关阅读:
    复制表部分内容到另一个表
    delphi使用ODAC控件TOraQuery操作表数据
    delphi使用ODAC控件查询存储过程返回结果集
    delphi使用ODAC控件事务处理(缓存提交)
    delphi使用ODAC控件事务处理(自动提交)
    delphi使用ODAC控件异步读取数据
    delphi使用ODAC控件常用功能
    delphi使用ODAC控件操作本地数据集
    时间限制关闭窗体的几点体会
    一个简单的双缓冲队列
  • 原文地址:https://www.cnblogs.com/magicg/p/12821859.html
Copyright © 2011-2022 走看看