Skip to content

Commit 269b0f3

Browse files
authored
Extended support for HTTP proxy in driver options (#1424)
1 parent 28b2f5d commit 269b0f3

File tree

5 files changed

+96
-3
lines changed

5 files changed

+96
-3
lines changed

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ conn.SetConnMaxLifetime(time.Hour)
160160
* 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).
161161
* max_compression_buffer - max size (bytes) of compression buffer during column by column compression (default 10MiB)
162162
* 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.
163+
* http_proxy - HTTP proxy address
163164

164165
SSL/TLS parameters:
165166

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

175176
### HTTP Support (Experimental)
176177

178+
**Note**: using HTTP protocol is possible only with `database/sql` interface.
179+
177180
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.
178181

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

206-
**Note**: using HTTP protocol is possible only with `database/sql` interface.
209+
#### Proxy support
210+
211+
HTTP proxy can be set in the DSN string by specifying the `http_proxy` parameter.
212+
(make sure to URL encode the proxy address)
213+
214+
```sh
215+
http://host1:8123,host2:8123/database?dial_timeout=200ms&max_execution_time=60&http_proxy=http%3A%2F%2Fproxy%3A8080
216+
```
217+
218+
If you are using `clickhouse.OpenDB`, set the `HTTProxy` field in the `clickhouse.Options`.
219+
220+
An alternative way is to enable proxy by setting the `HTTP_PROXY` (for HTTP) or `HTTPS_PROXY` (for HTTPS) environment variables.
221+
See more details in the [Go documentation](https://pkg.go.dev/net/http#ProxyFromEnvironment).
207222

208223
## Compression
209224

clickhouse_options.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"crypto/tls"
2323
"fmt"
2424
"net"
25+
"net/http"
2526
"net/url"
2627
"strconv"
2728
"strings"
@@ -121,6 +122,8 @@ type DialResult struct {
121122
conn *connect
122123
}
123124

125+
type HTTPProxy func(*http.Request) (*url.URL, error)
126+
124127
type Options struct {
125128
Protocol Protocol
126129
ClientInfo ClientInfo
@@ -143,7 +146,10 @@ type Options struct {
143146
HttpHeaders map[string]string // set additional headers on HTTP requests
144147
HttpUrlPath string // set additional URL path for HTTP requests
145148
BlockBufferSize uint8 // default 2 - can be overwritten on query
146-
MaxCompressionBuffer int // default 10485760 - measured in bytes i.e. 10MiB
149+
MaxCompressionBuffer int // default 10485760 - measured in bytes i.e.
150+
151+
// HTTPProxy specifies an HTTP proxy URL to use for requests made by the client.
152+
HTTPProxyURL *url.URL
147153

148154
scheme string
149155
ReadTimeout time.Duration
@@ -302,6 +308,12 @@ func (o *Options) fromDSN(in string) error {
302308
version,
303309
})
304310
}
311+
case "http_proxy":
312+
proxyURL, err := url.Parse(params.Get(v))
313+
if err != nil {
314+
return fmt.Errorf("clickhouse [dsn parse]: http_proxy: %s", err)
315+
}
316+
o.HTTPProxyURL = proxyURL
305317
default:
306318
switch p := strings.ToLower(params.Get(v)); p {
307319
case "true":

clickhouse_options_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ package clickhouse
1919

2020
import (
2121
"crypto/tls"
22+
"net/url"
2223
"testing"
2324
"time"
2425

2526
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
2628
)
2729

2830
// TestParseDSN does not implement all use cases yet
@@ -467,6 +469,19 @@ func TestParseDSN(t *testing.T) {
467469
},
468470
"",
469471
},
472+
{
473+
"http protocol with proxy",
474+
"http://127.0.0.1/?http_proxy=http%3A%2F%2Fproxy.example.com%3A3128",
475+
&Options{
476+
Protocol: HTTP,
477+
TLS: nil,
478+
Addr: []string{"127.0.0.1"},
479+
Settings: Settings{},
480+
scheme: "http",
481+
HTTPProxyURL: parseURL(t, "http://proxy.example.com:3128"),
482+
},
483+
"",
484+
},
470485
}
471486

472487
for _, testCase := range testCases {
@@ -484,3 +499,9 @@ func TestParseDSN(t *testing.T) {
484499
})
485500
}
486501
}
502+
503+
func parseURL(t *testing.T, v string) *url.URL {
504+
u, err := url.Parse(v)
505+
require.NoError(t, err)
506+
return u
507+
}

conn_http.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,13 @@ func dialHttp(ctx context.Context, addr string, num int, opt *Options) (*httpCon
201201
query.Set("default_format", "Native")
202202
u.RawQuery = query.Encode()
203203

204+
httpProxy := http.ProxyFromEnvironment
205+
if opt.HTTPProxyURL != nil {
206+
httpProxy = http.ProxyURL(opt.HTTPProxyURL)
207+
}
208+
204209
t := &http.Transport{
205-
Proxy: http.ProxyFromEnvironment,
210+
Proxy: httpProxy,
206211
DialContext: (&net.Dialer{
207212
Timeout: opt.DialTimeout,
208213
}).DialContext,

examples/std/connect.go

+40
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ package std
2020
import (
2121
"database/sql"
2222
"fmt"
23+
"net/url"
24+
2325
"github.com/ClickHouse/clickhouse-go/v2"
2426
)
2527

@@ -50,3 +52,41 @@ func ConnectDSN() error {
5052
}
5153
return conn.Ping()
5254
}
55+
56+
func ConnectUsingHTTPProxy() error {
57+
env, err := GetStdTestEnvironment()
58+
if err != nil {
59+
return fmt.Errorf("failed to get test environment: %w", err)
60+
}
61+
62+
proxyURL, err := url.Parse("http://proxy.example.com:3128")
63+
if err != nil {
64+
return fmt.Errorf("failed to parse proxy URL: %w", err)
65+
}
66+
67+
conn := clickhouse.OpenDB(&clickhouse.Options{
68+
Addr: []string{fmt.Sprintf("%s:%d", env.Host, env.Port)},
69+
Auth: clickhouse.Auth{
70+
Database: env.Database,
71+
Username: env.Username,
72+
Password: env.Password,
73+
},
74+
HTTPProxyURL: proxyURL,
75+
})
76+
return conn.Ping()
77+
}
78+
79+
func ConnectUsingHTTPProxyDSN() error {
80+
env, err := GetStdTestEnvironment()
81+
if err != nil {
82+
return fmt.Errorf("failed to get test environment: %w", err)
83+
}
84+
85+
urlEncodedProxyURL := url.QueryEscape("http://proxy.example.com:3128")
86+
87+
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))
88+
if err != nil {
89+
return err
90+
}
91+
return conn.Ping()
92+
}

0 commit comments

Comments
 (0)