Node.js 实现同端口监听HTTP与HTTPS
之前一直在考虑一个简单的小优化。
同事们在本地启动 Node.js HTTPS 服务后,然后在浏览器里面访问服务的页面时,总是忘了先写协议名https
,看到浏览器的出错提示时,才恍然大悟。
我就想实现一个功能:只监听一个端口,实现 HTTPS 与 HTTP 请求的监听,且自动将 HTTP 请求 Redirect 到 HTTPS。
在stackoverflow找到了对应的方法:https://stackoverflow.com/a/42019773
实现代码:
纯 Node.js 原生
const path = require('path');
const fs = require('fs');
const http = require('http');
const https = require('https');
const net = require('net');
/**
* Enable request handler being in HTTPS mode.
* @param handler {RequestListener}
* @param httpsConfig {{keyPath: string, certPath: string, [forceHttpsWhenEnabled]: boolean}}
* @return {Server & { https: Server, http: Server } }
*/
function enableHandlerHttps(handler, httpsConfig) {
const options = {
key: fs.readFileSync(path.resolve(httpsConfig.keyPath)),
cert: fs.readFileSync(path.resolve(httpsConfig.certPath))
};
// If no need to forcibly redirect HTTP requests to same path HTTPS route.
if (!httpsConfig.forceHttpsWhenEnabled) {
// OK. Normal flow.
return https.createServer(options, handler);
}
/**
* Automatically redirect http request to https.
* Only change the protocol.
* @see https://stackoverflow.com/a/42019773
*/
const net = require('net');
const server = net.createServer(conn => {
conn.once('data', buffer => {
// Pause the socket.
conn.pause();
const firstByte = buffer[0];
const httpReqFirstByteRange = [32, 127];
const httpsReqFirstByte = 22;
// Determine what proxy we need to use.
let protocol;
if (firstByte === httpsReqFirstByte) {
protocol = 'https';
} else if (
httpReqFirstByteRange[0] < firstByte &&
firstByte < httpReqFirstByteRange[1]
) {
protocol = 'http';
}
const proxy = server[protocol];
if (proxy) {
// Push the buffer back onto the front of the data stream.
conn.unshift(buffer);
// Emit the socket to the HTTP(s) server.
proxy.emit('connection', conn);
}
// 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(() => {
conn.resume();
});
});
});
// HTTP server proxy.
server.http = http.createServer((req, res) => {
// Force redirect.
const host = req.headers['host'];
// Use 301 - Moved Permanently.
// To notify browsers that update the bookmarks and cache the redirection.
res.writeHead(301, { Location: 'https://' + host + req.url });
res.end();
});
// HTTPS server proxy.
server.https = https.createServer(options, handler);
return server;
}
我们项目使用的 Koa,所以联调上方的基础方法:
const app = new Koa();
const server = enableHandlerHttps(app.callback(), {
keyPath: '', // HTTPS cert key path.
certPath: '', // HTTPS cert file path.
forceHttpsWhenEnabled: true // Enable.
});
server.listen(3000, function () {
// Server already started.
});