Better streaming static file server with Range and conditional-GET support
npx @tessl/cli install tessl/npm-send@1.2.00
# 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
```