Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended support for HTTP proxy in driver options #1424

Merged
merged 1 commit into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ conn.SetConnMaxLifetime(time.Hour)
* read_timeout - a duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix such as "300ms", "1s". Valid time units are "ms", "s", "m" (default 5m).
* max_compression_buffer - max size (bytes) of compression buffer during column by column compression (default 10MiB)
* client_info_product - optional list (comma separated) of product name and version pair separated with `/`. This value will be pass a part of client info. e.g. `client_info_product=my_app/1.0,my_module/0.1` More details in [Client info](#client-info) section.
* http_proxy - HTTP proxy address

SSL/TLS parameters:

Expand All @@ -174,6 +175,8 @@ clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms

### HTTP Support (Experimental)

**Note**: using HTTP protocol is possible only with `database/sql` interface.

The native format can be used over the HTTP protocol. This is useful in scenarios where users need to proxy traffic e.g. using [ChProxy](https://www.chproxy.org/) or via load balancers.

This can be achieved by modifying the DSN to specify the HTTP protocol.
Expand Down Expand Up @@ -203,7 +206,19 @@ conn := clickhouse.OpenDB(&clickhouse.Options{
})
```

**Note**: using HTTP protocol is possible only with `database/sql` interface.
#### Proxy support

HTTP proxy can be set in the DSN string by specifying the `http_proxy` parameter.
(make sure to URL encode the proxy address)

```sh
http://host1:8123,host2:8123/database?dial_timeout=200ms&max_execution_time=60&http_proxy=http%3A%2F%2Fproxy%3A8080
```

If you are using `clickhouse.OpenDB`, set the `HTTProxy` field in the `clickhouse.Options`.

An alternative way is to enable proxy by setting the `HTTP_PROXY` (for HTTP) or `HTTPS_PROXY` (for HTTPS) environment variables.
See more details in the [Go documentation](https://pkg.go.dev/net/http#ProxyFromEnvironment).

## Compression

Expand Down
14 changes: 13 additions & 1 deletion clickhouse_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
Expand Down Expand Up @@ -121,6 +122,8 @@ type DialResult struct {
conn *connect
}

type HTTPProxy func(*http.Request) (*url.URL, error)

type Options struct {
Protocol Protocol
ClientInfo ClientInfo
Expand All @@ -143,7 +146,10 @@ type Options struct {
HttpHeaders map[string]string // set additional headers on HTTP requests
HttpUrlPath string // set additional URL path for HTTP requests
BlockBufferSize uint8 // default 2 - can be overwritten on query
MaxCompressionBuffer int // default 10485760 - measured in bytes i.e. 10MiB
MaxCompressionBuffer int // default 10485760 - measured in bytes i.e.

// HTTPProxy specifies an HTTP proxy URL to use for requests made by the client.
HTTPProxyURL *url.URL

scheme string
ReadTimeout time.Duration
Expand Down Expand Up @@ -302,6 +308,12 @@ func (o *Options) fromDSN(in string) error {
version,
})
}
case "http_proxy":
proxyURL, err := url.Parse(params.Get(v))
if err != nil {
return fmt.Errorf("clickhouse [dsn parse]: http_proxy: %s", err)
}
o.HTTPProxyURL = proxyURL
default:
switch p := strings.ToLower(params.Get(v)); p {
case "true":
Expand Down
21 changes: 21 additions & 0 deletions clickhouse_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ package clickhouse

import (
"crypto/tls"
"net/url"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestParseDSN does not implement all use cases yet
Expand Down Expand Up @@ -467,6 +469,19 @@ func TestParseDSN(t *testing.T) {
},
"",
},
{
"http protocol with proxy",
"http://127.0.0.1/?http_proxy=http%3A%2F%2Fproxy.example.com%3A3128",
&Options{
Protocol: HTTP,
TLS: nil,
Addr: []string{"127.0.0.1"},
Settings: Settings{},
scheme: "http",
HTTPProxyURL: parseURL(t, "http://proxy.example.com:3128"),
},
"",
},
}

for _, testCase := range testCases {
Expand All @@ -484,3 +499,9 @@ func TestParseDSN(t *testing.T) {
})
}
}

func parseURL(t *testing.T, v string) *url.URL {
u, err := url.Parse(v)
require.NoError(t, err)
return u
}
7 changes: 6 additions & 1 deletion conn_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,13 @@ func dialHttp(ctx context.Context, addr string, num int, opt *Options) (*httpCon
query.Set("default_format", "Native")
u.RawQuery = query.Encode()

httpProxy := http.ProxyFromEnvironment
if opt.HTTPProxyURL != nil {
httpProxy = http.ProxyURL(opt.HTTPProxyURL)
}

t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Proxy: httpProxy,
DialContext: (&net.Dialer{
Timeout: opt.DialTimeout,
}).DialContext,
Expand Down
40 changes: 40 additions & 0 deletions examples/std/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package std
import (
"database/sql"
"fmt"
"net/url"

"github.com/ClickHouse/clickhouse-go/v2"
)

Expand Down Expand Up @@ -50,3 +52,41 @@ func ConnectDSN() error {
}
return conn.Ping()
}

func ConnectUsingHTTPProxy() error {
env, err := GetStdTestEnvironment()
if err != nil {
return fmt.Errorf("failed to get test environment: %w", err)
}

proxyURL, err := url.Parse("http://proxy.example.com:3128")
if err != nil {
return fmt.Errorf("failed to parse proxy URL: %w", err)
}

conn := clickhouse.OpenDB(&clickhouse.Options{
Addr: []string{fmt.Sprintf("%s:%d", env.Host, env.Port)},
Auth: clickhouse.Auth{
Database: env.Database,
Username: env.Username,
Password: env.Password,
},
HTTPProxyURL: proxyURL,
})
return conn.Ping()
}

func ConnectUsingHTTPProxyDSN() error {
env, err := GetStdTestEnvironment()
if err != nil {
return fmt.Errorf("failed to get test environment: %w", err)
}

urlEncodedProxyURL := url.QueryEscape("http://proxy.example.com:3128")

conn, err := sql.Open("clickhouse", fmt.Sprintf("clickhouse://%s:%d?username=%s&password=%s&http_proxy=%s", env.Host, env.Port, env.Username, env.Password, urlEncodedProxyURL))
if err != nil {
return err
}
return conn.Ping()
}
Loading