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

四年前写过一篇《用 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。这是缺少 中间证书 的缘故。下面是测试是否有中间证书的服务。如果没有,要将 1_root_bundle.crt 引入到 ca 项(CertificateChain)。如果有多个域名证书那么全部都要加上。

©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页