Skip to content
Draft
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9c89bc5
fix formatting and error messages
peterguy Mar 7, 2026
8cac467
add function to read HTTP_PROXY/HTTPS_PROXY/NO_PROXY environment vari…
peterguy Feb 25, 2026
b6ce714
use new function to read proxy settings from environment, preferring …
peterguy Feb 25, 2026
6a1fe69
use http.Request instead of manually building the request, and use Re…
peterguy Feb 25, 2026
ba2973c
commit to a buffered reader for the proxy connection so we avoid nast…
peterguy Feb 25, 2026
a18649f
clone the transport TLS config instead of creating a new one, and mak…
peterguy Feb 25, 2026
aa3a316
fix some comments
peterguy Feb 25, 2026
20709ff
whoops, forgot to commit the io import
peterguy Feb 25, 2026
af44dd2
dial the proxy, ensuring http/1.1 instead of http/2 for TLS-enabled p…
peterguy Feb 25, 2026
7f24ce7
add proxy tests
peterguy Feb 25, 2026
b201294
change name of test to match production method
peterguy Feb 25, 2026
ef63d4d
add 10ms delay to test proxy server startup to try to fix ubuntu tests
peterguy Feb 26, 2026
9b66453
go-lint.sh
peterguy Feb 26, 2026
3272fbc
wait for test proxy to startup
peterguy Feb 26, 2026
1bbcc58
go-lint.sh
peterguy Feb 26, 2026
c91176d
parse the endpoint into a URL up front, and gather the proxy from the…
peterguy Feb 27, 2026
05a9f01
use the parsed endpoint url and consolidate proxy handling because th…
peterguy Feb 27, 2026
f124df6
fix proxyDialAddr and add more tests for it
peterguy Feb 27, 2026
aca46f1
add EndpointURL to the tests and other places that should use it inst…
peterguy Feb 27, 2026
3820f62
use the client to connect to the API instead of http.DefaultClient so…
peterguy Feb 27, 2026
13bab52
restore unintentional whitespace changes
peterguy Mar 8, 2026
7ff4bd6
undo changes to search_jobs - do those changes in another PR
peterguy Mar 8, 2026
7a10ad3
clarify comments and direct all proxy usage to the custom dialer
peterguy Mar 9, 2026
99f475a
add a mutex for the buffered reader to match the concurency behavior …
peterguy Mar 9, 2026
35b8f7c
close the connection if the TLS handshake errors
peterguy Mar 9, 2026
2957e62
refactor to use httptest package
peterguy Mar 9, 2026
3e8c72d
rename functions and address possible leaks
peterguy Mar 9, 2026
70070ab
check I/O errors; use resp.ProtoMajor instead of strings
peterguy Mar 9, 2026
70b1f0f
add test to confirm that closing the connection on handshake error (`…
peterguy Mar 9, 2026
77ffe16
fmt.Errorf --> errors.Newf
peterguy Mar 9, 2026
41c65e6
add test for https connection rejection - ensure correct error handli…
peterguy Mar 9, 2026
b9aa6dc
add retry for test that is flaky on CI, probably due to resource cont…
peterguy Mar 10, 2026
7831cc9
another try at addressing CI test errors
peterguy Mar 10, 2026
73fb824
clean up proxy and remove buffered reader - don't actually need it
peterguy Mar 26, 2026
872fedf
clean up and refactor tests
peterguy Mar 26, 2026
2445d4e
refactor buildTransport to match incoming changes
peterguy Mar 26, 2026
7e37fb9
fix error capitalization
peterguy Mar 26, 2026
6aa57c0
undo capitalization change
peterguy Mar 26, 2026
9ca8fe1
fix rebase
peterguy Mar 27, 2026
7a74eb7
remove the custom dialer - turns out it is not actually needed
peterguy Mar 28, 2026
531566b
remove unused function
peterguy Mar 28, 2026
8a5477e
undo comment change as out of scope
peterguy Mar 31, 2026
cd0bd79
pare down to smoke tests
peterguy Apr 1, 2026
99ad843
remove all tests - they really aren't useful
peterguy Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 9 additions & 87 deletions internal/api/proxy.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package api

import (
"bufio"
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/url"
Expand All @@ -15,22 +12,22 @@ import (
//
// Note: baseTransport is considered to be a clone created with transport.Clone()
//
// - If a the proxyPath is not empty, a unix socket proxy is created.
// - Otherwise, the proxyURL is used to determine if we should proxy socks5 / http connections
// - If proxyPath is not empty, a unix socket proxy is created.
// - Otherwise, proxyURL is used to determine if we should proxy socks5 / http connections
func withProxyTransport(baseTransport *http.Transport, proxyURL *url.URL, proxyPath string) *http.Transport {
handshakeTLS := func(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
// Extract the hostname (without the port) for TLS SNI
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, &tls.Config{
ServerName: host,
// Pull InsecureSkipVerify from the target host transport
// so that insecure-skip-verify flag settings are honored for the proxy server
InsecureSkipVerify: baseTransport.TLSClientConfig.InsecureSkipVerify,
})
cfg := baseTransport.TLSClientConfig.Clone()
if cfg.ServerName == "" {
cfg.ServerName = host
}
tlsConn := tls.Client(conn, cfg)
Comment on lines +24 to +28
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely necessary, but cloning the client config preserves all of the settings, like insecureskipverify, automatically.

if err := tlsConn.HandshakeContext(ctx); err != nil {
tlsConn.Close()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably important - closing the connection on error. Should be closed when the program terminates, but explicitly closing now is more safe.

return nil, err
}
return tlsConn, nil
Expand All @@ -53,82 +50,7 @@ func withProxyTransport(baseTransport *http.Transport, proxyURL *url.URL, proxyP
// clear out any system proxy settings
baseTransport.Proxy = nil
} else if proxyURL != nil {
switch proxyURL.Scheme {
case "socks5", "socks5h":
// SOCKS proxies work out of the box - no need to manually dial
baseTransport.Proxy = http.ProxyURL(proxyURL)
case "http", "https":
dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
// Dial the proxy
d := net.Dialer{}
conn, err := d.DialContext(ctx, "tcp", proxyURL.Host)
if err != nil {
return nil, err
}

// this is the whole point of manually dialing the HTTP(S) proxy:
// being able to force HTTP/1.
// When relying on Transport.Proxy, the protocol is always HTTP/2,
// but many proxy servers don't support HTTP/2.
// We don't want to disable HTTP/2 in general because we want to use it when
// connecting to the Sourcegraph API, using HTTP/1 for the proxy connection only.
protocol := "HTTP/1.1"

// CONNECT is the HTTP method used to set up a tunneling connection with a proxy
method := "CONNECT"

// Manually writing out the HTTP commands because it's not complicated,
// and http.Request has some janky behavior:
// - ignores the Proto field and hard-codes the protocol to HTTP/1.1
// - ignores the Host Header (Header.Set("Host", host)) and uses URL.Host instead.
// - When the Host field is set, overrides the URL field
connectReq := fmt.Sprintf("%s %s %s\r\n", method, addr, protocol)

// A Host header is required per RFC 2616, section 14.23
connectReq += fmt.Sprintf("Host: %s\r\n", addr)

// use authentication if proxy credentials are present
if proxyURL.User != nil {
password, _ := proxyURL.User.Password()
auth := base64.StdEncoding.EncodeToString([]byte(proxyURL.User.Username() + ":" + password))
connectReq += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", auth)
}

// finish up with an extra carriage return + newline, as per RFC 7230, section 3
connectReq += "\r\n"

// Send the CONNECT request to the proxy to establish the tunnel
if _, err := conn.Write([]byte(connectReq)); err != nil {
conn.Close()
return nil, err
}

// Read and check the response from the proxy
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
if err != nil {
conn.Close()
return nil, err
}
if resp.StatusCode != http.StatusOK {
conn.Close()
return nil, fmt.Errorf("failed to connect to proxy %v: %v", proxyURL, resp.Status)
}
resp.Body.Close()
return conn, nil
}
dialTLS := func(ctx context.Context, network, addr string) (net.Conn, error) {
// Dial the underlying connection through the proxy
conn, err := dial(ctx, network, addr)
if err != nil {
return nil, err
}
return handshakeTLS(ctx, conn, addr)
}
baseTransport.DialContext = dial
baseTransport.DialTLSContext = dialTLS
// clear out any system proxy settings
baseTransport.Proxy = nil
}
baseTransport.Proxy = http.ProxyURL(proxyURL)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out, the whole custom dialer was breaking the ability to connect through TLS-enabled proxies. Simply removing it and just adding the proxy to the transport cleared everything up.

}

return baseTransport
Expand Down
Loading