为了账号安全,请及时绑定邮箱和手机立即绑定

如何使用 Node.js 通过代理发送 HTTP/2 请求?

如何使用 Node.js 通过代理发送 HTTP/2 请求?

呼唤远方 2022-11-11 10:49:47
我想使用 Node.js 的http2库通过代理向服务器发送 HTTP/2 请求。出于测试目的,我使用 Charles v4.2.7 作为代理,但 Charles 无法代理该请求。Charles 显示Malformed request URL "*"错误,因为它收到的请求是PRI * HTTP/2.0(HTTP/2 连接前言)。我可以使用 cURL(例如)通过我的 Charles 代理成功发送 HTTP/2 请求curl --http2 -x localhost:8888 https://cypher.codes,所以我认为这不是 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.js 错误node proxy.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 请求。我想尝试通过 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 请求吗?
查看完整描述

3 回答

?
江户川乱折腾

TA贡献1851条经验 获得超5个赞

要通过不理解它的代理来隧道 HTTP/2,您需要使用 HTTP/1.1 进行初始连接,然后仅在隧道中使用 HTTP/2。您的代码从一开始就使用 HTTP/2,这是行不通的。


要真正建立该隧道,您首先向目标主机发送 HTTP CONNECT 请求,并收到 200 响应,然后将来连接上的所有其他内容都会在您和目标主机之间来回转发。


一旦你的隧道工作了,你就可以发送 HTTP/2(或目标服务器理解的任何其他内容),它会直接到达你的目标。


在节点中执行此操作的代码如下所示:


const http = require('http');

const http2 = require('http2');


// Build a HTTP/1.1 CONNECT request for a tunnel:

const req = http.request({

  method: 'CONNECT',

  host: '127.0.0.1',

  port: 8888,

  path: 'cypher.codes'

});

req.end(); // Send it


req.on('connect', (res, socket) => {

  // When you get a successful response, use the tunnelled socket

  // to make your new request.

  const client = http2.connect('https://cypher.codes', {

    // Use your existing socket, wrapped with TLS for HTTPS:

    createConnection: () => tls.connect({

      socket: socket,

      ALPNProtocols: ['h2']

    })

  });


  // From here, use 'client' to do HTTP/2 as normal through the tunnel

});

我最近也一直在研究我自己的工具的内部结构,为代理添加完整的 HTTP/2 支持,并将其写在这里,这可能对你非常重要。https://github.com/httptoolkit/mockttp/blob/h2/test/integration/http2.spec.ts中的测试在这样的节点中有更多和更大的隧道 HTTP/2 示例,所以这些绝对值得也看看。当然,这一切仍在开发中,所以如果您有任何问题或发现任何错误,请告诉我。


查看完整回答
反对 回复 2022-11-11
?
www说

TA贡献1775条经验 获得超8个赞

身份验证以及如何避免 TLS 证书错误。


所以这是我的更新版本:


const http = require('http');

const http2 = require('http2');

const tls = require('tls');


// Build a HTTP/1.1 CONNECT request for a tunnel:

const username = '...';

const password = '...';

const req = http.request({

  method: 'CONNECT',

  host: '127.0.0.1',

  port: 8888,

  path: 'website.com', //the destination domain

  headers: { //this is how we authorize the proxy, skip it if you don't need it

    'Proxy-Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64')

  }

});

req.end(); // Send it


req.on('connect', (res, socket) => {

  // When you get a successful response, use the tunnelled socket to make your new request

  const client = http2.connect('https://website.com', {

    createConnection: () => tls.connect({

      host: 'website.com', //this is necessary to avoid certificate errors

      socket: socket,

      ALPNProtocols: ['h2']

    })

  });


  // From here, use 'client' to do HTTP/2 as normal through the tunnel

});


查看完整回答
反对 回复 2022-11-11
?
富国沪深

TA贡献1790条经验 获得超9个赞

为了显示我的解决方案,我们将向 API 发出请求,该 API 将您的 IP 作为 JSON 返回,然后您就可以适应您的需求。


这是代码:


/**

 * A URL without the path.

 */

const TARGET_AUTHOTIRY = 'https://api4.my-ip.io'


/**

 * You should use the host with the port equivalent to the protocol

 * HTTP => 80

 * HTTPS => 443

 */

const TARGET_HOST = 'api4.my-ip.io:443'


/**

 * Proxy configuration

 */

const PROXY_HOST = '<your_proxy_host>'

const PROXY_PORT = '<your_proxy_port>'

const PROXY_USERNAME = '<your_proxy_username>'

const PROXY_PASSWORD = '<your_proxy_password>'


/**

 * Establishes an connection to the target server throught the HTTP/1.0

 * proxy server.

 *

 * The CONNECT method tells the PROXY server where this connection should arive.

 *

 * After the connection is established you will be able to use the TCP socket to send data

 * to the TARGET server.

 */

const request = http.request({

  method: 'CONNECT',

  host: PROXY_HOST,

  port: PROXY_PORT,

  path: TARGET_HOST,

  headers: {

    'Host': TARGET_HOST,

    'Proxy-Authorization': `Basic ${Buffer.from(`${PROXY_USERNAME}:${PROXY_PASSWORD}`).toString('base64')}`

  }

})


/**

 * Wait the "connect" event and then uses the TCP socket to proxy the HTTP/2.0 connection throught.

 */

request.on('connect', (res, socket) => {

  /**

   * Check if it has successfully connected to the server

   */

  if (res.statusCode !== 200)

    throw new Error('Connection rejected by the proxy')


  /**

   * Use the TCP socket from the HTTP/1.0 as the socket for this new connection

   * without the need to establish the TLS connection manually and handle the errors

   * manually too.

   *

   * This method accepts all TCP and TLS options.

   */

  const client = http2.connect(TARGET_AUTHOTIRY, { socket })


  client.on('connect', () => {

    console.log('Connected to the page!')

  })


  /**

   * Request to check your IP

   */

  const req = client.request({

    ':path': '/ip.json',

  })


  req.on('response', (headers) => {

    console.log('Recieved a response')

  })


  /**

   * Stores the data recieved as a response

   */

  const buffers = []

  req.on('data', (buffer) => {

    buffers.push(buffer)

  })


  req.on('end', () => {

    console.log(Buffer.concat(buffers).toString('utf-8'))


    // Closes the connection with the server

    client.close()

  })


  req.end()

})


request.end()

我没有创建 TLS 套接字,而是将我的 TCP 套接字注入 HTTP/2.0 客户端。


socket选项未在方法文档中明确列出,但该方法接受所有net.connect ()和tls.connect()选项。


您可以在此处找到有关http2.connect方法的所有文档:HTTP 2 Node JS Documentation


查看完整回答
反对 回复 2022-11-11
  • 3 回答
  • 0 关注
  • 532 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号