or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-send

Better streaming static file server with Range and conditional-GET support

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/send@1.2.x

To install, run

npx @tessl/cli install tessl/npm-send@1.2.0

0

# Send

1

2

Send is a Node.js library for streaming files from the file system as HTTP responses with comprehensive support for partial content delivery through HTTP Range requests, conditional-GET negotiation, and granular event handling. It provides configurable options for cache control, ETag generation, dotfile handling, and more.

3

4

## Package Information

5

6

- **Package Name**: send

7

- **Package Type**: npm

8

- **Language**: JavaScript (Node.js)

9

- **Installation**: `npm install send`

10

11

## Core Imports

12

13

```javascript

14

const send = require('send');

15

```

16

17

For ES modules:

18

19

```javascript

20

import send from 'send';

21

```

22

23

## Basic Usage

24

25

```javascript

26

const http = require('http');

27

const send = require('send');

28

29

const server = http.createServer(function(req, res) {

30

// Basic file serving

31

send(req, req.url, { root: '/public' })

32

.pipe(res);

33

});

34

35

server.listen(3000);

36

```

37

38

## Capabilities

39

40

### Main Factory Function

41

42

Creates a SendStream instance for streaming files to HTTP responses.

43

44

```javascript { .api }

45

/**

46

* Create a new SendStream for the given path to send to a response

47

* @param {object} req - HTTP request object

48

* @param {string} path - URL-encoded path to serve

49

* @param {object} [options] - Configuration options

50

* @returns {SendStream} SendStream instance

51

*/

52

function send(req, path, options);

53

```

54

55

**Usage Examples:**

56

57

```javascript

58

// Basic usage

59

send(req, '/index.html').pipe(res);

60

61

// With options

62

send(req, pathname, {

63

root: '/www/public',

64

index: ['index.html'],

65

dotfiles: 'deny',

66

maxAge: '1d'

67

}).pipe(res);

68

69

// With event handling

70

send(req, pathname, options)

71

.on('error', function(err) {

72

res.statusCode = err.status || 500;

73

res.end(err.message);

74

})

75

.on('directory', function(res, path) {

76

res.statusCode = 301;

77

res.setHeader('Location', req.url + '/');

78

res.end('Redirecting to ' + req.url + '/');

79

})

80

.pipe(res);

81

```

82

83

### SendStream Class

84

85

The main class returned by the send() factory function, providing file streaming capabilities.

86

87

```javascript { .api }

88

/**

89

* SendStream constructor (internal - use send() factory function)

90

* @param {object} req - HTTP request object

91

* @param {string} path - File path to serve

92

* @param {object} [options] - Configuration options

93

*/

94

class SendStream extends Stream;

95

```

96

97

#### Core Methods

98

99

##### pipe()

100

101

Pipes the file content to an HTTP response with full request processing including Range requests, conditional-GET, error handling, and header management.

102

103

```javascript { .api }

104

/**

105

* Pipe to response object

106

* @param {Stream} res - HTTP response object

107

* @returns {Stream} The response object

108

*/

109

SendStream.prototype.pipe(res);

110

```

111

112

##### error()

113

114

Emits error or sends HTTP error response with appropriate status code and HTML document.

115

116

```javascript { .api }

117

/**

118

* Emit error with status code

119

* @param {number} status - HTTP status code

120

* @param {Error} [err] - Optional error object

121

*/

122

SendStream.prototype.error(status, err);

123

```

124

125

#### Conditional Request Methods

126

127

##### isConditionalGET()

128

129

Checks if the request includes conditional-GET headers.

130

131

```javascript { .api }

132

/**

133

* Check if this is a conditional GET request

134

* @returns {boolean} True if conditional headers present

135

*/

136

SendStream.prototype.isConditionalGET();

137

```

138

139

##### isPreconditionFailure()

140

141

Validates If-Match and If-Unmodified-Since request headers against response ETag and Last-Modified.

142

143

```javascript { .api }

144

/**

145

* Check if the request preconditions failed

146

* @returns {boolean} True if preconditions failed

147

*/

148

SendStream.prototype.isPreconditionFailure();

149

```

150

151

##### isFresh()

152

153

Checks if the cached response is fresh using conditional headers.

154

155

```javascript { .api }

156

/**

157

* Check if the cache is fresh

158

* @returns {boolean} True if cache is fresh

159

*/

160

SendStream.prototype.isFresh();

161

```

162

163

##### isRangeFresh()

164

165

Validates If-Range header against ETag or Last-Modified for Range requests.

166

167

```javascript { .api }

168

/**

169

* Check if the range is fresh

170

* @returns {boolean} True if range is fresh

171

*/

172

SendStream.prototype.isRangeFresh();

173

```

174

175

#### Response Methods

176

177

##### notModified()

178

179

Sends a 304 Not Modified response and removes content headers.

180

181

```javascript { .api }

182

/**

183

* Respond with 304 not modified

184

*/

185

SendStream.prototype.notModified();

186

```

187

188

##### redirect()

189

190

Performs directory redirect or emits directory event.

191

192

```javascript { .api }

193

/**

194

* Redirect to path

195

* @param {string} path - Path to redirect to

196

*/

197

SendStream.prototype.redirect(path);

198

```

199

200

#### Utility Methods

201

202

##### hasTrailingSlash()

203

204

Checks if the pathname ends with a forward slash.

205

206

```javascript { .api }

207

/**

208

* Check if the pathname ends with "/"

209

* @returns {boolean} True if ends with slash

210

*/

211

SendStream.prototype.hasTrailingSlash();

212

```

213

214

##### isCachable()

215

216

Determines if the response status code is cacheable (2xx or 304).

217

218

```javascript { .api }

219

/**

220

* Check if the request is cacheable

221

* @returns {boolean} True if cacheable status code

222

*/

223

SendStream.prototype.isCachable();

224

```

225

226

## Configuration Options

227

228

```javascript { .api }

229

interface SendOptions {

230

/** Enable or disable accepting ranged requests (default: true) */

231

acceptRanges?: boolean;

232

233

/** Enable or disable setting Cache-Control response header (default: true) */

234

cacheControl?: boolean;

235

236

/** How to treat dotfiles: 'allow', 'deny', or 'ignore' (default: 'ignore') */

237

dotfiles?: string;

238

239

/** Byte offset at which the stream ends (for Range request processing) */

240

end?: number;

241

242

/** Enable or disable etag generation (default: true) */

243

etag?: boolean;

244

245

/** File extensions to try if file doesn't exist */

246

extensions?: string[];

247

248

/** Enable immutable directive in Cache-Control (default: false) */

249

immutable?: boolean;

250

251

/** Index file names or disable with false (default: ['index.html']) */

252

index?: string[] | string | boolean;

253

254

/** Enable or disable Last-Modified header (default: true) */

255

lastModified?: boolean;

256

257

/** Max-age in milliseconds for http caching (default: 0). Alternative: maxage */

258

maxAge?: number | string;

259

260

/** Root directory for relative paths */

261

root?: string;

262

263

/** Byte offset at which the stream starts (default: 0, for Range request processing) */

264

start?: number;

265

}

266

```

267

268

**Configuration Examples:**

269

270

```javascript

271

// Disable dotfiles and enable caching

272

const options = {

273

root: '/public',

274

dotfiles: 'deny',

275

maxAge: '1 day',

276

immutable: true

277

};

278

279

// Custom index files and extensions

280

const options = {

281

root: '/public',

282

index: ['index.html', 'default.html'],

283

extensions: ['html', 'htm']

284

};

285

286

// Range request configuration

287

const options = {

288

acceptRanges: true,

289

start: 100, // Start at byte 100

290

end: 1000 // End at byte 1000

291

};

292

```

293

294

## Events

295

296

The SendStream instance emits the following events:

297

298

```javascript { .api }

299

interface SendStreamEvents {

300

/** Error occurred during processing */

301

'error': (err: Error) => void;

302

303

/** Directory was requested */

304

'directory': (res: Response, path: string) => void;

305

306

/** File was requested and found */

307

'file': (path: string, stat: fs.Stats) => void;

308

309

/** Headers are about to be set on a file */

310

'headers': (res: Response, path: string, stat: fs.Stats) => void;

311

312

/** File streaming has started */

313

'stream': (stream: fs.ReadStream) => void;

314

315

/** Streaming has completed */

316

'end': () => void;

317

}

318

```

319

320

**Event Handling Examples:**

321

322

```javascript

323

send(req, pathname, options)

324

.on('error', function(err) {

325

// Handle errors (404, 403, 500, etc.)

326

res.statusCode = err.status || 500;

327

res.end(err.message);

328

})

329

.on('directory', function(res, path) {

330

// Handle directory requests

331

res.statusCode = 301;

332

res.setHeader('Location', req.url + '/');

333

res.end('Redirecting to ' + req.url + '/');

334

})

335

.on('file', function(path, stat) {

336

// Log file access

337

console.log('Serving file:', path, 'Size:', stat.size);

338

})

339

.on('headers', function(res, path, stat) {

340

// Customize headers before they're sent

341

if (path.endsWith('.pdf')) {

342

res.setHeader('Content-Disposition', 'attachment');

343

}

344

})

345

.on('stream', function(stream) {

346

// Handle streaming start

347

console.log('Started streaming file');

348

})

349

.on('end', function() {

350

// Handle streaming completion

351

console.log('Finished streaming file');

352

})

353

.pipe(res);

354

```

355

356

## Error Handling

357

358

Send provides comprehensive error handling with appropriate HTTP status codes:

359

360

```javascript { .api }

361

interface SendErrors {

362

/** 400 Bad Request - Malformed URI, null bytes */

363

BadRequest: 400;

364

365

/** 403 Forbidden - Malicious path, denied dotfiles, directory without trailing slash */

366

Forbidden: 403;

367

368

/** 404 Not Found - File not found, ignored dotfiles */

369

NotFound: 404;

370

371

/** 412 Precondition Failed - Conditional request preconditions failed */

372

PreconditionFailed: 412;

373

374

/** 416 Range Not Satisfiable - Invalid range request */

375

RangeNotSatisfiable: 416;

376

377

/** 500 Internal Server Error - File system errors, headers already sent */

378

InternalServerError: 500;

379

}

380

381

interface SendError extends Error {

382

/** HTTP status code */

383

status: number;

384

/** Error code */

385

code?: string;

386

/** File system path that caused the error */

387

path?: string;

388

}

389

```

390

391

**Error Handling Patterns:**

392

393

```javascript

394

// Automatic error handling (default)

395

send(req, pathname, options).pipe(res);

396

397

// Custom error handling

398

send(req, pathname, options)

399

.on('error', function(err) {

400

if (err.status === 404) {

401

// Serve custom 404 page

402

res.statusCode = 404;

403

res.end('Custom not found page');

404

} else {

405

// Handle other errors

406

res.statusCode = err.status || 500;

407

res.end(err.message);

408

}

409

})

410

.pipe(res);

411

```

412

413

## Advanced Usage

414

415

### Custom File Types

416

417

```javascript

418

const path = require('path');

419

420

send(req, pathname, { root: '/public' })

421

.on('headers', function(res, filePath) {

422

const ext = path.extname(filePath);

423

switch (ext) {

424

case '.x-custom':

425

res.setHeader('Content-Type', 'application/x-custom-type');

426

break;

427

}

428

})

429

.pipe(res);

430

```

431

432

### Directory Listing

433

434

```javascript

435

const fs = require('fs');

436

437

send(req, pathname, { index: false, root: '/public' })

438

.once('directory', function(res, dirPath) {

439

const stream = this;

440

441

// Redirect to trailing slash for consistent URLs

442

if (!stream.hasTrailingSlash()) {

443

return stream.redirect(dirPath);

444

}

445

446

// Custom directory listing

447

fs.readdir(dirPath, function(err, list) {

448

if (err) return stream.error(500, err);

449

450

res.setHeader('Content-Type', 'text/plain; charset=UTF-8');

451

res.end(list.join('\\n') + '\\n');

452

});

453

})

454

.pipe(res);

455

```

456

457

### Range Request Handling

458

459

```javascript

460

// Enable range requests for video streaming

461

send(req, pathname, {

462

root: '/media',

463

acceptRanges: true

464

})

465

.on('headers', function(res, path, stat) {

466

// Add custom headers for media files

467

if (path.match(/\\.(mp4|webm)$/)) {

468

res.setHeader('Accept-Ranges', 'bytes');

469

}

470

})

471

.pipe(res);

472

```

473

474

### Conditional Caching

475

476

```javascript

477

// Aggressive caching for static assets

478

send(req, pathname, {

479

root: '/static',

480

maxAge: '1 year',

481

immutable: true,

482

etag: true,

483

lastModified: true

484

})

485

.pipe(res);

486

```