31

我在 Express 上玩弄了一下,我想知道,“最正确”的方法是处理链接到同一服务器的多个域。

假设我们有

  • foo.com
  • 酒吧网
  • baz.com

都指向111.222.333.444. 那台机器正在运行带有 Express 的 NodeJS。我目前的解决方案如下所示:

var express = require( 'express' ),
    app     = module.exports = express.createServer(),
// ... more lines ...
app.get( '/', routes.index.bind( app ) );

到目前为止,这非常简单。到目前为止,唯一的例外是在我的app.configure通话中,我没有拨打.use( express.static() ). 那是因为该.routes.index()方法现在看起来像这样:

var fs    = require( 'fs' ),
// ... more lines ...

exports.index = function( req, res ) {
    var host = /(\w+\.)?(.*)\.\w+/.exec( req.header( 'host' ) ),
        app  = this;

    switch( host[ 2 ] ) {
        case 'foo':
            app.use( express.static( '/var/www/foo' ) );
            fs.readFile( '/var/www/foo/index.html', 'utf8', fileReadFoo );
            break;
        case 'bar':
            app.use( express.static( '/var/www/bar' ) );
            fs.readFile( '/var/www/bar/index.html', 'utf8', fileReadBar );
            break;
        case 'baz':
            // ... lines ...
            res.render( 'index', { title: 'Baz Title example' } );
            break;
        default:
            res.send('Sorry, I do not know how to handle that domain.');
    }

    function fileReadFoo( err, text ) {
        res.send( text );
    }

    function fileReadBar( err, text ) {
        res.send( text );
    }
};

这里发生的是,我分析req.header条目host并解析域名。基于此,我调用该.static()方法,以便Express可以提供正确的静态资源等,此外,我只是简单地读取并发送index.html文件的内容。我也尝试使用Jade来提供纯HTML 文件,但Jadeinclude中的指令只接受相对路径。

但是,这确实有效,但我不确定这是否是一个好习惯。

欢迎任何建议/帮助。


更新

我想我需要更清楚地说明这一点。无论如何,我都不是初学者。我非常了解 ES 和 NGINX 等其他服务器的工作原理。我正在寻找关于 NodeJS/Express 的正确之处的合格答案。如果为此使用 Node/Express 没有任何意义,请详细说明。如果使用 Node/Express 有更好的方法,请解释一下。

谢谢 :-)

4

7 回答 7

39

Vadim的想法几乎是正确的vhost您可以配置如何使用中间件响应每个域:

// `baz.com`
var app = express.createServer();
app.get( '/', routes.index );

// ...

express.createServer()
    .use( express.vhost( 'foo.com', express.static( '/var/www/foo' ) ) )
    .use( express.vhost( 'bar.net', express.static( '/var/www/bar' ) ) )
    .use( express.vhost( 'baz.com', app ) )
    .use( function( req, res ) {
        res.send('Sorry, I do not know how to handle that domain.');
    })
    .listen( ... );

routes.index然后可以简化为仅处理baz.com请求:

exports.index = function( req, res ) {
    // ... lines ...
    res.render( 'index', { title: 'Baz Title example' } );
};

编辑

至于对比:

switch将首先有效地完成,并将确定如何基于host- 处理所有请求,类似于:

express.createServer().use(function( req, res, next ) {
    switch( req.host ) {
        case 'foo.com': express.static( '/var/www/foo' )( req, res, next ); break;
        case 'bar.net': express.static( '/var/www/bar' )( req, res, next ); break;
        case 'baz.com': app.handle( req, res, next ); break;
        default: res.send( ... );
    }
}).listen( ... );

它允许您在启动时设置堆栈,以便任何中间件立即可用:

server.stack = [
    express.vhost( 'foo.com', ... ),
    express.vhost( 'bar.net', ... ),
    express.vhost( 'baz.com', ... ),
    [Function]
];

这些也反映了您可能遇到的 2 个可能的问题来源:

没有过滤器的相同堆栈

每个Application只有 1 个中间件堆栈,您使用的所有中间件都直接添加到app.use(...). 尽管添加了一些条件,你仍然得到:

app.stack = [
    // ...,
    app.router,
    express.static( '/var/www/foo' ),
    express.static( '/var/www/bar' )
];

并且条件不会改变static中间件的响应方式——即通过req.path,而不是req.host——只有当它们在堆栈中开始响应时。

堆栈状态

而且,如果在发出static另一个请求之前没有添加中间件,那么我认为它们不会立即可用:

// GET http://foo.com/file 404
app.stack = [ app.router ]

// GET http://foo.com/ 200
app.stack = [ app.router, express.static( '/var/www/foo' ) ]

// GET http://foo.com/file 200
app.stack = [ app.router, express.static( '/var/www/foo' ) ]

这也可能意味着相同的static中间件可以多次添加到堆栈中:

// 3x GET http://foo.com/
app.stack = [
    app.router,
    express.static( '/var/www/foo' ),
    express.static( '/var/www/foo' ),
    express.static( '/var/www/foo' )
]

并且它们的添加取决于其他请求也表明可能存在竞争条件:

// was `foo.com` or `bar.net` first?
app.stack = [
    app.router,
    express.static( ? ),
    express.static( ? )
]
于 2012-08-14T22:43:59.657 回答
18

我喜欢使用bouncy作为前端反向代理——这让你可以将完全不同的 express 堆栈作为不同的服务器进程运行(每个都有不同的特性并且为了健壮性而分开)......

然后您可以决定如何路由到不同的端口,它适用于 WebSockets。

var bouncy = require('bouncy');

bouncy(function (req, bounce) {
    if (req.headers.host === 'bouncy.example.com') {
        bounce(8000);
    }
    else if (req.headers.host === 'trampoline.example.com') {
        bounce(8001)
    }
}).listen(80);
于 2012-08-14T22:55:57.650 回答
4

如果不知道意味着您必须在同一进程中运行主机的限制,很难回答这个问题,所以我会回应其他人所说的话,但希望能提供更多背景信息。

与 node 做的“最正确”的事情是在多个进程中运行主机并在另一个进程中反向代理请求。在同一个进程中运行多个站点充满了问题,其中最重要的是一个崩溃,使它们全部崩溃并需要重新启动整个过程。Node 的理念非常类似于 unix,它鼓励将程序保持小而独立。如果您将流程分开,您将获得应用程序的自然隔离。如果您追求单体设计,则必须编写和维护逻辑以将日志记录与不同站点分开,并且错误处理逻辑将变得更加复杂。毫无疑问,在其他情况下,您需要基于主机分支逻辑,但您的应用程序设计将鼓励而不是阻止这种情况。

如果你不喜欢其他技术栈(或者担心 nginx 目前在稳定分支中缺乏对 websockets 的支持),那么有一些用 nodejs 编写的可靠反向代理,包括 nodejitsu 的http-proxy,它在他们的Joyent 的云和弹性(在另一个答案中提到)上的生产 PaaS 堆栈具有较少的功能,但我相信正在浏览器的生产中使用。

bouncy 的作者实际上甚至暗示它是设计 nodejs 系统中最重要的架构模式之一。您可能还会注意到,他的回答已得到一些核心节点提交者的支持。

于 2012-08-18T19:42:00.630 回答
3

我使用nginx作为 node.js 的前端服务器。它是组织域、静态内容交付、负载控制和许多其他强大功能的最佳解决方案。绝对不需要在节点事件循环中这样做。这将决定您的应用程序的速度。

于 2012-08-14T21:23:37.683 回答
3

由于 Express 使用Connect,我很确定您可以使用 Connect 的虚拟主机中间件。它的操作类似于其他产品上的其他 vhost 模块。我没有多个域来测试并向您展示正确的代码,但我认为它是这样的:

express.createServer()
.use(express.vhost('hostname1.com', require('/path/to/hostname1').app)
.use(express.vhost('hostname2.com', require('/path/to/hostname2').app)
.listen(80)

如果您发现一台 Express 服务器不够用,请考虑使用 API 中的 Node.Cluster。如果这还不够,那么当前的做法是在您的 Express 服务器前面放置一个 asnyc 反向代理(例如 Nginx),并将代理指向您的 Express 服务器。

于 2012-08-18T19:05:22.197 回答
3

我不建议首先使用 Express。事实上,即使对于单个域,使用 Node 的 http 模块,也可以轻松编写一个具有 Express 所具有的所有功能的应用程序。

对于多个域,一旦您获得主机名,您就可以自定义您的路由或中间件或您想调用的任何名称。只要您在任何应用程序中都没有任何阻塞调用,您的单节点进程将服务的域数量是无关紧要的。如果 CPU 使用率成为瓶颈,您可以使用同一进程的集群。

如果您有内存中的会话数据,则无法进行集群。您将需要另一个进程来维护管理该特定会话的实例的状态。这一切都取决于您如何管理状态、状态的持久性等。

总的来说,除非提供其他细节,否则答案并不简单。我提供了一个单独的答案,而不是评论,因为我相信太多的开发人员使用 Express,从而限制了他们的灵活性。

于 2018-08-18T20:38:13.223 回答
2

有点逆流而上,我不得不说我不明白做这样的事情有什么意义。Node.js 有一个单一的流程设计约束。对于一个 Web 应用程序来说,限制 IO 是一项艰巨的工作,更不用说少数几个了。试图通过拥有多个应用程序来抽象它会使代码过于复杂,使其不可读。单个应用程序中的错误可能会影响所有应用程序。这是一个非常不稳定的配置。

如果你想看看你能不能做到,我想答案是肯定的。像 vhost 这样的东西在这里建议在另一个答案中。另一方面,我可能会采用某种关注点分离并限制我的应用程序。如果要将其放置在同一个服务器框中,我将执行以下操作:

  1. 可用核心数 -1 将是我将绑定到单个服务器的域数。
  2. 每个核心都将拥有一个绑定到它的 node.js 进程。它将实现覆盖单个网站的单个 Web 应用程序。
  3. 备用核心将包含某种“路由器”,要么是 nginx 解决方案,要么是另一个路由数据的 node.js/express 应用程序。

简而言之,不要想着做大,想着做大。

缺点:这是一种不同的缩放方式。你只能从一个盒子里取出这么多果汁。当我们谈论“多盒”环境时,我不确定如何扩展这个想法。

于 2012-08-18T00:10:59.757 回答