bogdanfinn / tls-client

net/http.Client like HTTP Client with options to select specific client TLS Fingerprints to use for requests.
BSD 4-Clause "Original" or "Old" License
667 stars 133 forks source link

[Feature Request]: Websocket Support #95

Open juzeon opened 6 months ago

juzeon commented 6 months ago

Describe the feature / enhancement and how it would improve things

Hello. Thanks for the library!

I'm not sure how it can work with Websocket. Typically I use this library, which requires a http client:

connRaw, resp, err := websocket.Dial(ctx,
    o.wssURL,
    &websocket.DialOptions{
        HTTPClient: client,// a *http.Client instance, not a tlsclient.HttpClient
        HTTPHeader: httpHeaders,
    })

Describe how your proposal will work, with code and/or pseudo-code

I have tried to implement a fake RoundTripper by:

// ...omit...
func (s MyRoundTripper) RoundTrip(hReq *http.Request) (*http.Response, error) {
    fReq, err := fhttp.NewRequest(hReq.Method, hReq.URL.String(), hReq.Body)
    if err != nil {
        return nil, err
    }
    fReq.Header = fhttp.Header(hReq.Header)
    fReq.Trailer = fhttp.Header(hReq.Trailer)
    fReq.Form = hReq.Form
    fReq.MultipartForm = hReq.MultipartForm
    fReq.PostForm = hReq.PostForm

    fResp, err := s.Client.Do(fReq)// s.Client is a tlsclient.HttpClient
    if err != nil {
        return nil, fmt.Errorf("error fetching response: %w", err)
    }
    return &http.Response{
        Status:           fResp.Status,
        StatusCode:       fResp.StatusCode,
        Proto:            fResp.Proto,
        ProtoMajor:       fResp.ProtoMajor,
        ProtoMinor:       fResp.ProtoMinor,
        Header:           http.Header(fResp.Header),
        Body:             fResp.Body,
        ContentLength:    fResp.ContentLength,
        TransferEncoding: fResp.TransferEncoding,
        Close:            fResp.Close,
        Uncompressed:     fResp.Uncompressed,
        Trailer:          http.Header(fResp.Trailer),
        Request:          hReq,
        TLS:              nil,
    }, nil
}

And use like this:

client := &http.Client{}
client.Transport = &MyRoundTripper{...omit initializing here...}
connRaw, resp, err := websocket.Dial(ctx,
    o.wssURL,
    &websocket.DialOptions{
        HTTPClient: client,// a *http.Client here
        HTTPHeader: httpHeaders,
    })

But I got this:

"failed to WebSocket dial: failed to send handshake request: Get \"https://...omit...": http2: invalid Upgrade request header: [\"websocket\"]"

Thanks in advance!

0x676e67 commented 3 months ago

Upgrading from http to websocket, you must use http1 protocol instead of http2 protocol

If you know rust, you can refer to: https://github.com/gngpp/reqwest-impersonate

HMaker commented 3 months ago

The missing HTTP1 protocol is not the only issue. For that to work tls_client must return a reference to the socket connected to the target server so it can be used by the websocket library.

I made tls_client use HTTP1 but got the following error from the websocket library

failed to WebSocket dial: response body is not a io.ReadWriteCloser: *http.cancelTimerBody

I think fResp.Body must be a io.ReadWriteCloser.