Skip to content

Commit 93b0331

Browse files
authored
[SEA-NodeJS] feat(kernel): proxy + socketTimeout ConnectionOptions on the kernel backend (#431)
* [SEA-NodeJS] feat(kernel): support the explicit proxy ConnectionOption on the kernel backend Map the public Thrift-shaped `ConnectionOptions.proxy` (`{protocol, host, port, auth}`) onto the kernel napi binding's `proxy?: string`, so the SAME proxy connection option that works on the Thrift backend now also routes kernel/SEA traffic through a proxy. `buildKernelProxyOptions` composes `protocol://[user:pass@]host:port`, percent-encoding any `auth.{username,password}` into the URL userinfo so credentials with reserved characters survive; the kernel parses the userinfo off and applies it as proxy basic-auth. Wired into `buildKernelConnectionOptions` alongside the TLS / HTTP option builders. The regenerated napi contract (`native/kernel/index.d.ts`) carries the new `proxy?: string` and `socketTimeoutMs?: number` fields exposed by the kernel PR (databricks/databricks-sql-kernel#129). Note: env-var proxying (`HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`) already worked on the kernel backend (reqwest honours it natively); this adds the *programmatic* path for callers who cannot set process env vars. Verified end-to-end via mitmproxy against a live serverless warehouse: explicit `proxy` option (no env var) routes all SEA calls through the proxy; a dead proxy port fails the connection (proving the proxy is used). Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com> * [SEA-NodeJS] feat(kernel): map the socketTimeout ConnectionOption onto the kernel The kernel napi binding exposes `socketTimeoutMs` (kernel `HttpConfig::request_timeout` / reqwest `Client::timeout`, kernel #129). Map the public `socketTimeout` ConnectionOption (ms) onto it in `buildKernelHttpOptions`, so the per-connection read timeout works on the kernel backend just like the Thrift path. Only a positive value is forwarded: `socketTimeout: 0` means "disabled / wait indefinitely" on Thrift, but forwarding `0` would make reqwest time out immediately, so it is omitted (kernel keeps its large default). Verified directly against a live serverless warehouse: `socketTimeout: 1` makes a SEA request time out. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com> * [SEA-NodeJS] refactor(kernel): pass proxy to the kernel as a structured object Change the kernel proxy mapping from a flattened URL string to the structured napi `proxy` object (kernel #129), mirroring the kernel's internal ProxyConfig: `{ url, username?, password?, bypassHosts? }`. `buildKernelProxyOptions` now composes `url` from `protocol://host:port` (no embedded credentials) and forwards `auth.{username,password}` as separate basic-auth fields — eliminating the URL percent-encoding of credentials. The `noProxy` host list is forwarded as `bypassHosts` (previously unexpressible through the URL-string form). Regenerated napi contract (native/kernel/index.d.ts) carries the new `ProxyInput` object type. Verified via mitmproxy (HttpProxyTests, SEA leg): http / https / proxy-with-auth all route through the proxy and the query succeeds. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com> * chore(kernel): bump KERNEL_REV to kernel v0.2.0 (0d46716) Point the bundled kernel binding at the latest kernel main — the v0.2.0 beta release (databricks/databricks-sql-kernel@0d46716), which includes the merged proxy + per-connection socketTimeout napi surface (#129) this PR maps onto, plus the query-tags wire support (#150). The kernel-e2e CI gate builds the napi from this SHA. Verified: proxy routes through mitmproxy and SELECT round-trips on the v0.2.0 binding. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com> --------- Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent 3358a61 commit 93b0331

2 files changed

Lines changed: 65 additions & 4 deletions

File tree

KERNEL_REV

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
34b4c202cc127021c923903d59c841daa2b44fef
1+
0d46716c466897148dfc1d2976ff03bdf097998c

lib/kernel/KernelAuth.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,32 @@ export interface KernelTlsOptions {
190190
*/
191191
export interface KernelHttpOptions {
192192
customHeaders?: Array<{ name: string; value: string }>;
193+
socketTimeoutMs?: number;
194+
}
195+
196+
/**
197+
* HTTP(S) proxy forwarded to the napi binding's `ConnectionOptions.proxy`
198+
* (kernel `ProxyConfig`). The public `ConnectionOptions.proxy` is the
199+
* Thrift-shaped `{protocol, host, port, auth}`; `buildKernelProxyOptions`
200+
* maps it onto the kernel's structured proxy input — `url` composed from
201+
* `protocol://host:port`, with `auth.{username,password}` forwarded as
202+
* separate basic-auth fields (NOT embedded in the URL, so no percent-encoding
203+
* footgun) and the `noProxy` host list forwarded as `bypassHosts`. The same
204+
* connection option therefore works identically on both backends.
205+
*/
206+
export interface KernelProxyOptions {
207+
proxy?: {
208+
url: string;
209+
username?: string;
210+
password?: string;
211+
bypassHosts?: string;
212+
};
193213
}
194214

195215
export type KernelNativeConnectionOptions = KernelSessionDefaults &
196216
KernelTlsOptions &
197217
KernelHttpOptions &
218+
KernelProxyOptions &
198219
(
199220
| {
200221
hostName: string;
@@ -401,7 +422,7 @@ function validateHeaderToken(kind: 'name' | 'value', headerName: string, token:
401422
}
402423

403424
export function buildKernelHttpOptions(options: ConnectionOptions): KernelHttpOptions {
404-
const { customHeaders, userAgentEntry } = options;
425+
const { customHeaders, userAgentEntry, socketTimeout } = options;
405426

406427
const headers: Array<{ name: string; value: string }> = [];
407428
if (customHeaders) {
@@ -423,7 +444,18 @@ export function buildKernelHttpOptions(options: ConnectionOptions): KernelHttpOp
423444
// Python connector's unconditional `base_headers` append.
424445
headers.push({ name: 'User-Agent', value: buildUserAgentString(userAgentEntry) });
425446

426-
return { customHeaders: headers };
447+
const http: KernelHttpOptions = { customHeaders: headers };
448+
// Per-connection socket read timeout (ms). The public `socketTimeout`
449+
// ConnectionOption maps onto the kernel napi `socketTimeoutMs`
450+
// (kernel `HttpConfig::request_timeout` / reqwest `Client::timeout`).
451+
// Only forward a POSITIVE value: `socketTimeout: 0` means "disabled / wait
452+
// indefinitely" on the Thrift path, but forwarding `0` would make reqwest
453+
// time out immediately, so we omit it and let the kernel keep its (large)
454+
// default — preserving the "effectively no idle timeout" semantics.
455+
if (typeof socketTimeout === 'number' && socketTimeout > 0) {
456+
http.socketTimeoutMs = socketTimeout;
457+
}
458+
return http;
427459
}
428460

429461
/**
@@ -514,6 +546,32 @@ export function buildKernelRetryOptions(config: {
514546
return out;
515547
}
516548

549+
/**
550+
* Map the public `ConnectionOptions.proxy` (`{protocol, host, port, auth}` —
551+
* the same shape the Thrift backend accepts) onto the kernel's structured napi
552+
* proxy input. The `url` is composed from `protocol://host:port` (no embedded
553+
* credentials); `auth.{username,password}` are forwarded as separate
554+
* basic-auth fields (the kernel applies them via reqwest `Proxy::basic_auth`),
555+
* avoiding any URL percent-encoding footgun. The `noProxy` host list (a driver
556+
* option, not on the published `.d.ts`) is forwarded as `bypassHosts`. The
557+
* kernel accepts only `http://` / `https://`; a SOCKS protocol surfaces a clear
558+
* kernel error at connect (reqwest SOCKS support is not compiled in).
559+
*/
560+
export function buildKernelProxyOptions(options: ConnectionOptions): KernelProxyOptions {
561+
const { proxy } = options;
562+
if (!proxy) {
563+
return {};
564+
}
565+
const { noProxy } = options as ConnectionOptions & { noProxy?: string };
566+
const out: NonNullable<KernelProxyOptions['proxy']> = {
567+
url: `${proxy.protocol}://${proxy.host}:${proxy.port}`,
568+
};
569+
if (proxy.auth?.username !== undefined) out.username = proxy.auth.username;
570+
if (proxy.auth?.password !== undefined) out.password = proxy.auth.password;
571+
if (typeof noProxy === 'string' && noProxy.length > 0) out.bypassHosts = noProxy;
572+
return { proxy: out };
573+
}
574+
517575
export function buildKernelConnectionOptions(options: ConnectionOptions): KernelNativeConnectionOptions {
518576
const { authType } = options as { authType?: string };
519577

@@ -523,7 +581,8 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
523581
intervalsAsString: boolean;
524582
maxConnections?: number;
525583
} & KernelTlsOptions &
526-
KernelHttpOptions = {
584+
KernelHttpOptions &
585+
KernelProxyOptions = {
527586
hostName: options.host,
528587
httpPath: prependSlash(options.path),
529588
// Match the NodeJS Thrift driver, which surfaces INTERVAL columns as
@@ -539,6 +598,8 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
539598
...buildKernelTlsOptions(options),
540599
// HTTP headers (caller `customHeaders` + composed `User-Agent`).
541600
...buildKernelHttpOptions(options),
601+
// HTTP(S) proxy — the same `ConnectionOptions.proxy` the Thrift path uses.
602+
...buildKernelProxyOptions(options),
542603
};
543604

544605
// kernel-only pool sizing; read via cast to match how this function reads the

0 commit comments

Comments
 (0)