发布于 ,更新于 

Node.js 实现同端口监听HTTP与HTTPS

之前一直在考虑一个简单的小优化。
同事们在本地启动 Node.js HTTPS 服务后,然后在浏览器里面访问服务的页面时,总是忘了先写协议名https,看到浏览器的出错提示时,才恍然大悟。

我就想实现一个功能:只监听一个端口,实现 HTTPS 与 HTTP 请求的监听,且自动将 HTTP 请求 Redirect 到 HTTPS

stackoverflow找到了对应的方法:https://stackoverflow.com/a/42019773

实现代码

纯 Node.js 原生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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 联调

我们项目使用的 Koa,所以联调上方的基础方法:

1
2
3
4
5
6
7
8
9
10
11
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.
});