Nodejs在同一端口上使用HTTP和HTTPS协议

50
我一直在谷歌和stackoverflow上搜索,但是我找不到喜欢的答案 ;-)
我有一个运行在HTTPS和3001端口上的NodeJS服务器。现在我想获取所有传入的HTTP请求并将它们重定向到相同的URL,但是使用HTTPS。
这应该是可能的。对吧?
谢谢!

1
@ScottGress 那个问题/答案真的很老了。我相信这里的发帖者想要一个更新的回复。 - basarat
4个回答

93

遵循惯例,您不需要在同一端口上进行侦听

按照惯例,当您请求http://127.0.0.1时,浏览器会尝试连接到80端口。如果您尝试打开https://127.0.0.1,您的浏览器将尝试连接到443端口。因此,为了安全地处理所有流量,只需在http上侦听端口80,并使用重定向到已经监听443端口的https。这是代码:

var https = require('https');

var fs = require('fs');
var options = {
    key: fs.readFileSync('./key.pem'),
    cert: fs.readFileSync('./cert.pem')
};

https.createServer(options, function (req, res) {
    res.end('secure!');
}).listen(443);

// Redirect from http port 80 to https
var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
    res.end();
}).listen(80);

使用 https 进行测试:

$ curl https://127.0.0.1 -k
secure!

使用 http:

$ curl http://127.0.0.1 -i
HTTP/1.1 301 Moved Permanently
Location: https://127.0.0.1/
Date: Sun, 01 Jun 2014 06:15:16 GMT
Connection: keep-alive
Transfer-Encoding: chunked

如果必须监听同一端口

没有简单的方法可以让http/https监听同一端口。你最好的选择是创建一个代理服务器,它在一个简单的网络套接字上运行,根据传入连接的性质(http vs. https),将其导向(http或https)。

这里是完整的代码(基于https://gist.github.com/bnoordhuis/4740141),它可以实现这一点。它监听localhost:3000并将其流式传输到http(然后将其重定向到https),或者如果传入的连接是https,则直接将其传递给https处理程序。

var fs = require('fs');
var net = require('net');
var http = require('http');
var https = require('https');

var baseAddress = 3000;
var redirectAddress = 3001;
var httpsAddress = 3002;
var httpsOptions = {
    key: fs.readFileSync('./key.pem'),
    cert: fs.readFileSync('./cert.pem')
};

net.createServer(tcpConnection).listen(baseAddress);
http.createServer(httpConnection).listen(redirectAddress);
https.createServer(httpsOptions, httpsConnection).listen(httpsAddress);

function tcpConnection(conn) {
    conn.once('data', function (buf) {
        // A TLS handshake record starts with byte 22.
        var address = (buf[0] === 22) ? httpsAddress : redirectAddress;
        var proxy = net.createConnection(address, function () {
            proxy.write(buf);
            conn.pipe(proxy).pipe(conn);
        });
    });
}

function httpConnection(req, res) {
    var host = req.headers['host'];
    res.writeHead(301, { "Location": "https://" + host + req.url });
    res.end();
}

function httpsConnection(req, res) {
    res.writeHead(200, { 'Content-Length': '5' });
    res.end('HTTPS');
}

作为测试,如果您使用https连接,则会得到https处理程序:

$ curl https://127.0.0.1:3000 -k
HTTPS

如果您将其与http连接,您将获得重定向处理程序(它只会带您到https处理程序):

$ curl http://127.0.0.1:3000 -i
HTTP/1.1 301 Moved Permanently
Location: https://127.0.0.1:3000/
Date: Sat, 31 May 2014 16:36:56 GMT
Connection: keep-alive
Transfer-Encoding: chunked

HTTPS重定向时连接被拒绝。有什么想法吗? - Mathew Kurian
这对于在非标准端口上进行HTTPS重定向真的非常有用,谢谢! - Heath Mitchell
这个程序无法处理大请求 - 请尝试使用几百KB的数据,只有第一个“块”可以通过,其余部分无法正确传输。 - Dávid Molnár

36
如果要在单个端口上提供HTTP和HTTPS服务,您可以直接代理请求到相关的HTTP实现,而不是将套接字传输到另一个端口。
httpx.js

'use strict';
let net = require('net');
let http = require('http');
let https = require('https');

exports.createServer = (opts, handler) => {

    let server = net.createServer(socket => {
        socket.once('data', buffer => {
            // Pause the socket
            socket.pause();

            // Determine if this is an HTTP(s) request
            let byte = buffer[0];

            let protocol;
            if (byte === 22) {
                protocol = 'https';
            } else if (32 < byte && byte < 127) {
                protocol = 'http';
            }

            let proxy = server[protocol];
            if (proxy) {
                // Push the buffer back onto the front of the data stream
                socket.unshift(buffer);

                // Emit the socket to the HTTP(s) server
                proxy.emit('connection', socket);
            }
            
            // As of NodeJS 10.x the socket must be 
            // resumed asynchronously or the socket
            // connection hangs, potentially crashing
            // the process. Prior to NodeJS 10.x
            // the socket may be resumed synchronously.
            process.nextTick(() => socket.resume()); 
        });
    });

    server.http = http.createServer(handler);
    server.https = https.createServer(opts, handler);
    return server;
};

example.js

'use strict';
let express = require('express');
let fs = require('fs');
let io =  require('socket.io');

let httpx = require('./httpx');

let opts = {
    key: fs.readFileSync('./server.key'),
    cert: fs.readFileSync('./server.cert')
};

let app = express();
app.use(express.static('public'));

let server = httpx.createServer(opts, app);
let ws = io(server.http);
let wss = io(server.https);
server.listen(8080, () => console.log('Server started'));


到目前为止,这是最好的答案。真的很有帮助。比为一个应用程序服务多个端口要好得多。 - Dan
这真的可行。这比以上某个答案中使用.once('data')更好。这样做可以处理更大的请求。 - Dávid Molnár

18

我知道这是一个老问题,但我将其作为其他人的参考。 我发现最简单的方法是使用 https://github.com/mscdex/httpolyglot 模块。看起来可以可靠地实现它所说的功能。

    var httpolyglot = require('httpolyglot');
    var server = httpolyglot.createServer(options,function(req,res) {
      if (!req.socket.encrypted) {
      // Redirect to https
        res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
        res.end();
      } else {
        // The express app or any other compatible app 
        app.apply(app,arguments);
      }
  });
 // Some port
 server.listen(11000);

-1
如果是纯Node.JS HTTP模块,你可以尝试这个:
if (!request.connection.encrypted) { // Check if the request is not HTTPS
    response.writeHead(301, { // May be 302
        Location: 'https://' + YourHostName + ':3001' + request.url
        /* Here you can add some more headers */
    });

    response.end(); // End the response
} else {
    // Behavior for HTTPS requests
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接