You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
5.0 KiB
122 lines
5.0 KiB
5 years ago
|
# fd [![Build Status](https://secure.travis-ci.org/rvagg/node-fd.png)](http://travis-ci.org/rvagg/node-fd)
|
||
|
|
||
|
File descriptor manager for Node.js. *Available in npm as <strong>fd</strong>*.
|
||
|
|
||
|
**fd** manages `fs.open()` and `fs.close()` calls safely for you where there may be timing issues related to multiple-use of the same descriptor.
|
||
|
|
||
|
**fd** provides `checkin()` and `checkout()` functions so your application can register its intent to use a file descriptor after it's been opened and then register that it has finished with the descriptor so that any pending `fs.close()` operations may be performed.
|
||
|
|
||
|
**fd** naturally couples with [async-cache](https://github.com/isaacs/async-cache) to provide a safe pool of file descriptors.
|
||
|
|
||
|
## Example
|
||
|
|
||
|
Lets make a static resource web server! This example can be found in the *example/* directory of this repository.
|
||
|
|
||
|
We use [async-cache](https://github.com/isaacs/async-cache) to cache both `fs.sync()` calls and `fd`s, but we hook it up to **fd** so we can safely manage opens and closes.
|
||
|
|
||
|
```js
|
||
|
const fdman = require('fd')()
|
||
|
, http = require('http')
|
||
|
, fs = require('fs')
|
||
|
, path = require('path')
|
||
|
, AC = require('async-cache')
|
||
|
, mime = require('mime')
|
||
|
|
||
|
, ROOT = path.join(__dirname, 'public')
|
||
|
|
||
|
// an async cache for fs.stat calls, fresh for 10s
|
||
|
, statCache = AC({
|
||
|
max : 100
|
||
|
, maxAge : 10000
|
||
|
, load : function (path, callback) {
|
||
|
fs.stat(path, callback)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
// an async cache for fds, fresh for 10s
|
||
|
, fdCache = AC({
|
||
|
max : 100
|
||
|
, maxAge : 10000
|
||
|
// use fdman to open & close
|
||
|
, load : fdman.open.bind(fdman)
|
||
|
, dispose : fdman.close.bind(fdman)
|
||
|
})
|
||
|
|
||
|
, serveError = function (res) {
|
||
|
res.statusCode = 404
|
||
|
res.setHeader('content-type', 'text/plain')
|
||
|
res.end(http.STATUS_CODES[res.statusCode] + '\n')
|
||
|
}
|
||
|
|
||
|
http.createServer(function (req, res) {
|
||
|
var p = path.join(ROOT, req.url)
|
||
|
|
||
|
// get a fs.stat for this file
|
||
|
statCache.get(p, function (err, stat) {
|
||
|
if (err || !stat.isFile())
|
||
|
return serveError(res)
|
||
|
|
||
|
// get an fd for this file
|
||
|
fdCache.get(p, function (err, fd) {
|
||
|
var mimeType = mime.lookup(path.extname(p))
|
||
|
// get a safe checkin function from fdman that
|
||
|
// we could safely all multiple times for this single
|
||
|
// checkout
|
||
|
, checkin = fdman.checkinfn(p, fd)
|
||
|
|
||
|
// check out the fd for use
|
||
|
fdman.checkout(p, fd)
|
||
|
|
||
|
res.setHeader(
|
||
|
'content-type'
|
||
|
// don't force download, just show it
|
||
|
, mimeType != 'application/octet-stream' ? mimeType : 'text/plain'
|
||
|
)
|
||
|
|
||
|
// stream from the fd to the response
|
||
|
var st = fs.createReadStream(p, { fd: fd, start: 0, end: stat.size })n
|
||
|
.on('end', checkin)
|
||
|
.on('error', checkin)
|
||
|
|
||
|
// override destroy so we don't close the fd
|
||
|
st.destroy = function () {}
|
||
|
|
||
|
st.pipe(res)
|
||
|
|
||
|
})
|
||
|
})
|
||
|
}).listen(8080)
|
||
|
```
|
||
|
|
||
|
## API
|
||
|
|
||
|
### fd()
|
||
|
Create a new instance of **fd**. Typically called with `var fdman = require('fd')()`. You can have multiple, separate instances of **fd** operating at the same time, hence the need to instantiate.
|
||
|
|
||
|
### fdman.open(path, callback)
|
||
|
Equivalent to `fs.open(path, callback)`, you'll get back an `err` and `fd` parameters but the descriptor will go into the managed pool.
|
||
|
|
||
|
### fdman.close(path, fd)
|
||
|
Will call `fs.close(fd)` *only when the `fd` is no longer in use*. i.e. it will wait till all current uses have been checked in (see below).
|
||
|
|
||
|
### fdman.checkout(path, fd)
|
||
|
Called when your application may need to use the `fd`. This should be called as early as possible, even if your application may not end up using it.
|
||
|
|
||
|
It is important to perform a `checkout()` as soon as you have a reference to the file descriptor if you may be using it, otherwise an asynchronous call may interrupt and call `close()` before you use it. You *don't have to use the `fd`* to register your intent to use it, as long as you eventually call `checkin()`.
|
||
|
|
||
|
### fdman.checkin(path, fd)
|
||
|
Register with **fd** that you have finished using the descriptor and it can be safely closed if need be.
|
||
|
|
||
|
The descriptor may not need to be closed or there may be other uses of the descriptor currently checked out so a `checkin()` won't automatically lead to a `close()`.
|
||
|
|
||
|
### fdman.checkinfn(path, fd)
|
||
|
Returns a function that, when called, will safely perform a `checkin()` for you on the given path and descriptor. An important property of the function is that it will only perform a single `checkin()` regardless of how many times it is called.
|
||
|
|
||
|
This returned function is helpful for calling `checkin()` from multiple points in your application, such as in case of error, and you don't need to worry about whether it's been previously called for the current `checkout()`.
|
||
|
|
||
|
See the example above how this can be used.
|
||
|
|
||
|
|
||
|
## Licence
|
||
|
|
||
|
fd is Copyright (c) 2012 Rod Vagg [@rvagg](https://twitter.com/rvagg) and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details.
|