了解Express.js:创建自己的节点HTTP请求路由器

(自己动手:节点HTTP路由器)

Express是一个了不起的JavaScript框架,可作为许多全栈Web应用程序的后端。 我们许多人每天都在使用它,并且精通如何 使用它,但可能对它的工作原理缺乏了解。 如今,无需深入研究Express源代码,我们将重新创建一些路由功能,以更好地了解框架的运行环境以及如何处理响应和请求。

如果您想查看最终的源代码,可以在Github上找到它。 请和我一起编写代码,以获得更好的学习体验!

入门

让我们从模拟Express的“ Hello World”应用程序开始 。 我们将略微修改它,因为我们不会引入express ,而是引入我们自己创建的模块。

首先,创建一个新的项目文件夹,并使用默认配置启动一个npm项目。

mkdir diy- node -router
cd diy- node -router
npm init -y

验证您的package.json文件如下所示:

{
  "name" : "diy-node-router" ,
  "version" : "1.0.0" ,
  "description" : "" , 
  "main" : "index.js" ,
  "scripts" : {
    "test" : "echo \"Error: no test specified\" && exit 1"
  },
  "author" : "" ,
  "license" : "ISC"
}

接下来,我们将创建我们的index.js文件。 在此文件中,我们将复制express “ Hello World”示例,但引入我们自己的模块(我们将以短期顺序创建此模块)。

const router = require ( './src/diy-router' );
const app = router();
const port = 3000 ;

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

app.listen(port, () => console .log( `Example app listening on port ${port} !` ));

这在本质上是一样的express “Hello World”的例子例子。 基于此代码,我们知道router模块应该是一个在调用时返回app对象的函数。 这个对象应该有一个listen方法来开始侦听端口上的请求,还有一个get方法来设置get请求处理。 我们还将设置一个post方法,因为我们最终希望我们的应用处理帖子。

搭建DIY路由器模块

现在,我们创建实际的路由器模块。 在新的src目录中创建diy-router.js文件。

mkdir src
cd src
touch diy-router.js

我们不想一次吃饱,所以我们首先创建一个导出必需方法的模块。

module .exports = ( ( ) => {
  const router = () => {
    const get = ( route, handler ) => {
      console .log( 'Get method called!' );
    };
    
    const listen = ( port, cb ) => {
      console .log( 'Listen method called!' );
    };

    return {
      get,
      listen
    };
  };

  return router;
})();

希望到目前为止,所有这些都有意义:我们创建了一个router函数,该函数在被调用时返回getlisten方法。 在这一点上,每个方法都忽略其参数,仅记录它已被调用。 然后将此函数包装在立即调用函数表达式(IIFE)中。 如果您不熟悉我们为什么使用IIFE,我们这样做是为了保护数据隐私。 当我们拥有不想在模块本身之外公开的变量和函数时,接下来的步骤将更加明显。

此时,我们可以返回到根目录并使用node运行我们的应用程序。

node .

如果一切顺利,您将看到类似以下的输出:

Get method called !
Listen method called !

完美,一切都连在一起! 现在,让我们开始响应http请求提供内容。

处理HTTP请求

为了获得一些基本的HTTP请求处理功能,我们将node的内置http模块引入到diy-routerhttp模块具有一个createServer方法,该方法带有一个带有请求和响应参数的函数。 每次将HTTP请求发送到listen方法中指定的端口时,都会执行此函数。 下面的示例代码显示了如何使用http模块在端口8080上返回文本“ Hello World”。

http.createServer( ( req, res ) => {
   res.write( 'Hello World!' );
   res.end();
}).listen( 8080 );

我们想在我们的模块中使用这种功能,但是我们需要让用户指定自己的端口。 此外,我们将要执行用户提供的回调函数。 让我们在diy-router模块的listen方法中使用此示例功能,并确保使用port和callback函数更加灵活。

const http = require ( 'http' );

module .exports = ( ( ) => {
  const router = () => {
    const get = ( route, handler ) => {
      console .log( 'Get method called!' );
    };
    
    const listen = ( port, cb ) => {
      http.createServer( ( req, res ) => {
        res.write( 'Hello World!' );
        res.end();
      }).listen(port, cb);
    };

    return {
      get,
      listen
    };
  };

  return router;
})();

让我们运行我们的应用程序,看看会发生什么。

node .

我们在控制台中看到以下内容:

Get method called!
Example app listening on  port  3000!

这是一个好兆头。 让我们打开我们最喜欢的Web浏览器并导航到http:// localhost:3000

(简单的“ Hello World!”应用程序)

看起来不错! 现在,我们正在通过端口3000提供内容。这很棒,但是我们仍然不提供与路由相关的内容。 例如,如果导航到http:// localhost:3000 / test-route,您将看到相同的“ Hello World!”。 信息。 在任何实际应用程序中,我们都希望我们为用户提供的内容取决于所提供URL中的内容。

添加和查找路线

我们需要能够向我们的应用程序添加任意数量的routes ,并在调用该路由时执行正确的路由处理程序功能。 为此,我们将一个routes数组添加到我们的模块中。 此外,我们将创建addRoutefindRoute函数。 名义上,代码可能看起来像这样:

let routes = [];

const addRoute = ( method, url, handler ) => {
  routes.push({ method, url, handler });
};

const findRoute = ( method, url ) => {
  return routes.find( route => route.method === method && route.url === url);
}

我们将使用getpost方法中的addRoute方法。 findRoute方法仅返回与提供的methodurl匹配的routes中的第一个元素。

在以下代码段中,我们添加了数组和两个函数。 另外,我们修改了get方法并添加了post方法,这两个方法都使用addRoute函数将用户指定的路由添加到routes数组。

注意:由于只能在模块内访问routes数组以及addRoutefindRoute方法,因此我们可以使用IIFE“显示模块”模式将它们暴露在模块外部。

const http = require ( 'http' );

module .exports = ( ( ) => {
  let routes = [];

  const addRoute = ( method, url, handler ) => {
    routes.push({ method, url, handler });
  };

  const findRoute = ( method, url ) => {
    return routes.find( route => route.method === method && route.url === url);
  }
  
  const router = () => {
    const get = ( route, handler ) => addRoute( 'get' , route, handler);
    const post = ( route, handler ) => addRoute( 'post' , route, handler);
    
    const listen = ( port, cb ) => {
      http.createServer( ( req, res ) => {
        res.write( 'Hello World!' );
        res.end();
      }).listen(port, cb);
    };

    return {
      get,
      post,
      listen
    };
  };

  return router;
})();

最后,让我们在传递给createServer方法的函数中使用findRoute函数。 成功找到路由后,我们应该调用与之关联的处理程序函数。 如果未找到该路由,则应返回404错误,指出未找到该路由。 该代码从概念上看起来将如下所示:

const method = req.method.toLowerCase();
const url = req.url.toLowerCase();
const found = findRoute(method, url);
if (found) {
  return found.handler(req, res);
}
res.writeHead( 404 , { 'Content-Type' : 'text/plain' });
res.end( 'Route not found.' );

现在,将其合并到我们的模块中。 在此过程中,我们将添加一小段代码来为响应对象创建send方法。

const http = require ( 'http' );

module .exports = ( ( ) => {
  let routes = [];

  const addRoute = ( method, url, handler ) => {
    routes.push({ method, url, handler });
  };

  const findRoute = ( method, url ) => {
    return routes.find( route => route.method === method && route.url === url);
  }
  
  const router = () => {
    const get = ( route, handler ) => addRoute( 'get' , route, handler);
    const post = ( route, handler ) => addRoute( 'post' , route, handler);
    
    const listen = ( port, cb ) => {
      http.createServer( ( req, res ) => {
        const method = req.method.toLowerCase();
        const url = req.url.toLowerCase();
        const found = findRoute(method, url);
        if (found) {
          res.send = content => {
            res.writeHead( 200 , { 'Content-Type' : 'text/plain' });
            res.end(content);
          };
          return found.handler(req, res);
        }
        
        res.writeHead( 404 , { 'Content-Type' : 'text/plain' });
        res.end( 'Route not found.' );
        
      }).listen(port, cb);
    };

    return {
      get,
      post,
      listen
    };
  };

  return router;
})();

让我们看看这个动作! 同样,从根目录运行您的应用程序。

node .

您应该看到该应用程序正在端口3000上提供。在浏览器中,导航到http:// localhost:3000 。 您应该看到“ Hello World!” 但是现在,如果导航到http:// localhost:3000 / test-route ,应该会收到“找不到路由”消息。 成功!

现在我们要确认我们可以在应用程序中实际添加/test-route作为路由。 在index.js ,设置此路由。

const router = require ( './src/diy-router' );
const app = router();
const port = 3000 ;

app.get( '/' , (req, res) => res.send( 'Hello World!' ));
app.get( '/test-route' , (req, res) => res.send( 'Testing testing' ));

app.listen(port, () => console .log( `Example app listening on port ${port} !` ));

重新启动服务器并导航到http:// localhost:3000 / test-route 。 如果看到“测试测试”,则说明您已成功设置路由!

注意:如果您有足够的乐趣,可以在这里结束! 这是路由的重要入门。 如果您想更深入一点并能够从我们的路线中提取参数,请继续阅读!

提取路线参数

在现实世界中,我们很可能在url字符串中包含参数。 例如,假设我们有一组用户,并希望基于url字符串中的参数来获取用户。 我们的url字符串可能最终类似于/user/:username ,其中username代表与用户关联的唯一标识。

要创建此功能,我们可以开发一些正则表达式规则以匹配任何url参数。 我不建议这样做,而是建议我们引入一个名为route-parser的出色模块来为我们完成此任务。 route-parser模块为具有match方法的每条路由创建一个新对象,并match了所有正则表达式魔术。要在我们的模块中进行所需的更改,请执行以下操作:

从命令行安装模块:

npm i route-parser

diy-router.js文件的顶部,需要该模块。

const Route = require( 'route-parser' );

addRoute函数中,而不是添加计划url字符串,而是添加Route类的新实例。

const addRoute = ( method, url, handler ) => {
  routes.push({ method, url : new Route(url), handler });
};

接下来,我们将更新findRoute函数。 在此更新中,我们使用Route对象的match方法将提供的url与路由字符串进行匹配。 换句话说,导航到/user/johndoe将匹配路由字符串/user/:username

如果找到匹配项,我们不仅要返回匹配项,还希望返回从URL中提取的参数。

const findRoute = ( method, url ) => {
  const route = routes.find( route => {
    return route.method === method && route.url.match(url);
  });
  if (!route) return null ;
  return { handler : route.handler, params : route.url.match(url) };
};

要处理此新功能,我们需要重新访问传递给http.createServer的函数中调用findRoutehttp.createServer 。 我们要确保路由中的所有参数都作为属性添加到请求对象上。

if (found) {
  req.params = found.params;
  res.send = content => {
    res.writeHead( 200 , { 'Content-Type' : 'text/plain' });
    res.end(content);
};

因此,我们的最终模块将如下所示:

const http = require ( 'http' );
const Route = require ( 'route-parser' );

module .exports = ( ( ) => {
  let routes = [];

  const addRoute = ( method, url, handler ) => {
    routes.push({ method, url : new Route(url), handler });
  };

  const findRoute = ( method, url ) => {
    const route = routes.find( route => {
      return route.method === method && route.url.match(url);
    });

    if (!route) return null ;

    return { handler : route.handler, params : route.url.match(url) };
  };

  const get = ( route, handler ) => addRoute( 'get' , route, handler);
  const post = ( route, handler ) => addRoute( 'post' , route, handler);

  const router = () => {
    const listen = ( port, cb ) => {
      http
        .createServer( ( req, res ) => {
          const method = req.method.toLowerCase();
          const url = req.url.toLowerCase();
          const found = findRoute(method, url);

          if (found) {
            req.params = found.params;
            res.send = content => {
              res.writeHead( 200 , { 'Content-Type' : 'text/plain' });
              res.end(content);
            };

            return found.handler(req, res);
          }

          res.writeHead( 404 , { 'Content-Type' : 'text/plain' });
          res.end( 'Route not found.' );
        })
        .listen(port, cb);
    };

    return {
      get,
      post,
      listen
    };
  };

  return router;
})();

让我们测试一下! 在index.js文件中,我们将添加一个新的用户终结点,并查看是否可以通过更改url查询字符串在用户之间进行切换。 如下更改您的index.js文件。 这将根据提供的请求的params属性过滤user数组。

const router = require ( './src/diy-router' );
const app = router();
const port = 3000 ;

app.get( '/' , (req, res) => res.send( 'Hello World!' ));
app.get( '/test-route' , (req, res) => res.send( 'Testing testing' ));
app.get( '/user/:username' , (req, res) => {
  const users = [
    { username : 'johndoe' , name : 'John Doe' },
    { username : 'janesmith' , name : 'Jane Smith' }
  ];

  const user = users.find( user => user.username === req.params.username);

  res.send( `Hello, ${user.name} !` );
});

app.listen(port, () => console .log( `Example app listening on port ${port} !` ));

现在,重新启动您的应用程序。

node .

首先导航到http:// localhost:3000 / user / johndoe ,观察内容,然后导航到http:// localhost:3000 / user / janesmith 。 您应该分别收到以下答复:

Hello, John Doe!
Hello, Jane Smith!

最终密码

该项目的最终代码可以在Github上找到 。 感谢您的编码!

结论

在本文中,我们观察到,尽管Express是令人难以置信的工具,但我们可以通过实现自己的自定义模块来复制其路由功能。 进行这种练习确实有助于拉回“窗帘”,并使您意识到实际上并没有任何“魔术”。 话虽如此,我绝对不建议为下一个Node项目滚动自己的框架!

像Express这样的框架之所以如此令人难以置信的原因之一是,它们得到了许多出色开发人员的广泛关注。 它们具有健壮的设计,并且比任何单个开发人员都可以部署的解决方案更高效,更安全。

那么您对这项练习有何看法? 这是否激发您尝试复制Express功能的其他方面? 在评论中让我知道!

From: https://hackernoon.com/understanding-express-js-creating-your-own-node-http-request-router-4190a9b6aad6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值