Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fc85a20
fix formatting and error messages
peterguy Mar 7, 2026
55a5d3c
add function to read HTTP_PROXY/HTTPS_PROXY/NO_PROXY environment vari…
peterguy Feb 25, 2026
3098fe7
use new function to read proxy settings from environment, preferring …
peterguy Feb 25, 2026
278a38a
use http.Request instead of manually building the request, and use Re…
peterguy Feb 25, 2026
b3395c5
commit to a buffered reader for the proxy connection so we avoid nast…
peterguy Feb 25, 2026
b4af75c
clone the transport TLS config instead of creating a new one, and mak…
peterguy Feb 25, 2026
7029f04
fix some comments
peterguy Feb 25, 2026
72d731c
whoops, forgot to commit the io import
peterguy Feb 25, 2026
e853afc
dial the proxy, ensuring http/1.1 instead of http/2 for TLS-enabled p…
peterguy Feb 25, 2026
bf0fb19
add proxy tests
peterguy Feb 25, 2026
b12b686
change name of test to match production method
peterguy Feb 25, 2026
4ea79de
add 10ms delay to test proxy server startup to try to fix ubuntu tests
peterguy Feb 26, 2026
40fbc88
go-lint.sh
peterguy Feb 26, 2026
d02861d
wait for test proxy to startup
peterguy Feb 26, 2026
0186637
go-lint.sh
peterguy Feb 26, 2026
cf90285
parse the endpoint into a URL up front, and gather the proxy from the…
peterguy Feb 27, 2026
c01d0cc
use the parsed endpoint url and consolidate proxy handling because th…
peterguy Feb 27, 2026
1fa4544
fix proxyDialAddr and add more tests for it
peterguy Feb 27, 2026
c452276
add EndpointURL to the tests and other places that should use it inst…
peterguy Feb 27, 2026
37fbaa3
use the client to connect to the API instead of http.DefaultClient so…
peterguy Feb 27, 2026
c8a912e
restore unintentional whitespace changes
peterguy Mar 8, 2026
eeb5eca
undo changes to search_jobs - do those changes in another PR
peterguy Mar 8, 2026
81e1780
clarify comments and direct all proxy usage to the custom dialer
peterguy Mar 9, 2026
469b118
add a mutex for the buffered reader to match the concurency behavior …
peterguy Mar 9, 2026
d222127
close the connection if the TLS handshake errors
peterguy Mar 9, 2026
765bc6a
refactor to use httptest package
peterguy Mar 9, 2026
cc25440
rename functions and address possible leaks
peterguy Mar 9, 2026
7386ce6
check I/O errors; use resp.ProtoMajor instead of strings
peterguy Mar 9, 2026
b5d897c
add test to confirm that closing the connection on handshake error (`…
peterguy Mar 9, 2026
c8fe355
fmt.Errorf --> errors.Newf
peterguy Mar 9, 2026
2652bad
add test for https connection rejection - ensure correct error handli…
peterguy Mar 9, 2026
8a9a210
add retry for test that is flaky on CI, probably due to resource cont…
peterguy Mar 10, 2026
332be40
another try at addressing CI test errors
peterguy Mar 10, 2026
97f4d42
clean up proxy and remove buffered reader - don't actually need it
peterguy Mar 26, 2026
30ad910
clean up and refactor tests
peterguy Mar 26, 2026
02244ea
refactor buildTransport to match incoming changes
peterguy Mar 26, 2026
f8a746c
fix error capitalization
peterguy Mar 26, 2026
b54f2b9
undo capitalization change
peterguy Mar 26, 2026
6a6f97b
fix rebase
peterguy Mar 27, 2026
57b7ea6
remove the custom dialer - turns out it is not actually needed
peterguy Mar 28, 2026
b986bac
remove unused function
peterguy Mar 28, 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
3 changes: 2 additions & 1 deletion cmd/src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ func (c *config) apiClient(flags *api.Flags, out io.Writer) api.Client {
return api.NewClient(opts)
}

// readConfig reads the config file from the given path.
// readConfig reads the config from the standard config file, the (deprecated) user-specified config file,
// the environment variables, and the (deprecated) command-line flags.
func readConfig() (*config, error) {
cfgFile := *configPath
userSpecified := *configPath != ""
Expand Down
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)
if err := tlsConn.HandshakeContext(ctx); err != nil {
tlsConn.Close()
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)
}

return baseTransport
Expand Down
Loading
Loading