【发布时间】:2020-10-31 13:11:32
【问题描述】:
我想使用 Node.js 的 http2 库通过代理向服务器发送 HTTP/2 请求。
出于测试目的,我使用 Charles v4.2.7 作为代理,但 Charles 无法代理请求。 Charles 显示Malformed request URL "*" 错误,因为它收到的请求是PRI * HTTP/2.0(HTTP/2 Connection Preface)。我可以使用 cURL(例如curl --http2 -x localhost:8888 https://cypher.codes)通过我的 Charles 代理成功发送 HTTP/2 请求,所以我认为这不是 Charles 的问题,而是我的 Node.js 实现的问题。
这是我的 Node.js HTTP/2 客户端实现,它尝试通过我在 http://localhost:8888 上监听的 Charles 代理向 https://cypher.codes 发送 GET 请求:
const http2 = require('http2');
const client = http2.connect('http://localhost:8888');
client.on('error', (err) => console.error(err));
const req = client.request({
':scheme': 'https',
':method': 'GET',
':authority': 'cypher.codes',
':path': '/',
});
req.on('response', (headers, flags) => {
for (const name in headers) {
console.log(`${name}: ${headers[name]}`);
}
});
req.setEncoding('utf8');
let data = '';
req.on('data', (chunk) => { data += chunk; });
req.on('end', () => {
console.log(`\n${data}`);
client.close();
});
req.end();
这是我在运行node proxy.js 时遇到的Node.js 错误(proxy.js 是包含上述代码的文件):
events.js:200
throw er; // Unhandled 'error' event
^
Error [ERR_HTTP2_ERROR]: Protocol error
at Http2Session.onSessionInternalError (internal/http2/core.js:746:26)
Emitted 'error' event on ClientHttp2Stream instance at:
at emitErrorNT (internal/streams/destroy.js:92:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
at processTicksAndRejections (internal/process/task_queues.js:81:21) {
code: 'ERR_HTTP2_ERROR',
errno: -505
}
我使用详细输出重新运行了上述 cURL 请求,看起来 cURL 首先使用 HTTP/1 向代理发送 CONNECT,然后使用 HTTP/2 发送 GET 请求。
$ curl -v --http2 -x localhost:8888 https://cypher.codes
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8888 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to cypher.codes:443
> CONNECT cypher.codes:443 HTTP/1.1
> Host: cypher.codes:443
> User-Agent: curl/7.64.1
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 Connection established
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* CONNECT phase completed!
* CONNECT phase completed!
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=cypher.codes
* start date: Jun 21 04:38:35 2020 GMT
* expire date: Sep 19 04:38:35 2020 GMT
* subjectAltName: host "cypher.codes" matched cert's "cypher.codes"
* issuer: CN=Charles Proxy CA (8 Oct 2018, mcypher-mbp.local); OU=https://charlesproxy.com/ssl; O=XK72 Ltd; L=Auckland; ST=Auckland; C=NZ
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7ff50d00d600)
> GET / HTTP/2
> Host: cypher.codes
> User-Agent: curl/7.64.1
> Accept: */*
>
...
我想尝试通过 Node.js 做同样的事情(首先发送一个 HTTP/1 CONNECT 请求,然后在同一个 TCP 连接上发送我的 HTTP/2 请求),但我不知道该怎么做.创建 HTTP/2 客户端(即http2.connect('http://localhost:8888');)的行为会发送 HTTP/2 连接前言。我考虑过首先使用 HTTP/1 创建连接(例如使用 http 库),然后将其升级到 HTTP/2,但我找不到任何有关如何执行此操作的示例。
有人可以帮我使用 Node.js 通过代理发送 HTTP/2 请求吗?
更新(2020-07-13):我在首先使用 HTTP/1 创建连接、发送 CONNECT 请求,然后尝试使用 HTTP/2 发送 GET 请求方面取得了更多进展在同一个插座上。我可以看到在 Charles 中通过了 CONNECT 请求,但没有看到额外的 GET 请求,这表明我在尝试对 HTTP/2 请求使用相同的套接字时仍然做错了。这是我更新的代码:
const http = require('http');
const http2 = require('http2');
const options = {
hostname: 'localhost',
port: 8888,
method: 'CONNECT',
path: 'cypher.codes:80',
headers: {
Host: 'cypher.codes:80',
'Proxy-Connection': 'Keep-Alive',
'Connection': 'Keep-Alive',
},
};
const connReq = http.request(options);
connReq.end();
connReq.on('connect', (_, socket) => {
const client = http2.connect('https://cypher.codes', {
createConnection: () => { return socket },
});
client.on('connect', () => console.log('http2 client connect success'));
client.on('error', (err) => console.error(`http2 client connect error: ${err}`));
const req = client.request({
':path': '/',
});
req.setEncoding('utf8');
req.on('response', (headers, flags) => {
let data = '';
req.on('data', (chunk) => { data += chunk; });
req.on('end', () => {
console.log(data);
client.close();
});
});
req.end();
});
【问题讨论】:
标签: javascript node.js proxy http2 charles-proxy