HTTP/2 Adapter
The HTTP/2 adapter provides native HTTP/2 support through Node.js’s built-in http2 module. It supports multiplexed streams over a single TCP connection, session pooling with automatic lifecycle management, ALPN negotiation, and proxy tunneling via CONNECT.
import rezo from 'rezo/adapters/http2'; Import and Setup
import rezo, {
Rezo,
RezoError,
RezoHeaders,
RezoFormData,
RezoCookieJar
} from 'rezo/adapters/http2';
// Default instance
const { data } = await rezo.get('https://api.example.com/users');
// Custom instance
const client = new Rezo({
baseURL: 'https://api.example.com',
timeout: 15000
}); Multiplexed Streams
HTTP/2’s primary advantage is multiplexing: multiple requests and responses can be in flight simultaneously over a single TCP connection, avoiding the head-of-line blocking that limits HTTP/1.1.
const client = new Rezo({ baseURL: 'https://api.example.com' });
// All three requests share one underlying TCP connection
const [users, posts, comments] = await Promise.all([
client.get('/users'),
client.get('/posts'),
client.get('/comments')
]); With HTTP/1.1, browsers and clients open multiple TCP connections (typically 6) to achieve parallelism. With HTTP/2, a single connection handles all concurrent streams, reducing memory usage and TLS handshake overhead.
Session Pool
The adapter manages HTTP/2 sessions through the Http2SessionPool singleton. Sessions are created per origin (protocol + host) and reused across requests.
Pool Behavior
- Idle timeout: Sessions with zero active streams are closed after 60 seconds of inactivity.
- Cleanup interval: A background timer runs every 30 seconds to evict idle sessions.
- GOAWAY tracking: When a server sends a GOAWAY frame, the session is marked as unhealthy and a new session is created for the next request.
- Health checks: Before reusing a session, the pool verifies the session is not closed, destroyed, or GOAWAY’d, and that the underlying socket is still writable.
- Reference counting: Each active stream increments the session’s reference count. The pool only evicts sessions with zero references.
// Sessions are managed automatically -- no manual intervention needed
const client = new Rezo({ baseURL: 'https://api.example.com' });
// First request creates a session
await client.get('/endpoint-a');
// Second request reuses the same session
await client.get('/endpoint-b');
// After 60 seconds of inactivity, the session closes automatically The cleanup timer uses unref() so it does not prevent Node.js from exiting when all other work is done.
Exported Pool Access
For advanced use cases, the pool class is exported:
import { Http2SessionPool } from 'rezo/adapters/http2';
const pool = Http2SessionPool.getInstance();
// The pool is a singleton; all Rezo instances share it Pseudo-Headers
HTTP/2 uses pseudo-headers (prefixed with :) instead of a request line. The adapter sets these automatically:
| Pseudo-Header | Value |
|---|---|
:method | HTTP method (GET, POST, etc.) |
:path | URL path + query string |
:scheme | http or https |
:authority | Host and port |
// For a request to https://api.example.com/users?page=1
// The adapter sends these pseudo-headers:
// :method = GET
// :path = /users?page=1
// :scheme = https
// :authority = api.example.com Ordered Pseudo-Headers for Stealth
When using Rezo’s stealth module, pseudo-header order can be controlled to match specific browser fingerprints. Different browsers send pseudo-headers in different orders, and HTTP/2 fingerprinting tools (like Akamai’s) detect this.
import { RezoStealth } from 'rezo/adapters/http2';
const stealth = new RezoStealth({ profile: 'chrome-131' });
const client = new Rezo({ stealth });
// Pseudo-headers are ordered according to the Chrome 131 profile
// Chrome order: :method, :authority, :scheme, :path
// (different from the default h2 order)
const { data } = await client.get('https://bot-protected-site.com'); When no stealth profile is active, pseudo-headers are sent in the default order: :method, :path, :scheme, :authority.
Proxy Tunneling
The HTTP/2 adapter supports all three proxy types through CONNECT tunneling.
SOCKS Proxy
SOCKS4 and SOCKS5 proxies create a tunnel at the TCP level, then the HTTP/2 session is established over the tunnel:
const { data } = await rezo.get('https://example.com', {
proxy: {
protocol: 'socks5',
host: '127.0.0.1',
port: 1080,
auth: { username: 'user', password: 'pass' }
}
}); HTTP/HTTPS Proxy
For HTTP and HTTPS proxies, the adapter sends an HTTP/1.1 CONNECT request to establish a tunnel, then negotiates TLS and HTTP/2 over the tunnel:
const { data } = await rezo.get('https://example.com', {
proxy: {
protocol: 'http',
host: 'proxy.company.com',
port: 3128
}
}); The tunneling flow:
- TCP connection to proxy
CONNECT target:443 HTTP/1.1request- Proxy returns
200 Connection Established - TLS handshake over the tunnel with ALPN
['h2', 'http/1.1'] - ALPN check — if the server does not negotiate
h2, the connection fails with an error - HTTP/2 session created over the TLS tunnel
ALPN Verification
After the TLS handshake, the adapter checks tlsSocket.alpnProtocol to confirm the server supports HTTP/2. If the server only supports HTTP/1.1, the adapter throws an error rather than silently downgrading:
try {
await rezo.get('https://http1-only-server.com');
} catch (error) {
// Error: Server does not support HTTP/2 (ALPN: http/1.1)
} Compression
The HTTP/2 adapter supports gzip, deflate, brotli, and zstd decompression:
const { data } = await rezo.get('https://api.example.com/data', {
headers: {
'Accept-Encoding': 'gzip, deflate, br, zstd'
}
}); Zstd support is loaded lazily — the @sinonjs/text-encoding or similar zstd module is imported only when a response uses zstd encoding, so it does not affect startup time or bundle size when unused.
Streaming
Stream large responses without buffering:
const response = await rezo.stream('https://example.com/large-dataset.jsonl');
for await (const chunk of response.data) {
const lines = chunk.toString().split('
');
for (const line of lines) {
if (line.trim()) {
const record = JSON.parse(line);
processRecord(record);
}
}
} Downloads and Uploads
// Download with progress
const client = new Rezo({
hooks: {
onDownloadProgress: [({ loaded, total, percent }) => {
console.log(`${percent.toFixed(1)}% complete`);
}]
}
});
await client.download('https://example.com/file.tar.gz', './file.tar.gz');
// Upload with multipart form data
const form = new RezoFormData();
form.append('document', fs.createReadStream('./report.pdf'));
await rezo.upload('https://example.com/upload', form); Cookie Jar
The HTTP/2 adapter supports full cookie jar integration, just like the HTTP adapter:
const jar = new RezoCookieJar();
const client = new Rezo({
baseURL: 'https://api.example.com',
jar
});
await client.post('/auth/login', { username: 'admin', password: 'secret' });
// Session cookies are automatically included in subsequent requests
const { data } = await client.get('/protected/resource'); Retry and Redirect Handling
The adapter includes the same retry logic and redirect handling as the HTTP adapter:
const { data } = await rezo.get('https://example.com/api/data', {
retry: {
limit: 3,
statusCodes: [502, 503, 504]
},
maxRedirects: 5
}); Debug Mode
const { data } = await rezo.get('https://api.example.com/users', {
debug: true
});
// [Rezo Debug] GET https://api.example.com/users
// [Rezo Debug] Adapter: http2
// [Rezo Debug] HTTP/2: Acquiring session for api.example.com...
// [Rezo Debug] Response: 200 OK (87.21ms) When to Use This Adapter
The HTTP/2 adapter is the right choice when:
- You make many concurrent requests to the same origin and want them multiplexed over a single connection
- You need HTTP/2 server push handling
- You want stealth pseudo-header ordering for anti-bot bypass
- Your target server requires HTTP/2 (some modern APIs do)
- You need all the features of the HTTP adapter (cookies, proxy, TLS, compression) with HTTP/2 multiplexing
If you do not specifically need HTTP/2, the HTTP adapter is the safer default — it works with all servers and does not fail if a server only supports HTTP/1.1.