0
# HTTP Tunneling
1
2
HTTP and HTTPS agents that tunnel web traffic through SSH connections, enabling secure web browsing and API access through SSH tunnels.
3
4
## Capabilities
5
6
### HTTPAgent Class
7
8
HTTP agent that tunnels HTTP requests through SSH connections.
9
10
```javascript { .api }
11
/**
12
* HTTP agent that tunnels requests through SSH connection
13
* Extends Node.js http.Agent with SSH tunneling capability
14
*/
15
class HTTPAgent extends http.Agent {
16
/**
17
* Create HTTP agent with SSH tunneling
18
* @param connectCfg - SSH connection configuration
19
* @param agentOptions - Standard HTTP agent options
20
*/
21
constructor(connectCfg: ClientConfig, agentOptions?: http.AgentOptions);
22
23
/**
24
* Create connection through SSH tunnel
25
* @param options - HTTP request options
26
* @param callback - Callback receiving tunneled connection
27
*/
28
createConnection(options: http.RequestOptions, callback: ConnectCallback): void;
29
}
30
31
type ConnectCallback = (err: Error | null, socket?: Socket) => void;
32
```
33
34
**HTTP Agent Usage Examples:**
35
36
```javascript
37
const { HTTPAgent } = require('ssh2');
38
const http = require('http');
39
40
// Create HTTP agent with SSH tunnel
41
const agent = new HTTPAgent({
42
host: 'jump-server.com',
43
username: 'user',
44
privateKey: require('fs').readFileSync('/path/to/key')
45
}, {
46
keepAlive: true,
47
maxSockets: 10
48
});
49
50
// Use agent for HTTP requests
51
const options = {
52
hostname: 'internal-api.corp',
53
port: 80,
54
path: '/api/data',
55
method: 'GET',
56
agent: agent
57
};
58
59
const req = http.request(options, (res) => {
60
console.log(`statusCode: ${res.statusCode}`);
61
console.log(`headers:`, res.headers);
62
63
res.on('data', (chunk) => {
64
console.log(chunk.toString());
65
});
66
});
67
68
req.on('error', (error) => {
69
console.error(error);
70
});
71
72
req.end();
73
```
74
75
### HTTPSAgent Class
76
77
HTTPS agent that tunnels HTTPS requests through SSH connections with TLS support.
78
79
```javascript { .api }
80
/**
81
* HTTPS agent that tunnels requests through SSH connection
82
* Extends Node.js https.Agent with SSH tunneling capability
83
*/
84
class HTTPSAgent extends https.Agent {
85
/**
86
* Create HTTPS agent with SSH tunneling
87
* @param connectCfg - SSH connection configuration
88
* @param agentOptions - Standard HTTPS agent options
89
*/
90
constructor(connectCfg: ClientConfig, agentOptions?: https.AgentOptions);
91
92
/**
93
* Create TLS connection through SSH tunnel
94
* @param options - HTTPS request options
95
* @param callback - Callback receiving tunneled TLS connection
96
*/
97
createConnection(options: https.RequestOptions, callback: ConnectCallback): void;
98
}
99
```
100
101
**HTTPS Agent Usage Examples:**
102
103
```javascript
104
const { HTTPSAgent } = require('ssh2');
105
const https = require('https');
106
107
// Create HTTPS agent with SSH tunnel
108
const agent = new HTTPSAgent({
109
host: 'jump-server.com',
110
username: 'user',
111
agent: process.env.SSH_AUTH_SOCK, // Use SSH agent
112
agentForward: true
113
}, {
114
keepAlive: true,
115
maxSockets: 5,
116
// TLS options
117
rejectUnauthorized: false, // For self-signed certs
118
ca: [require('fs').readFileSync('/path/to/ca.pem')]
119
});
120
121
// Use agent for HTTPS requests
122
const options = {
123
hostname: 'secure-api.corp',
124
port: 443,
125
path: '/api/secure-data',
126
method: 'POST',
127
headers: {
128
'Content-Type': 'application/json',
129
'Authorization': 'Bearer token123'
130
},
131
agent: agent
132
};
133
134
const req = https.request(options, (res) => {
135
console.log(`statusCode: ${res.statusCode}`);
136
137
let data = '';
138
res.on('data', (chunk) => {
139
data += chunk;
140
});
141
142
res.on('end', () => {
143
console.log('Response:', JSON.parse(data));
144
});
145
});
146
147
req.write(JSON.stringify({ query: 'data' }));
148
req.end();
149
```
150
151
## Advanced Usage Patterns
152
153
### Web Scraping Through SSH Tunnel
154
155
```javascript
156
const { HTTPAgent, HTTPSAgent } = require('ssh2');
157
const http = require('http');
158
const https = require('https');
159
160
class TunneledScraper {
161
constructor(sshConfig) {
162
this.httpAgent = new HTTPAgent(sshConfig, {
163
keepAlive: true,
164
timeout: 30000
165
});
166
167
this.httpsAgent = new HTTPSAgent(sshConfig, {
168
keepAlive: true,
169
timeout: 30000,
170
rejectUnauthorized: false
171
});
172
}
173
174
async fetchPage(url) {
175
return new Promise((resolve, reject) => {
176
const urlObj = new URL(url);
177
const isHttps = urlObj.protocol === 'https:';
178
const lib = isHttps ? https : http;
179
const agent = isHttps ? this.httpsAgent : this.httpAgent;
180
181
const options = {
182
hostname: urlObj.hostname,
183
port: urlObj.port || (isHttps ? 443 : 80),
184
path: urlObj.pathname + urlObj.search,
185
method: 'GET',
186
agent: agent,
187
headers: {
188
'User-Agent': 'Mozilla/5.0 (compatible; SSH2-Scraper)'
189
}
190
};
191
192
const req = lib.request(options, (res) => {
193
let data = '';
194
res.on('data', chunk => data += chunk);
195
res.on('end', () => resolve({
196
statusCode: res.statusCode,
197
headers: res.headers,
198
body: data
199
}));
200
});
201
202
req.on('error', reject);
203
req.setTimeout(30000, () => {
204
req.destroy();
205
reject(new Error('Request timeout'));
206
});
207
208
req.end();
209
});
210
}
211
212
async scrapeSite(urls) {
213
const results = [];
214
215
for (const url of urls) {
216
try {
217
console.log(`Fetching: ${url}`);
218
const result = await this.fetchPage(url);
219
results.push({ url, ...result });
220
} catch (error) {
221
console.error(`Failed to fetch ${url}:`, error.message);
222
results.push({ url, error: error.message });
223
}
224
}
225
226
return results;
227
}
228
229
destroy() {
230
this.httpAgent.destroy();
231
this.httpsAgent.destroy();
232
}
233
}
234
235
// Usage
236
const scraper = new TunneledScraper({
237
host: 'proxy-server.com',
238
username: 'user',
239
privateKey: privateKey
240
});
241
242
scraper.scrapeSite([
243
'http://internal-site1.corp/page1',
244
'https://internal-site2.corp/api/data',
245
'http://restricted-site.corp/info'
246
]).then(results => {
247
console.log('Scraping results:', results);
248
scraper.destroy();
249
});
250
```
251
252
### REST API Client with SSH Tunnel
253
254
```javascript
255
const { HTTPSAgent } = require('ssh2');
256
const https = require('https');
257
258
class TunneledAPIClient {
259
constructor(sshConfig, apiBaseUrl) {
260
this.baseUrl = apiBaseUrl;
261
this.agent = new HTTPSAgent(sshConfig, {
262
keepAlive: true,
263
maxSockets: 20
264
});
265
}
266
267
async request(method, endpoint, data = null, headers = {}) {
268
return new Promise((resolve, reject) => {
269
const url = new URL(endpoint, this.baseUrl);
270
271
const options = {
272
hostname: url.hostname,
273
port: url.port || 443,
274
path: url.pathname + url.search,
275
method: method.toUpperCase(),
276
agent: this.agent,
277
headers: {
278
'Content-Type': 'application/json',
279
'Accept': 'application/json',
280
...headers
281
}
282
};
283
284
if (data) {
285
const jsonData = JSON.stringify(data);
286
options.headers['Content-Length'] = Buffer.byteLength(jsonData);
287
}
288
289
const req = https.request(options, (res) => {
290
let responseData = '';
291
res.on('data', chunk => responseData += chunk);
292
res.on('end', () => {
293
const result = {
294
statusCode: res.statusCode,
295
headers: res.headers,
296
data: responseData
297
};
298
299
if (res.headers['content-type']?.includes('application/json')) {
300
try {
301
result.data = JSON.parse(responseData);
302
} catch (e) {
303
// Keep as string if not valid JSON
304
}
305
}
306
307
resolve(result);
308
});
309
});
310
311
req.on('error', reject);
312
313
if (data) {
314
req.write(JSON.stringify(data));
315
}
316
317
req.end();
318
});
319
}
320
321
async get(endpoint, headers) {
322
return this.request('GET', endpoint, null, headers);
323
}
324
325
async post(endpoint, data, headers) {
326
return this.request('POST', endpoint, data, headers);
327
}
328
329
async put(endpoint, data, headers) {
330
return this.request('PUT', endpoint, data, headers);
331
}
332
333
async delete(endpoint, headers) {
334
return this.request('DELETE', endpoint, null, headers);
335
}
336
337
destroy() {
338
this.agent.destroy();
339
}
340
}
341
342
// Usage
343
const apiClient = new TunneledAPIClient({
344
host: 'bastion.company.com',
345
username: 'developer',
346
privateKey: privateKey
347
}, 'https://internal-api.corp');
348
349
async function demonstrateAPI() {
350
try {
351
// GET request
352
const users = await apiClient.get('/users');
353
console.log('Users:', users.data);
354
355
// POST request
356
const newUser = await apiClient.post('/users', {
357
name: 'John Doe',
358
email: 'john@company.com'
359
});
360
console.log('Created user:', newUser.data);
361
362
// PUT request
363
const updated = await apiClient.put(`/users/${newUser.data.id}`, {
364
name: 'John Smith'
365
});
366
console.log('Updated user:', updated.data);
367
368
// DELETE request
369
await apiClient.delete(`/users/${newUser.data.id}`);
370
console.log('User deleted');
371
372
} catch (error) {
373
console.error('API error:', error);
374
} finally {
375
apiClient.destroy();
376
}
377
}
378
379
demonstrateAPI();
380
```
381
382
### WebSocket Tunneling
383
384
While not directly supported, WebSocket connections can be tunneled using the underlying connection:
385
386
```javascript
387
const { HTTPSAgent } = require('ssh2');
388
const WebSocket = require('ws');
389
390
function createTunneledWebSocket(sshConfig, wsUrl) {
391
const agent = new HTTPSAgent(sshConfig);
392
393
// Create WebSocket with custom agent
394
const ws = new WebSocket(wsUrl, {
395
agent: agent,
396
headers: {
397
'User-Agent': 'SSH2-WebSocket-Client'
398
}
399
});
400
401
return ws;
402
}
403
404
// Usage
405
const ws = createTunneledWebSocket({
406
host: 'tunnel-server.com',
407
username: 'user',
408
privateKey: privateKey
409
}, 'wss://internal-websocket.corp/socket');
410
411
ws.on('open', () => {
412
console.log('WebSocket connected through SSH tunnel');
413
ws.send(JSON.stringify({ type: 'hello', data: 'world' }));
414
});
415
416
ws.on('message', (data) => {
417
console.log('Received:', JSON.parse(data));
418
});
419
420
ws.on('close', () => {
421
console.log('WebSocket connection closed');
422
});
423
```
424
425
## Configuration Options
426
427
### SSH Connection Configuration
428
429
```javascript { .api }
430
interface ClientConfig {
431
// Connection details
432
host: string;
433
port?: number;
434
username: string;
435
436
// Authentication
437
password?: string;
438
privateKey?: Buffer | string;
439
passphrase?: string;
440
agent?: string | BaseAgent;
441
442
// Connection options
443
keepaliveInterval?: number;
444
readyTimeout?: number;
445
446
// Advanced options
447
algorithms?: AlgorithmList;
448
debug?: DebugFunction;
449
}
450
```
451
452
### HTTP Agent Options
453
454
```javascript { .api }
455
interface HTTPAgentOptions extends http.AgentOptions {
456
// Connection pooling
457
keepAlive?: boolean;
458
keepAliveMsecs?: number;
459
maxSockets?: number;
460
maxFreeSockets?: number;
461
462
// Timeouts
463
timeout?: number;
464
465
// Other options
466
scheduling?: 'lifo' | 'fifo';
467
}
468
469
interface HTTPSAgentOptions extends https.AgentOptions {
470
// All HTTP options plus TLS options
471
rejectUnauthorized?: boolean;
472
ca?: string | Buffer | Array<string | Buffer>;
473
cert?: string | Buffer;
474
key?: string | Buffer;
475
passphrase?: string;
476
ciphers?: string;
477
secureProtocol?: string;
478
}
479
```
480
481
## Error Handling
482
483
### Connection Error Handling
484
485
```javascript
486
const { HTTPAgent } = require('ssh2');
487
488
const agent = new HTTPAgent({
489
host: 'unreliable-server.com',
490
username: 'user',
491
privateKey: privateKey
492
}, {
493
timeout: 10000,
494
maxSockets: 5
495
});
496
497
// Handle agent errors
498
agent.on('error', (error) => {
499
console.error('Agent error:', error);
500
});
501
502
// Handle individual request errors
503
function makeRequest(options) {
504
return new Promise((resolve, reject) => {
505
const req = http.request({
506
...options,
507
agent: agent
508
}, (res) => {
509
// Handle response
510
resolve(res);
511
});
512
513
req.on('error', (error) => {
514
if (error.code === 'ECONNREFUSED') {
515
console.error('Target server refused connection');
516
} else if (error.code === 'ETIMEDOUT') {
517
console.error('Request timed out');
518
} else if (error.code === 'ENOTFOUND') {
519
console.error('Host not found');
520
}
521
reject(error);
522
});
523
524
req.on('timeout', () => {
525
req.destroy();
526
reject(new Error('Request timeout'));
527
});
528
529
req.end();
530
});
531
}
532
```
533
534
### Retry Logic
535
536
```javascript
537
class ResilientTunneledClient {
538
constructor(sshConfig, options = {}) {
539
this.sshConfig = sshConfig;
540
this.maxRetries = options.maxRetries || 3;
541
this.retryDelay = options.retryDelay || 1000;
542
this.recreateAgent();
543
}
544
545
recreateAgent() {
546
if (this.httpAgent) this.httpAgent.destroy();
547
if (this.httpsAgent) this.httpsAgent.destroy();
548
549
this.httpAgent = new HTTPAgent(this.sshConfig);
550
this.httpsAgent = new HTTPSAgent(this.sshConfig);
551
}
552
553
async requestWithRetry(options, retries = 0) {
554
try {
555
return await this.makeRequest(options);
556
} catch (error) {
557
if (retries < this.maxRetries && this.isRetryableError(error)) {
558
console.log(`Request failed, retrying in ${this.retryDelay}ms (attempt ${retries + 1}/${this.maxRetries})`);
559
560
// Recreate agents on connection errors
561
if (error.code === 'ECONNREFUSED' || error.code === 'ECONNRESET') {
562
this.recreateAgent();
563
}
564
565
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
566
return this.requestWithRetry(options, retries + 1);
567
}
568
throw error;
569
}
570
}
571
572
isRetryableError(error) {
573
const retryableCodes = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND'];
574
return retryableCodes.includes(error.code);
575
}
576
577
makeRequest(options) {
578
// Implementation using this.httpAgent or httpsAgent
579
}
580
581
destroy() {
582
if (this.httpAgent) this.httpAgent.destroy();
583
if (this.httpsAgent) this.httpsAgent.destroy();
584
}
585
}
586
```
587
588
## Performance Considerations
589
590
### Connection Pooling
591
592
```javascript
593
// Configure agents for optimal performance
594
const httpAgent = new HTTPAgent(sshConfig, {
595
keepAlive: true, // Reuse connections
596
keepAliveMsecs: 30000, // Keep alive for 30 seconds
597
maxSockets: 50, // Allow up to 50 concurrent connections
598
maxFreeSockets: 10, // Keep 10 idle connections
599
timeout: 60000 // 60 second timeout
600
});
601
602
// Monitor connection usage
603
console.log('Current connections:', httpAgent.getCurrentConnections());
604
console.log('Free connections:', httpAgent.getFreeSockets());
605
```
606
607
### Request Batching
608
609
```javascript
610
class BatchedTunneledClient {
611
constructor(sshConfig) {
612
this.agent = new HTTPSAgent(sshConfig, {
613
keepAlive: true,
614
maxSockets: 20
615
});
616
this.requestQueue = [];
617
this.processing = false;
618
}
619
620
async batchRequest(requests) {
621
// Process multiple requests concurrently
622
const promises = requests.map(req => this.makeRequest(req));
623
return Promise.allSettled(promises);
624
}
625
626
async makeRequest(options) {
627
return new Promise((resolve, reject) => {
628
const req = https.request({
629
...options,
630
agent: this.agent
631
}, resolve);
632
633
req.on('error', reject);
634
req.end();
635
});
636
}
637
}
638
```
639
640
## Type Definitions
641
642
### HTTP Agent Types
643
644
```javascript { .api }
645
import { Agent as HttpAgent } from 'http';
646
import { Agent as HttpsAgent } from 'https';
647
import { Socket } from 'net';
648
649
interface ClientConfig {
650
host: string;
651
port?: number;
652
username: string;
653
password?: string;
654
privateKey?: Buffer | string;
655
passphrase?: string;
656
agent?: string | BaseAgent;
657
[key: string]: any;
658
}
659
```