【问题标题】:ExpressJS Server - How to handle multiple domainsExpressJS 服务器 - 如何处理多个域
【发布时间】:2012-08-11 05:46:43
【问题描述】:

我在 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.headerhost 条目并解析域名。基于此,我调用 .static() 方法,以便 Express 可以提供正确的静态资源等,此外,我只是简单地读取并发送 index.html的内容> 文件。我也尝试使用 Jade 来提供纯 HTML 文件,但 Jade 中的 include 指令只接受相对路径。

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

欢迎任何建议/帮助。


更新

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

谢谢:-)

【问题讨论】:

  • +1,有趣的思维方式。
  • 为什么它们必须全部托管在同一个进程中?在 node.js 中这样做是不自然的。如果“baz”网站崩溃,您将关闭静态网站。
  • 它仍然是原创的,可能对特定用例有用。例如协调几个机器人。在同一个进程中多次运行同一个应用程序。可能很有趣。
  • 由于浏览器缓存,这不是一个很好的解决方案。如果您有两个同名文件 - 例如styles.css - 那么当你访问 bar 时 foo css 可能会从缓存中加载。我试过这将是一个 SPA 应用程序,这个问题也会影响你的 app.min.js。

标签: javascript node.js express


【解决方案1】:

瓦迪姆是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]
];

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

没有过滤器的相同堆栈

每个Application 只有一个中间件堆栈,您使用的所有中间件都直接添加到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( ? )
]

【讨论】:

  • 它看起来和我在那里手动做的基本一样?或者背后还有什么魔力。
  • @jAndy 它将确保static 中间件立即绑定并且只会响应列出的域。我可能是非常错误的......但是,我认为你有它,你不能在请求http://foo.com/之前请求http://foo.com/file。而且,我认为中间件不会保留它们所绑定的域关联,因此如果您首先访问 http://foo.com/http://bar.net/file 可能会以 http://foo.com/file 响应。
  • 我不认为我理解您的评论。中间件不必保留域关联,因为每个请求都包含域名。请求http://foo.com会正确产生Host: foo.com,连续请求http://bar.net/file会产生Host: bar.net
  • 我编辑了我的答案,试图详细说明我的评论。对不起,篇幅较长,但希望它有助于澄清我想说的话。
  • 不支持https。
【解决方案2】:

我喜欢使用 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);

【讨论】:

  • 基于@BinoCarlos 示例,对于那些好奇的人,我整理了一个非常简单的示例,用于快速轻松地添加和删除多个主机。它支持字符串匹配以及正则表达式,并允许您在不重新启动文件的情况下编辑文件(如果您使用类似 forever 的东西)检查一下:gist.github.com/Swivelgames/1cdf37c3317a4f82db89
【解决方案3】:

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

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

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

bouncy 的作者实际上建议它为one of the most important architectural patterns in designing a nodejs system。您可能还会注意到,他的回答已得到一些核心节点提交者的支持。

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 好吧,如果我想使用 nginx,我就不会问这个问题。无论如何感谢您的回复。但我真的很想弄清楚这些东西。 “nodeJS/Express 能做这件事吗?”、“性能如何?”、“那是正确的方式吗”、“等等”。
    • Connect/Express 有 vhost senchalabs.org/connect/vhost.html 中间件来玩域。但我仍然建议使用单独的服务器来提高应用程序性能。
    • 为什么 Express/Node 这么慢?
    • 所有代码(I/O 操作除外)都会阻塞事件循环(线程)。所以事件循环应该尽可能快地工作。它应该只包含必要的业务逻辑。更多代码 - 更慢的事件循环。
    • 我很清楚 ecmascript 的工作原理。不过,这段代码中没有繁重的计算,它应该很快就会减轻。
    【解决方案5】:

    由于 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 服务器前面放置一个异步反向代理(例如 Nginx),并将代理指向您的 Express 服务器。

    【讨论】:

      【解决方案6】:

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

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

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

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

      【讨论】:

      • 当然,也许你可以在没有 express 的情况下做更高级的事情,但它更快。这就像说你不会使用 jquery 而只使用 vanilla javascript。命令更短,编写代码更高效。
      【解决方案7】:

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

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

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

      简而言之,不要考虑做大,要考虑做大。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-22
        • 2011-10-21
        • 2011-10-16
        • 1970-01-01
        • 1970-01-01
        • 2010-09-18
        • 2015-03-20
        • 1970-01-01
        相关资源
        最近更新 更多