websockets-rs / rust-websocket

A WebSocket (RFC6455) library written in Rust

Home Page:http://websockets-rs.github.io/rust-websocket/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Http Proxy Support (+ relevant tokio-tungstenite discussion)

UE2020 opened this issue · comments

Is it possible to proxy a connection using http proxies?

Maybe.

If you want to connect using HTTP CONNECT proxy, you need to supply custom underlying channel instead of usual automatic TCP for the implementation.

If you expect to the proxy to hangle HTTP upgrades properly and send something like GET ws://host/path HTTP/1.1, you'll probably need to either modify the library; or to provide HTTP handling part yourself and only use this library for WebSocket message format encoding/decoding.

Note that this library is not well-maintained, and you should try tungstenite and its satellites first.

An example of going through HTTP proxy to a secure websocket (with tokio and tungstenite, obviously, as prescribed by the previous commenter):

let tcp = TcpStream::connect("proxy_domain:80").await?;
let (mut request_sender, conn) = conn::handshake(tcp).await?;
let conn = tokio::spawn(conn.without_shutdown());
// create an HTTP method CONNECT request, port mandatory, even if 80 or 443
let req = Request::connect("domain.tld:443")
    .header("Proxy-Authorization", format!("Basic {}", proxy_base64))
    .body(())?;
let res = request_sender.send_request(req).await?;
assert!(response.status() == StatusCode::OK);
// `into_parts` panics if the connection is HTTP/2! Which might be negotiated if the proxy_domain is https! So maybe specify to use HTTP 1.1  above! (we don't worry this time, we are connecting to HTTP, on port 80. Does `without_shutdown` solve that instead? can we do a HTTP/2 connection to the proxy? find out next time on dragonballz
// unwrapping the joinhandle against panic, then the withoutshutdown
let tcp = connection.await??.io;
let req = Request::get("wss://domain.tld/path?query")
    .header("Sec-WebSocket-Key", tungstenite::handshake::client::generate_key())
    .header("Connection", "Upgrade")
    .header("Upgrade", "websocket")
    .header("Sec-WebSocket-Version", "13")
    .header("Host", "domain.tld")
    // technically not required, but many servers will demand it, so we are basically spoofing that we came from a webpage (can't have path though, just "origin" ...or is it "authority"? Idk I forgor. Don't remember which places `user:password@` goes and goesn't... ¯\_(ツ)_/¯   )
    .header("Origin", "https://domain.tld")
    .body(())?;
let (mut ws_stream, _) = tokio_tungstenite::client_async_tls(req, tcp).await?;

Note all the different variants of urls, don't mix them up.

Thank you very much! FYI, I don't think you're supposed to include ws:// in the Origin header.

@UE2020 you have to include the schema/protocol in a non-null Origin: header, but perhaps https:// would be more appropriate than wss:// here(you wrote ws, I had wss).
As in "we came here to upgrade to websocket from your https site".
I will change my example now.

@UE2020 you have to include the schema/protocol in a non-null Origin: header, but perhaps https:// would be more appropriate than wss:// here(you wrote ws, I had wss). As in "we came here to upgrade to websocket from your https site". I will change my example now.

It's Ok, but do you have any experience with async-socks5 which maybe easier?

  1. What is "Ok"? 🤣
  2. No, I don't have experience with async-socks5, but
    async_socks5::connect
    fast_socks5::client::Socks5Stream and
    tokio_socks::tcp::Socks5Stream should all work.

But I don't know how to write the code, do you have any example?

async-socks5

It is based on Tokio 1, but async in current version of rust-websocket is based on legacy Tokio 0.1.

This Issue is derailed and we are talking about tokio_tungstenite here instead :v

This Issue is derailed and we are talking about tokio_tungstenite here instead :v

To be fair, the README of this crate suggests tungstenite as an alternative.