ExpressJS服务器 - 如何处理多个域

时间:2012-08-14 21:04:23

标签: javascript node.js express

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

让我们假设我们有

  • foo.com
  • bar.net
  • 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 <的内容/ em>文件。我尝试使用 Jade 以及提供普通的 HTML文件,但 Jade 中的include指令只接受相对路径。

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

任何建议/帮助欢迎。


更新

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

谢谢: - )

7 个答案:

答案 0 :(得分:28)

瓦迪姆是almost onto the right idea。您可以使用vhost middleware

配置如何回复每个域
// `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]
];

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

没有过滤器的相同堆栈

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

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

条件不会改变static中间件响应的方式 - 这是req.path,而不是req.host - 只有当它们在堆栈中才开始响应时。

筹码状态

而且,如果在{em>之后 之后没有添加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( ? )
]

答案 1 :(得分:11)

我喜欢使用bouncy作为前端反向代理 - 这使您可以运行完全不同的快速堆栈作为不同的服务器进程(每个具有不同的功能并且为了健壮性而分开)...

然后,您可以决定如何路由到不同的端口,它可以正常使用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);

答案 2 :(得分:4)

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

答案 3 :(得分:3)

如果不知道约束意味着你必须在同一个进程中运行主机,那么很难回答这个问题,所以我会回应其他人所说的内容,但希望能提供更多的上下文。

与节点“最正确”的做法是在多个进程中运行主机并在另一个进程中反向代理请求。在同一个进程中运行多个站点时会出现问题,尤其是一个站点崩溃,将它们全部关闭并需要重新启动整个过程。 Node的理念非常类似于unix,它鼓励将程序保持小而且分离。如果您分离流程,您将获得应用程序的自然隔离。如果您采用单片设计,则必须编写和维护逻辑以将记录与不同站点分开,并且错误处理逻辑将变得更加复杂。毫无疑问,在其他情况下,您需要根据主机分支逻辑,但您的应用程序设计将鼓励而不是劝阻。

如果你不喜欢其他技术堆栈(或者例如nginx目前缺乏对stable分支中websockets的支持),那么在nodejs中有一些固态反向代理,包括nodejitsu的http-proxy用于Joyent云端的生产PaaS堆栈和bouncy(在另一个答案中提到)功能较少,但我相信它正在浏览器中用于生产。

有弹性的作者实际上认为它是one of the most important architectural patterns in designing a nodejs system。您可能还注意到他的答案已被某些核心节点提交者所支持。

答案 4 :(得分:2)

在某种程度上违背流程我不得不说我不知道​​做这样的事情是否有意义。 Node.js有一个进程设计约束。限制IO对于一个Web应用程序来说很难,更不用说一些了。尝试通过使用多个应用程序来抽象它会使代码复杂化使其无法读取。单个应用程序中的错误可能会影响所有应用程序。这是一种非常不稳定的配置。

如果你想看看能否做到,我想答案是肯定的。像vhost之类的东西在另一个答案中建议here。另一方面,我可能会采取某种关注点分离并约束我的应用程序。如果要将它放在同一个服务器盒中,我会执行以下操作:

  1. 可用核心数-1将是我将绑定到单个服务器的域数。
  2. 每个核心都将保存一个node.js进程。它将实现覆盖单个网站的单个Web应用程序。
  3. 备用核心将拥有某种“路由器”,可以是nginx解决方案,也可以是另一个node.js / express应用程序,用于路由数据。
  4. 简而言之,不要考虑变大,想想走得更远。

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

答案 5 :(得分:2)

由于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。如果这还不够,那么目前的做法是将一个asnyc反向代理(如Nginx)放在Express服务器前面,并将代理指向Express服务器。

答案 6 :(得分:2)

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

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

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

总的来说,除非提供其他详细信息,否则答案并不简单。 我提供一个单独的答案,而不是发表评论,因为我相信太多的开发人员使用快速表达方式,因此限制了它们的灵活性。