Skip to content

Commit

Permalink
Extended support for HTTP proxy in driver options (#1424)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkaflik authored Oct 16, 2024
1 parent 28b2f5d commit 269b0f3
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 3 deletions.
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()
}

0 comments on commit 269b0f3

Please sign in to comment.