一台 VPS 主机运行多个网站,多个 HTTPS 域名(基于 nodejs)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangxin09/article/details/80300481

四年前写过一篇《用 nodejs 做反向代理服务器》,那时基于 HTTP 的,时过境迁,HTTPS 已是主流。怎么把 HTTP 升级到 SSL 呢?这里为大家稍作介绍一下,作法稍有不同。

支持 SSL

首先 nodejs 支持 HTTPS 很简单,只需要把 require(‘http’) 变为 require(‘https’),然后导入证书文件即可,当然也要把监听端口 80 变为 443。
##支持多个 SSL
一个 SSL 证书毫无问题,如果多个呢?因为多个域名是对应不同的 SSL 证书。node 支持 SNI(Server Name Indication),自带 API 就支持——它就是专门满足这个功能需求的。

SNI (Server Name Indication)是用来改善服务器与客户端 SSL (Secure Socket Layer)和 TLS (Transport Layer Security) 的一个扩展。主要解决一台服务器只能使用一个证书(一个域名)的缺点,随着服务器对虚拟主机的支持,一个服务器上可以为多个域名提供服务,因此SNI必须得到支持才能满足需求。
##完整例子
例子如下。需要注意,后端 tomcat 没有 HTTPS,不能像以前 301 跳转。转为,每个网站一个 tomcat,用不同端口划分(如 81、82、83)。这样服务器资源的吃得比较多,毕竟不是一个 tomcat 挂多个网站。

const http = require('https'),
    httpProxy = require('http-proxy'),
    fs = require('fs'),
    tls = require('tls');

// 新建一个代理 Proxy Server 对象  
var proxy = httpProxy.createProxyServer({});

// 捕获异常  
proxy.on('error', function(err, req, res) {
    res.writeHead(500, {
        'Content-Type': 'text/plain'
    });
    res.end('Something went wrong. And we are reporting a custom error message.');
});

const secureContext = {
        'framework.ajaxjs.com': tls.createSecureContext({
                key: fs.readFileSync('./ssl/2_framework.ajaxjs.com.key', 'utf8'),
                cert: fs.readFileSync('./ssl/1_framework.ajaxjs.com_bundle.crt', 'utf8')
            }),
        'myotherdomain.com': tls.createSecureContext({
            key: fs.readFileSync('../path_to_key2.pem', 'utf8'),
            cert: fs.readFileSync('../path_to_cert2.crt', 'utf8'),
            ca: fs.readFileSync('../path_to_certificate_authority_bundle.ca-bundle2', 'utf8'), // this ca property is optional 中间证书
        }),
    },
    options = {
        SNICallback: function(domain, cb) {
            if (secureContext[domain]) {
                if (cb) {
                    cb(null, secureContext[domain]);
                } else {
                    // compatibility for older versions of node
                    return secureContext[domain];
                }
            } else {
                throw new Error('No keys/certificates for domain requested');
            }
        },
        key: fs.readFileSync('./ssl/2_www.ajaxjs.com.key'),
        cert: fs.readFileSync('./ssl/1_www.ajaxjs.com_bundle.crt')
    };

// 另外新建一个 HTTP 80 端口的服务器,也就是常规 Node 创建 HTTP 服务器的方法。  
// 在每次请求中,调用 proxy.web(req, res config) 方法进行请求分发  
var server = require('https').createServer(options, function(req, res) {
    // 在这里可以自定义你的路由分发  
    var host = req.headers.host,
        ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    console.log("client ip:" + ip + ", host:" + host);

    switch (host) {
        // case 'framework.ajaxjs.com':
        case 'bbs.aaaa.com':
            res.writeHead(301, { 'Location': 'http://framework.ajaxjs.com/framework/' });
            console.log(res._header);
            res.end('hihi');
            break;
        case 'framework.ajaxjs.com':
            proxy.web(req, res, { target: 'http://127.0.0.1:81' });
            break;
        case 'ajaxjs.com':
        case 'www.ajaxjs.com':
            proxy.web(req, res, { target: 'http://127.0.0.1:82' });
            break;
        case 'weixintest.ajaxjs.com':
            proxy.web(req, res, { target: 'http://127.0.0.1:83' });
            break;
        default:
            // res.writeHead(302, { 'Location': 'https://www.baidu.com/' });
            // res.end();
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end('Welcome to my server!');
    }
});

var port = 443;
console.log("listening on port: " + port);
server.listen(port);

非 HTTP 跳转

80 端口的话,就全部跳转到 443,这样需要多一个 nodejs 监听 80 端口,只做跳转。

// 80 forward to 443
var server = require('http').createServer(function(req, res) {
    var host = req.headers.host;
    switch (host) {
        case 'ajaxjs.com':
        case 'www.ajaxjs.com':
            res.writeHead(301, { 'Location': 'https://www.ajaxjs.com/' });
            res.end();
            break;
        case 'framework.ajaxjs.com':
            res.writeHead(301, { 'Location': 'https://framework.ajaxjs.com/' });
            res.end();
            break;
        default:
            // res.writeHead(302, { 'Location': 'https://www.baidu.com/' });
            // res.end();
            res.writeHead(200, {
                'Content-Type': 'text/plain'
            });
            res.end('Welcome to my server!');
    }
});

var port = 80;
console.log("listening on port: " + port);
server.listen(port);

Ref:https://stackoverflow.com/questions/12219639/is-it-possible-to-dynamically-return-an-ssl-certificate-in-nodejs#answer-20285934

19–1-10
最近发现小程序接口,iOS 访问正常,安卓却出现 request:fail ssl hand shake error。这是缺少 中间证书 的缘故。下面是测试是否有中间证书的服务。
https://www.myssl.cn/tools/check-server-cert.html
如果没有,要将 1_root_bundle.crt 引入到 ca 项(CertificateChain)。如果有多个域名证书那么全部都要加上。

展开阅读全文

没有更多推荐了,返回首页