or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

http-tunneling.mdindex.mdkey-management.mdport-forwarding.mdsftp-operations.mdssh-agents.mdssh-client.mdssh-server.md

http-tunneling.mddocs/

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

```