Giter Club home page Giter Club logo

q-io's Introduction

Q-IO

npm version

Build Status

Interfaces for IO that make use of promises.

Q-IO now subsumes all of Q-HTTP and Q-FS.

The Q-IO package does not export a main module. You must reach in directly for q-io/fs, q-io/http, and q-io/http-apps.

Filesystem

var FS = require("q-io/fs");

File system API for Q promises with method signatures patterned after CommonJS/Fileystem/A but returning promises and promise streams.

open(path, options)

Open returns a promise for either a buffer or string Reader or a Writer depending on the flags.

The options can be omitted, abbreviated to a flags string, or expanded to an options object.

  • flags: r, w, a, b, default of r, not bytewise
  • charset: default of utf-8
  • bufferSize: in bytes
  • mode: UNIX permissions
  • begin first byte to read (defaults to zero)
  • end one past the last byte to read. end - begin == length

read(path, options)

read is a shortcut for opening a file and reading the entire contents into memory. It returns a promise for the whole file contents. By default, read provides a string decoded from UTF-8. With the bytewise mode flag, provides a Buffer.

The options argument is identical to that of open.

return FS.read(__filename, "b")
.then(function (content) {
    // ...
})
return FS.read(__filename, {
    flags: "b"
})

write(path, content, options)

write is a shortcut for opening a file and writing its entire content from a single string or buffer.

The options are identical to that of open, but the "w" flag is implied, and the "b" flag is implied if the content is a buffer.

return FS.write("hello.txt", "Hello, World!\n")
.then(function () {
    return FS.read("hello.txt")
})
.then(function (hello) {
    expect(hello).toBe("Hello, World!\n")
})

append(path, content, options)

append is a shorthand for opening a file for writing from the end of the existing content from a single string or buffer.

The options are identical to that of open, but the "w+" flags are implied, and the "b" flag is implied if the content is a buffer.

copy(source, target)

Copies a single file from one path to another. The target must be the full path, including the file name. Unlike at the shell, the file name is not inferred from the source path if the target turns out to be a directory.

Returns a promise for the completion of the operation.

copyTree(source, target)

Copies a file or tree of files from one path to another. Symbolic links are copied but not followed.

Returns a promise for the completion of the operation.

list(path)

Returns a promise for a list of file names in a directory. The file names are relative to the given path.

listTree(path, guard(path, stat))

Returns a promise for a list of files in a directory and all the directories it contains. Does not follow symbolic links.

The second argument is an optional guard function that determines what files to include and whether to traverse into another directory. It receives the path of the file, relative to the starting path, and also the stats object for that file. The guard must return a value like:

  • true indicates that the entry should be included
  • false indicates that the file should be excluded, but should still be traversed if it is a directory.
  • null indiciates that a directory should not be traversed.

listDirectoryTree(path)

Returns a promise for a deep list of directories.

makeDirectory(path, mode)

Makes a directory at a given path. Fails if the parent directory does not exist. Returns a promise for the completion of the operation.

The mode is an optional Unix mode as an integer or string of octal digits.

makeTree(path, mode)

Finishes a path of directories. For any branch of the path that does not exist, creates a directory. Fails if any branch of the path already exists but is not a directory.

Makes any directories with the given Unix mode.

remove(path)

Removes a file at the given path. Fails if a directory exists at the given path or if no file exists at the path.

removeTree(path)

Removes a file or directory at a given path, recursively removing any contained files and directories, without following symbolic links.

rename(source, target)

Moves a file or directory from one path to another using the underlying rename(2) implementation, thus it cannot move a file across devices.

move(source, target)

Moves a file or directory from one path to another. If the source and target are on different devices, falls back to copying and removing, using copyTree(source, target) and, if completely successful, removeTree(source).

link(source, target)

Creates a hard link from the source

symbolicCopy(source, target, type)

Creates a relative symbolic link from the target to the source with an effect that resembles copying a file.

The type is important for Windows. It is "file" by default, but may be "directory" or "junction".

symbolicLink(target, link, type)

Creates a symbolic link at the target path. The link may be absolute or relative. The type must be "file", "directory", or "junction" and is mandatory to encourage Windows portability.

chown(path, uid, gid)

Changes the owner for a path using Unix user-id and group-id numbers.

chmod(path, mode)

Changes the Unix mode for a path. Returns a promise.

stat(path)

Follows all symbolic links along a path and returns a promise for the metadata about a path as a Stats object. The Stats object implements:

  • size the size of the file in bytes
  • isDirectory(): returns whether the path refers to a directory with entries for other paths.
  • isFile(): returns whether the path refers to a file physically stored by the file system.
  • isBlockDevice(): returns whether the path refers to a Unix device driver, in which case there is no actual data in storage but the operating system may allow you to communicate with the driver as a blocks of memory.
  • isCharacterDevice(): returns whether the path refers to a Unix device driver, in which case there is no actual data in storage but the operating system may allow you to communicate with the driver as a stream.
  • isSymbolicLink(): returns whether the path refers to a symbolic link or junction. Stats for symbolic links are only discoverable through statLink since stat follows symbolic links.
  • isFIFO(): returns whether the path refers to a Unix named pipe.
  • isSocket(): returns whether the path refers to a Unix domain socket.
  • lastModified(): returns the last time the path was opened for writing as a Date
  • lastAccessed(): returns the last time the path was opened for reading or writing as a Date

statLink(path)

Returns a promise for the Stats for a path without following symbolic links.

statFd(fd)

Returns a promise for the Stats for a Unix file descriptor number.

exists(path)

Follows symbolic links and returns a promise for whether an entry exists at a given path.

isFile(path)

Follows symbolic links and returns a promise for whether a file exists at a given path and does not cause an exception if nothing exists at that path.

isDirectory(path)

Follows symbolic links and returns a promise for whether a directory exists at a given path and does not cause an exception if nothing exists at that path.

isSymbolicLink(path)

Returns a promise for whether a symbolic link exists at a given path and does not cause an exception if nothing exists at that path.

lastModified(path)

Follows symbolic links and returns a promise for the Date when the entry at the given path was last opened for writing, but causes an exception if no file exists at that path.

lastAccessed(path)

Follows symbolic links and returns a promise for the Date when the entry at the given path was last opened for reading or writing, but causes an exception if no file exists at that path.

split(path)

Splits a path into the names of entries along the path. If the path is absolute, the first component is either a drive (with a colon) on Windows or an empty string for the root of a Unix file system.

join(paths) or join(...paths)

Joins a sequence of paths into a single normalized path. All but the last path are assumed to refer to directories.

resolve(...paths)

Like join but treats each path like a relative URL, so a terminating slash indicates that a path is to a directory and the next path begins at that directory.

normal(...paths)

Takes a single path or sequence of paths and joins them into a single path, eliminating self . and parent .. entries when possible.

absolute(path)

Joins and normalizes a path from the current working directory, returning a string.

canonical(path)

Returns a promise for the absolute, canonical location of a given path, following symbolic links and normalizing path components. An entry does not need to exist at the end of the path.

readLink(path)

Returns a promise for the path string of a symbolic link at a given path.

contains(parent, child)

For any two absolute or relative paths, computes whether the parent path is an ancestor of the child path.

relative(source, target)

Returns a promise for the relative path from one path to another using .. parent links where necessary. This operation is asynchronous because it is necessary to determine whether the source path refers to a directory or a file.

relativeFromFile(source, target)

Assuming that the source path refers to a file, returns a string for the relative path from the source to the target path.

relativeFromDirectory(source, target)

Assuming that the source path refers to a directory, returns a string for the relative path from the source to the target path.

isAbsolute(path)

Returns whether a path begins at the root of a Unix file system or a Windows drive.

isRelative(path)

Returns whether a path does not begin at the root of a Unix file system or Windows drive.

isRoot(path)

Returns whether a path is to the root of a Unix file system or a Windows drive.

root(path)

Returns the Windows drive that contains a given path, or the root of a Unix file system.

directory(path)

Returns the path to the directory containing the given path.

base(path, extension)

Returns the last entry of a path. If an extension is provided and matches the extension of the path, removes that extension.

extension(path)

Returns the extension for a path (everything following the last dot . in a path, unless that dot is at the beginning of the entry).

reroot(path)

Returns an attenuated file system that uses the given path as its root. The resulting file system object is identical to the parent except that the child cannot open any file that is not within the root. Hard links are effectively inside the root regardless, but symbolic links cannot be followed outside of the jail.

toObject(path)

Reads every file in the file system under a given path and returns a promise for an object that contains the absolute path and a Buffer for each of those files.

glob(pattern)

Not yet implemented

match(pattern, path)

Not yet implemented

Mock Filesystem

Q-IO provides a mock filesystem interface. The mock filesystem has the same interface as the real one and has most of the same features, but operates on a purely in-memory, in-process, in-javascript filesystem.

A mock filesystem can be created from a data structure. Objects are directories. Keys are paths. A buffer is a file’s contents. Anything else is coerced to a string, then to a buffer in the UTF-8 encoding.

var MockFs = require("q-io/fs-mock");
var mockFs = MockFs({
    "a": {
        "b": {
            "c.txt": "Content of a/b/c.txt"
        }
    },
    "a/b/d.txt": new Buffer("Content of a/b/d.txt", "utf-8")
})

You can also instantiate a mock file system with the content of a subtree of a real file system. You receive a promise for the mock filesystem.

var FS = require("q-io/fs");
FS.mock(__dirname)
.then(function (fs) {
    //
})
.done();

HTTP

The HTTP module resembles CommonJS/JSGI.

var HTTP = require("q-io/http");

Server(app)

The http module exports a Server constructor.

  • accepts an application, returns a server.
  • calls the application function when requests are received.
    • if the application returns a response object, sends that response.
  • listen(port)
    • accepts a port number.
    • returns a promise for undefined when the server has begun listening.
  • stop()
    • returns a promise for undefined when the server has stopped.

request(request object or url)

The http module exports a request function that returns a promise for a response.

read(request object or url)

The http module exports a read function, analogous to Fs.read(path), but returning a promise for the content of an OK HTTP response.

  • accepts a request object or a URL string.
  • returns a promise for the response body as a string provided that the request is successful with a 200 status.
    • rejects the promise with the response as the reason for failure if the request fails.

normalizeRequest(request object or url)

  • coerces URLs into request objects.
  • completes an incomplete request object based on its url.

normalizeResponse(response)

  • coerces strings, arrays, and other objects supporting forEach into proper response objects.
  • if it receives undefined, it returns undefined. This is used as a singal to the requester that the responder has taken control of the response stream.

Request

A complete request object has the following properties.

  • url the full URL of the request as a string
  • path the full path as a string
  • scriptName the routed portion of the path, like "" for http://example.com/ if no routing has occurred.
  • pathInfo the part of the path that remains to be routed, like / for http://example.com or http://example.com/ if no routing has occurred.
  • version the requested HTTP version as an array of strings.
  • method like "GET"
  • scheme like "http"
  • host like "example.com" in case of default ports (80 or 443), otherwise like example.com:8080
  • hostname like "example.com"
  • port the port number, like 80
  • remoteHost
  • remotePort
  • headers corresponding values, possibly an array for multiple headers of the same name.
  • agent a custom node HTTP or HTTPS agent. HTTP and HTTPS agents can implement custom socket pools, allow use of SSL client certificates and self-signed certificates.
  • body an array of string or node buffers
  • timeout an optional socket timeout in miliseconds to thread through to the HTTP agent.
  • node the wrapped Node request object
  • cancelled a promise that indicates by rejection that the HTTP request should be aborted and the response promise should be rejected with the given error. Such a promise must not be fulfilled.

Response

A complete response object has the following properties.

  • status the HTTP status code as a number, like 200.
  • headers
  • body any forEachable, such as an array of strings
  • onclose is an optional function that this library will call when a response concludes.
  • node the wrapped Node response object.

headers

Headers are an object mapping lower-case header-names to corresponding values, possibly an array for multiple headers of the same name, for both requests and responses.

body

body is a representation of a readable stream, either for the content of a request or a response. It is implemented as a Q-IO reader.

  • forEach(callback)
    • accepts a callback(chunk) function
      • accepts a chunk as either a string or a Buffer
      • returns undefined or a promise for undefined when the chunk has been flushed.
    • returns undefined or a promise for undefined when the stream is finished writing.
    • the forEach function for arrays of strings or buffers is sufficient for user-provided bodies
  • the forEach function is the only necessary function for bodies provided to this library.
  • in addition to forEach, bodies provided by this library support the entire readable stream interface provided by q-io.
  • read()
    • returns a promise for the entire body as a string or a buffer.

application

An HTTP application is a function that accepts a request and returns a response. The request function itself is an application. Applications can be chained and combined to make advanced servers and clients.

  • accepts a request
  • returns a response, a promise for a response, or nothing if no response should be sent.

Streams

Reader

Reader instances have the following methods:

  • read()
  • forEach(callback)
  • close()
  • node the underlying node reader

Additionally, the Reader constructor has the following methods:

  • read(stream, charset) accepts any foreachable and returns either a buffer or a string if given a charset.
  • join(buffers) consolidates an array of buffers into a single buffer. The buffers array is collapsed in place and the new first and only buffer is returned.

The reader module exports a function that accepts a Node reader and returns a Q reader.

Writer

Writer instances have the following methods:

  • write(content) writes a chunk of content, either from a string or a buffer.
  • flush() returns a promise to drain the outbound content all the way to its destination.
  • close()
  • destroy()
  • node the underlying node writer

The writer module exports a function that accepts a Node writer and returns a Q writer.

Buffer

var BufferStream = require("q-io/buffer-stream");
var stream = BufferStream(new Buffer("Hello, World!\n", "utf-8"), "utf-8")

HTTP Applications

The HTTP applications module provides a comprehensive set of JSGI-alike applications and application factories, suitable for use with the http server and client.

var Apps = require("q-io/http-apps");

ok(content, contentType, status) : Response

Creates an HTTP 200 Ok response with the given content, content type, and status.

The content may be a string, buffer, array of strings, array of buffers, a readable stream of strings or buffers, or (generally) anything that implements forEach.

The default content type is text/plain.

The default status is 200.

badRequest(request) : Response

An application that returns an HTTP 400 Bad request response for any request.

notFound(request) : Response

An application that returns an HTTP 404 Not found response for any request.

methodNotAllowed(request) : Response

An application that returns an HTTP 405 Method not allowed response for any request. This is suitable for any endpoint where there is no viable handler for the request method.

notAcceptable(request) : Response

An application that returns an HTTP 406 Not acceptable response for any request. This is suitable for any situation where content negotiation has failed, for example, if you cannot response with any of the accepted encoding, charset, or language.

redirect(request, location, status, tree) : Response

Not to be confused with an HTTP application, this is a utility that generates redirect responses.

The returns response issues a redirect to the given location. The utility fully qualifies the location.

This particular method should be used directly to generate an HTTP 301 Temporary redirect response, but passing 307 in the status argument turns it into an HTTP 307 Permanent redirect response.

This particular method should be used to send all requests to a specific location, but setting the tree argument to true causes the redirect to follow the remaining unrouted path from the redirect location, so if you move an entire directory tree from one location to another, this redirect can forward to all of them.

redirectTree(request, location) : Response

Produces an HTTP 301 Temporary redirect from one directory tree to another, using redirect.

permanentRedirect(request, location) : Response

Produces an HTTP 307 Permanent redirect to a given location, using redirect.

permanentRedirectTree(request, location) : Response

Produces an HTTP 307 Permanent redirect from one directory tree to another, using redirect.

file(request, path, contentType) : Response

Produces an HTTP response with the file at a given path. By default, it infers the content type from the extension of the path.

The file utility produces an e-tag header suitable for cache control, and may produce an HTTP 304 Not modified if the requested resource has the same entity tag.

The file utility may produce an HTTP 206 Partial content response with a content-range header if the request has a range header. If the partial range request cannot be satisified, it may respond HTTP 416 Not satisfiable.

In all cases, the response body is streamed from the file system.

etag(stat)

Computes an entity tag for a file system Stats object, using the node.ino, size, and last modification time.

directory(request, path)

This is not yet implemented.

json(object, visitor, tabs) : Response

Returns an HTTP 200 Ok response from some JSON, using the same argumensts as JSON.stringify.

Content(body, contentType, status) : Application

A factory that produces an HTTP application that will always respond with the given content, content type, and status. The default content type is text/plain and the default status is 200.

The body may be a string, array of strings or buffers, or a readable stream of strings or buffers.

File(path, contentType) : Application

A factory that produces an HTTP application that will always respond with the file at the given path. The content type is inferred from the path extension by default, but can be overridden with contentType.

FileTree(path, options) : Application

A factory that produces an HTTP application that responds to all requests with files within a branch of the file system starting at the given path and using any unprocessed portion of the request location.

Options include:

  • notFound(request, response): alternate 404 responder, defaults to HttpApps.notFound
  • file(request, path, contentType, fs): alternate file responder, defaults to HttpApps.file
  • contentType: forces the content type of file requests, forwarded to the file handler.
  • directory(request, path, contentType, fs): alternate directory responder, defaults to HttpApps.directory.
  • redirectSymbolicLinks: directs the client to use a redirect response for symbolic links instead of following links internally.
  • permanent: symbolic links that are turned into HTTP redirects will be permanent.
  • followInsecureSymbolicLinks: directs FileTree to serve files that are outside the root path of the file tree if a symbolic link traverses there.
  • fs: alternate file system, defaults to the fs module.

Redirect(path) : Application

A factory that produces an HTTP application that temporarily redirects to the given path.

RedirectTree(path) : Application

A factory that produces an HTTP application that redirects all requests under the requested path to parallel locations at the given path.

PermanentRedirect(path) : Application

A factory that produces an HTTP application that redirects all requests to an exact location and instructs the requester's cache never to ask again.

PermanentRedirectTree(path) : Application

A factory that produces an HTTP application that redirects all requests under the request path to a parallel location under the given path and instructs the requester's cache never to ask again.

Cap(app, notFound) : Application

A factory that produces an HTTP application that will cause an HTTP 404 Not found response if the request has not reached the end of its route (meaning pathInfo is not "" or "/"), or will forward to the given application.

Routing

Several routing application factories have the same form. They all take an object as their first argument and an optional fallback application as their second. The object maps each of the supported options for keys to an HTTP application for handling that option.

  • Branch(paths, notFound) routes the next unprocessed path component
  • Method(methods, notFound) routes the HTTP method. Methods are upper-case.
  • ContentType(contentTypes, notAcceptable) routes based on the "accept" request header and produces a "content-type" response header.
  • Langauge(languages, notAcceptable) routes based on the "accept-language" header and produces a "language" response header.
  • Charaset(charsets, notAcceptable) routes based on the "accept-charset" header and produces a "charset" response header.
  • Encoding(encodings, notAcceptable) routes based on the "accept-encoding" request header and produces an "encoding" response header.
  • Host(hosts, notFound) routes based on the host name of the request "host" header, which defaults to "*". This is equivalent to virtual host mapping.

Select(selector) : Application

Produces an HTTP application that uses a function to determine the next application to route. The selector is a function that accepts the request and returns an HTTP application.

FirstFound(apps)

Returns an HTTP application that attempts to respond with each of a series of applications and returns the first response that does not have a 404 Not found status, or whatever response comes last.

Error(application, debug) : Application

Wraps an application such that any exceptions get converted into HTTP 500 Server error responses. If debug is enabled, produces the exception and stack traces in the body of the response.

Log(application, log, stamp) : Application

Wraps an application such that request and response times are logged. The log function reports to console.log by default. The stamp(message) function prefixes an ISO time stamp by default.

Time(application) : Application

Adds an x-response-time header to the response, with the time from receiving starting the request to starting the response in miliseconds.

Date(application) : Application

Adds a date header to the response with the current date for cache control purposes.

Tap(app, tap) : Application

Wraps an application such that the tap function receives the request first. If the tap returns nothing, the request goes to the app. If the tap returns a response, the app never gets called.

Trap(app, trap) : Application

Wraps an application such that the trap function receives the response. If it returns nothing, the response if forwarded. If the trap returns a response, the original response is discarded.

ParseQuery(application)

Wraps an application such that the query string is parsed and placed in request.parse.

Coverage

Use npm run cover to generate and view a coverage report of Q-IO.

File Percentage Missing
fs-boot.js 87% 41
fs.js 72% 100
reader.js 94% 8
writer.js 91% 8
fs-common.js 87% 52
fs-root.js 88% 11
fs-mock.js 91% 46
buffer-stream.js 89% 6
http.js 93% 25
http-apps.js 80% 286
http-cookie.js 79% 15

Copyright 2009–2013 Kristopher Michael Kowal MIT License (enclosed)

q-io's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

q-io's Issues

listTree performance

I noticed that listTree() was going slower than I expected for large numbers of files. Therefore, I decided to do some benchmarks to narrow down the slow down. As a result, it looks like an opportunity exists to close a performance gap in either Q or Q-IO. Performance becomes very noticeably affected in the benchmark code with Node-style callbacks versus Q promises, and therefore, that may be a good place to investigate optimization. HTH! More info and/or pull requests to follow, I hope.

Time information in seconds: lower Real time is better.

Test Paths Returned Real User Sys
require('q-io/fs').listTree('stuff') 96737 40.392 40.373 2.918
require('glob')('stuff/**', { dot:true }) 96737 12.873 12.955 2.431
syncListTree('stuff') 96737 3.302 2.203 1.103
asyncFindTree('stuff') 96737 3.175 3.290 2.563
asyncFindTreeQstat('stuff') 96737 18.138 18.064 2.769
asyncFindTreeQstatQreaddir('stuff') 96737 21.262 21.482 2.688
asyncFindTreeQ('stuff') 96737 27.629 27.736 3.027
find | stat 96737 1.776 0.619 2.962

Edit: I updated a wrong measurement for require('q-io/fs').listTree('stuff'). The correct measurement is ~40 seconds (not 35 seconds as I had written).

The vanilla sync and async times are very good. The shell find | stat result is also provided for a baseline reference outside of Node.js. I've also included the glob() tool, which looks like it could benefit from optimization as well. Our goal is to get Q and/or Q-IO almost as good as vanilla async.

Benchmark codes are:

require('q-io/fs').listTree('stuff')
.then(function(paths) {
  console.log('count =', paths.length);
});


require('glob')('stuff/**', { dot:true }, function(err, paths) {
  console.log('count =', paths.length);
});


var FS = require('fs');
var path = require('path');
function syncListTree(basePath) {
  var stack = [basePath];
  var result = [];
  while (stack.length) {
    var basePath = stack.pop();
    var stat = FS.statSync(basePath);
    if (stat.isDirectory()) {
      var list = FS.readdirSync(basePath);
      stack = stack.concat(list.map(function(x) { return path.join(basePath, x); }));
    }
    result.push(basePath);
  }
  return result;
}
console.log('count =', syncListTree('stuff').length);


var path = require('path');
var FS = require('fs');
function asyncFindTree(basePath, cb) {
  FS.stat(basePath, function(error, stat) {
    if (stat.isDirectory()) {
      FS.readdir(basePath, function(error, children) {
        children = children.map(function(child) { return path.join(basePath, child); });
        var count = children.length;
        var paths_ = [[basePath]];
        function checkCompletion() {
          if (count == 0) {
            var result = Array.prototype.concat.apply([], paths_);
            cb(null, result);
          }
        }
        checkCompletion(); // <-- for the case of children.length == 0
        children.forEach(function(child) {
          asyncFindTree(child, function(err, paths) {
            --count;
            paths_.push(paths);
            checkCompletion();
          });
        });
      });
    } else {
      cb(null, [basePath]);
    }
  });
}
asyncFindTree('stuff', function(error, paths) {
  console.log('count =', paths.length);
});


var Q = require('q');
var path = require('path');
var FS = require('fs');
function asyncFindTreeQstat(basePath, cb) {
  return Q.nfcall(FS.stat, basePath)
  .then(function(stat) {
    if (stat.isDirectory()) {
      FS.readdir(basePath, function(error, children) {
        children = children.map(function(child) { return path.join(basePath, child); });
        var count = children.length;
        var paths_ = [[basePath]];
        function checkCompletion() {
          if (count == 0) {
            var result = Array.prototype.concat.apply([], paths_);
            cb(null, result);
          }
        }
        checkCompletion(); // <-- for the case of children.length == 0
        children.forEach(function(child) {
          asyncFindTreeQstat(child, function(err, paths) {
            --count;
            paths_.push(paths);
            checkCompletion();
          });
        });
      });
    } else {
      cb(null, [basePath]);
    }
  });
}
asyncFindTreeQstat('stuff', function(error, paths) {
  console.log('count =', paths.length);
});


var Q = require('q');
var path = require('path');
var FS = require('fs');
function asyncFindTreeQstatQreaddir(basePath, cb) {
  return Q.nfcall(FS.stat, basePath)
  .then(function(stat) {
    if (stat.isDirectory()) {
      return Q.nfcall(FS.readdir, basePath)
      .then(function(children) {
        children = children.map(function(child) { return path.join(basePath, child); });
        var count = children.length;
        var paths_ = [[basePath]];
        function checkCompletion() {
          if (count == 0) {
            var result = Array.prototype.concat.apply([], paths_);
            cb(null, result);
          }
        }
        checkCompletion(); // <-- for the case of children.length == 0
        children.forEach(function(child) {
          asyncFindTreeQstatQreaddir(child, function(err, paths) {
            --count;
            paths_.push(paths);
            checkCompletion();
          });
        });
      });
    } else {
      cb(null, [basePath]);
    }
  });
}
asyncFindTreeQstatQreaddir('stuff', function(error, paths) {
  console.log('count =', paths.length);
});


var Q = require('q');
var path = require('path');
var FS = require('fs');
function asyncFindTreeQ(basePath) {
  return Q.nfcall(FS.stat, basePath)
  .then(function(stat) {
    if (stat.isDirectory()) {
      return Q.nfcall(FS.readdir, basePath)
      .then(function(children) {
        children = children.map(function(child) { return path.join(basePath, child); });
        return Q.all(children.map(function(child) {
          return asyncFindTreeQ(child)
        }))
        .then(function(paths_) {
          return Array.prototype.concat.apply([basePath], paths_);
        });
      });
    } else {
      return [basePath];
    }
  });
}
asyncFindTreeQ('stuff')
.then(function(paths) {
  console.log('count =', paths.length);
});
time find -L stuff | xargs stat --format '%Y :%y %n' | wc --lines

http.request() support socket pooling configuration?

Is there a way to control socket pooling via http.request()?

Will it just inherit nodejs' global http.agent config? Ex:
require('http').globalAgent.maxSockets = 10;

Somewhat related question - are there any "official" code examples of using require("q-io/http").request?

thanks for this and Q BTW. Awesome libs.

Specify Q as a peer dependency

Instead of specifying Q as a regular dependency, if you use peer dependency:

  "peerDependencies": {
    "q": ">=0.9.7 <1.1"
  },

then installation of Q-IO won't install another copy of Q, but will reuse parent project's copy of Q.

This will make versions of promises consistent across Q and Q-IO, avoiding gotcha in kriskowal/q#519

Feature: Make q-io/fs more graceful (workaround EMFILE errors)

I would like to request consideration of using graceful-fs rather than fs, in particular to avoid EMFILE errors like this one.

Here is one thought about how to implement this:

var FS = require('q-io/fs'); // <-- if user wants standard fs
var GFS = require('q-io/graceful-fs'); // <-- if user wants graceful-fs

Alternatively, would it be feasible to implement "graceful" functionality directly in q-io/fs? (by retrying operations that fail with EMFILE, for instance, like graceful-fs does)

(Btw, thanks for this awesome library. It's terrific!)

Create Collections documentation micro-site

It should be easier to look up what collections implement a method and what methods are implemented by a collection. Create a micro-site that makes it easy to browse in both dimensions.

delete with files with wildcard

Hello
Is there a way for deleting files with wild cards with using q-io ?
Tried remove function but does no seem to support wildcards
Did I missed something ?
Thanks

Canonical should accept non-existing paths

The reroot implementation assumes that canonical will expand any symbolic links along a path, but will just normalize any trailing terms that do not exist.

Canonical will need to be reimplemented in terms of lstat and realpath.

Alternately, reroot may need to be reimplemented in terms of the existing canonical. reroot needs to be weary of relative paths that would escape the jail.

proposed change to copyTree() to allow pre-existing target directories

The CommonJS/Fileystem/A spec is vague about various behavior (unless there's standard expected behavior I'm not aware of), but having copyTree() fail because makeDirectory() fails due to an existing target directory, seems unfortunate and inconsistent with the behavior at the file level, which just overwrites existing target files (as long as they're writable).

I propose changing copyTree() to allow for preexisting directories. A less desirable (IMO) alternative would be to leave copyTree() as is and add this as, say, "mergeTree()".

  exports.copyTree = function(source, target) {
    var self = this;
    return Q.when(self.stat(source), function(stat) {
      if (stat.isFile()) {
        return self.copy(source, target);
      } else if (stat.isDirectory()) {
        return self.exists(target).then(function(dstExists) {
          var copySubTree = Q.when(self.list(source), function(list) {
            return Q.all(list.map(function(child) {
              return self.copyTree(self.join(source, child), self.join(target, child));
            }));
          });
          if (dstExists) {
            return copySubTree;
          } else {
            return Q.when(self.makeDirectory(target), function() {
              return copySubTree;
            });
          }
        });
      } else if (stat.isSymbolicLink()) {
        return self.symbolicCopy(source, target);
      }
    });
  }; 

HTTP POST w/ body troubles

I can do a post without a body, ei, basic auth. when i add a body i run into lots of problems (post stalls, or doesn't get sent or get's sent but doesn't make sense)

var api_login_req = {
            url: urls.api.session.post,
            headers: _.pick(req.headers, 'authorization'),
            method: "POST",
            body:"osdijfoisdjfosdf"
        };

        return HTTP.
        request(api_login_req).

the body in the above code causes this request to stall, it doesn't get sent at all. when i comment out the body eveything is ok.

looking in the Qhttp code i see that that body needs a forEach. however i'm not sure if Q.when(req.body... would transform it into something with a forEach... anyway i changed my body into this:

var api_login_req = {
            url: urls.api.session.post,
            headers: _.pick(req.headers, 'authorization'),
            method: "POST",
            body:["osdijfoisdjfosdf"]
        };

        return HTTP.
        request(api_login_req).

and my post gets sent. however on the receiving end (using connect, w/ bodyParser) my req.body is empty.

it would be great to have an example of how to use this function.

move(src,tar) does not work between network shares

You have to manually replace
move(src, tar)
by
copyTree(src, tar)
removeTree(src, tar)
since you are intended to use it with different network shares.

best regards and thanks

-------occurrence------------

  • windows 7
  • node 0.8

Need FS.walk(path, cb)

Need an FS.walk(path, cb) to permit a file system traversal to stream results. It will need to specifically use Q.fcall to send a message to a remote promise.

Progress on file reader

The file reader returned by FS.open should have an observable progress measurement. This would involve a Node.js fstat to get the size and assign it to the stream’s length.

Via #39

Cannot serve binaries with Apps.file

When I try to serve images with Apps.file they are encoded with UTF-8 (and stops being images).

This is because FS.open is called without flags or charset: https://github.com/kriskowal/q-io/blob/master/http-apps/fs.js#L128 and the default behavior for FS.open is to use charset UTF-8 when no flags are set https://github.com/kriskowal/q-io/blob/master/fs.js#L79 which in turn makes the reader join the results with .join("") https://github.com/kriskowal/q-io/blob/master/reader.js#L47.

Image serving worked before, but default behavior for FS.open was changed some while ago (by mistake?) without any comment fbb2177#L5R80.

This could be fixed by either revert the default behavior of FS.open or by adding a binary flag to its use in Apps.file.

MockFs.move inconsistency

Given and existing directory myDirectory
Doing

fs.move("myDirectory", "myDirectory")

will throw "Can't move over existing entry myDirectory"
where as doing

MockFs.move("myDirectory", "myDirectory")

will succeed.

I'm unsure which one is correct. The fs behavior is strictly correct and might catch potential errors earlier but the mock behavior is much more convenient!

I tried simply removing the equality checks @ line 271 and 303 but it cause some test failures for symlink handling.

Reader does not include `node`

The docs say that the Node.JS Stream is available on the Reader and Writer objects as the node attribute, but they don't have that.

It would be really nice if the Reader and Writer could be drop-in supersets of the Node 0.10 streams. I think it would only mean renaming read() to slurp() and exposing the underlying Stream methods as well as the JSGI forEach() and Q .next() methods.

(Actually, perhaps the JSGI forEach should be a decorator added by the JSGI framework when it encounters a stream object)

HTTP does not handle non-utf8 charsets

I encountered some issues trying to use q-io/http as a client of sites using latin1 character set:

  1. The documentation of the charset option is ambiguous in the context of http client. It could mean
    a. either the character set in which the response body will be returned (possibly after iconv) or
    b. the character set used by the server as the response (this seems to be implicitly the interpretation used by q-io http).

    I would certainly wish (a) would be the case, because I want to have the input I get in a consistent character set. Furthermore (b) is problematic because, in general, I have no way of knowing what character set a server will return until I see the content-type response header.

  2. In my tests, q-io silently fails when a character set other than utf-8 is used as an option. There may be a deeper issue with the NodeReader

Make HTTP requests cancelable

There is not presently a way to abort an outstanding HTTP request. This would require a mechanism for canceling a promise, which is not yet supported by Q.

rerooted FS missing some functions

I'm not sure if I'm doing it wrong (an example in the docs would be great), but when I simply reroot() a FS the rerooted version is missing some functions, e.g.:

var FS = require('q-io/fs');

function checkFuncs(obj) {
    checkFunc(obj, 'makeDirectory');
    checkFunc(obj, 'makeTree');
}
function checkFunc(obj, name) {
    console.log(name + ": " + (obj[name]?"exists":"missing"));
}

console.log("Main FS:");
checkFuncs(FS);

FS.reroot('/')
.then(function(fs) {
    console.log("Rerooted FS:");
    checkFuncs(fs);
});

produces:

$ node rerooted_test.js 
Main FS:
makeDirectory: exists
makeTree: exists
Rerooted FS:
makeDirectory: missing
makeTree: exists

This causes problems in places because if I call makeTree() on a rerooted FS it fails because it will eventually call this.makeDirectory() internally (which isn't a function).

So am I doing something wrong or is this just a bug?

lastModified() generates an unhandled rejected promise warning

lastModified() generates an unhandled rejected promise warning on program exist nevertheless the call has .fail() handler. Example to reproduce:

var QFS = require('q-io/fs');

QFS.lastModified('/nonexistent')
.fail(function(err) {
    console.log('error caught!');
});

Confused about json apps

There are HandleJsonResponse(s), Json and json http apps, and it's unclear how they should be used.

this.GET('foo').json().app(gimmeAnObject) stringifies and ok's but it doesn't add the json mime type. handleJsonResponse stringifies and adds the mime type but doesn't ok.

Tell me how it should be done and I'll fix the doc.

Http server very slow

Hi,

I just got into the whole promise/generator issue and tried q-io/http like so:

var http = require("q-io/http");
var Apps = require("q-io/http-apps");

http.Server(function(req, res) {
return Apps.ok("Hello");
}).listen(8080);

Now I did a very small and simple http benchmark using wrk and boom against:

var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(8080, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8080/');

While I know that this not even remotely a serious test, I did notice a real difference.

Using the callback-hell version I got around 11200 req/sec and using q-io/http I got about 350 req/s. That's a couple of magnitudes slower.

Now, the difference can have multiple reasons. First, in the q-io/http version there is a lot more js involved and js is slow. I guess the callback version is so fast because basically there is almost no javascript involved and so it's a libuv benchmark basically.
Or it is some internal q-io stuff which takes some time.

Maybe you could help me solving this dilemma ;)

MockFS.write doesn't truncate before writing.

Here's a modification to the existing test case which reveals the bug:

    it("should write a file to a mock filesystem", function () {
        return FS.mock(FS.join(__dirname, "fixture"))
        .then(function (mock) {

            return Q.fcall(function () {
                return mock.write("hello.txt", "Goodbye!\n");
            })
            .then(function () {
                return mock.read("hello.txt");
            })
            .then(function (contents) {
                expect(contents).toBe("Goodbye!\n");
            });
        });
    });

The result:

> jasmine-node spec

.....................................F........................................

Failures:

  1) write should write a file to a mock filesystem
   Message:
     Expected 'Hello, World!
Goodbye!
' to be 'Goodbye!
'.

q-io/fs's write replaces the content, but q-io/fs-mock's write appends

var fs = require('q-io/fs');

fs.write('dummy', '123').then(function() {
    return fs.write('dummy', '456')
}).then(function() {
    return fs.read('dummy');
}).then(function(content) {
    console.log('content', content);
    // '456'
});


var MockFs = require('q-io/fs-mock');
fs = MockFs({
    'dummy2': '123'
});
fs.write('dummy2', '456').then(function() {
    return fs.write('dummy2', '789');
}).then(function() {
    return fs.read('dummy2');
}).then(function(content) {
    console.log('content mock', content);
    // '123456789'
});

odd behavior with copyTree

followup to #75

I put up a repository here that replicates behavior i'm seeing that I can't track down for the life of me. Every time I try to copy the directory in the reposiroty via copyTree the resulting directory contains only a subset of the files and the promise never seems to fall through to either the fail or success case.

This is probably something obvious and i'm just overtired, but it's killing me.

You can run the test with node test.js which will just do the copy. Running sh test.sh will remove the targetDir, run the test, and then compare the resulting directories.

My output :

$ sh test.sh
Copying from /Users/jsoverson/development/src/qiotest/sourcedir to /Users/jsoverson/development/src/qiotest/targetDir
Only in sourceDir/js/vendor/ace/build/src: mode-properties.js
Only in sourceDir/js/vendor/ace/build/src: mode-python.js
Only in sourceDir/js/vendor/ace/build/src: mode-r.js
[... cut ...]
Only in sourceDir/js/vendor/ace/build/src: worker-lua.js
Only in sourceDir/js/vendor/ace/build/src: worker-php.js
Only in sourceDir/js/vendor/ace/build/src: worker-xquery.js
files in sourceDir : 265
files in targetDir : 198

If you move or remove enough files so there are 198 or less in sourceDir then the operation will succeed every time.

Tested and saw this same unexpected behavior on:
MBA w/ OSX 10.9.1 - node 0.10.15
MBP w/ OSX 10.8.5 - node 0.10.17

Tested and saw normal, expected behavior on:
Ubuntu 13.10 - node 0.10.15

Proxy-fs

I want to be able to instanciate a proxy fs where the proxy loads and caches all lower level FS data as methods are called on the proxy object.

I want to then serialize the proxy data to a file and later instanciate a mock-fs object with it.

The idea is that I can pass a virtual FS object to a library, record all FS data needed while library executes and on second run feed the cached FS to the library instead of a real FS.

How would this fit into the q-io ecosystem?

FS.move and FS.rename

We currently expose the low level system call rename(2) as move. This does not move over file systems. The current implementation should be renamed to rename and a move command created composing copyTree and removeTree, perhaps only if rename fails because of a device boundary.

Re montagejs/minit#42

req.headers.host do not include port number when using q-io('http')

Hey guys,

I have a problem with q-io/http as I'm sending new request within the requested resource but req.headers.host turns out to be 'localhost' within it instead of 'localhost|:3000'. The request does actually get sent to the correct URL, but the Express request object does not contain the correct headers when it ends up there.

For reference, the behavior does work as expected with require('request'). So I'm switching there for now, if I can not find a fix myself.

Solve request body copy cancellation problems

In HTTP server requests, we wrap the response.body with Reader, then copy it to the NodeWriter(nodeRequest) to transfer it to the Node.js stack. This caused problems with hanging connections and processes (particularly our own test processes), if the user does not consume or cancel the request.body.

I made an attempt to address the issue by canceling the pipe, but this was foolish, since it broke transfers that exceeded one chunk.

In v2, request.body will have to explicitly cancelled if unneeded, and the tests need to accommodate this. The implicit cancel needs to be removed and a new Q-IO published. This impacts ASAP.

Reader.forEach() does not support thisp

The signature of Reader.forEach() is not consistent with the same method in the BufferStream class. The latter supports a second argument "thisp", the former does not.

Document that there is no main module

Per #6, there is an expectation that q-io exports a main module. Update the documentation to make it more clear that there is no main module, and the names of the public modules in the Q-IO namespace, q-io/fs, q-io/http, and q-io/http-apps.

Incorporate iconv

In v2, I would like to move away from accepting charset options on HTTP requests and file openers, or expand the range of accepted charsets using iconv. Regardless, I would like the internal and external interface for encoding, decoding, and transcoding should be revealed with intermediate streams, establishing a pipe or and pipeThrough convention.

Recommendations for hitting file limit on copyTree?

I'm not seeing a way to manage a directory copy with copyTree() where the number of open files may exceed the OS limit.

If i'm missing something, please let me know.

Is there a way to limit the number of copies happening at once?

Empty file in FS.stat or FS.read directly after FS.write

I have a weird case where a FS.write succeeds but the next FS.stat or FS.read on that file return zero-size (or empty file content). When I open the file in my editor I do see the expected content.

Of course usually you wouldn't read same file instantly after you write it but this happens in my unit tests.

This only happens if the call to FS.stat or FS.read is very close behind the FS.write.

If I replace FS.stat with a node.js wrapper, like: Q.nfcall(fs.stat, file) I get the same behaviour.

If I put a some other promises between the write and reads (like a bunch chaining then()'s with some FS.stats on same file) then later the file checks out correctly.

If I replace those filler FS.stats with a Q.delay(100) and then check I do get the empty file again.

So it looks like there is something going on with the filesystem: is there some sort of stat caching behaviour going on in Q-io or Node?

This is in node 0.10.17, on Windows vista 64 and also on a Ubuntu VM using Vagrant.

How can I copy a stream using promises?

I have a readable stream that I would normally pipe into a writable stream.

readable.pipe(writable);

How can I achieve the same "copying" of streams using q-io?

Undefnied agent causes error in node's HTTP module

HTTP read() function fails when given string URLs:

 require('q-io/http').read('http://example.com').done()

TypeError: Cannot read property 'defaultPort' of undefined
at new ClientRequest (_http_client.js:50:54)
at Agent.request (_http_agent.js:301:10)
at Object.exports.request (http.js:52:22)
at ./node_modules/q-io/http.js:295:29
at _fulfilled (./node_modules/q/q.js:798:54)
at self.promiseDispatch.done (./node_modules/q/q.js:827:30)
at Promise.promise.promiseDispatch (./node_modules/q/q.js:760:13)
at ./node_modules/q/q.js:821:14
at flush (./node_modules/q/q.js:108:17)
at process._tickCallback (node.js:598:11)

This may be because node doesn't handle undefined agent:

 require('http').request({agent:undefined})

TypeError: Cannot read property 'defaultPort' of undefined
at new ClientRequest (_http_client.js:50:54)
at Agent.request (_http_agent.js:301:10)
at Object.exports.request (http.js:52:22)
at repl:1:17

Node v0.11.9 on OS X (via homebrew), q-io 1.10.6.

Add listTreeStream()

Provide a streaming version of listTree(), returns a stream of paths to be consumed on demand via a Reader. Implement in v2.

open() and copy() misbehave if the destination file is readonly

This seems to be happening because Writer(), in writer.js, is returning Q(self) instead of a deferred 'begin' promise, as Reader() does.

If I amend Writer, as below, using the open event, then it will work correctly for both writable and non-writable files (unit-tests pass). However, as far as I am aware HTTP streams do not emit the open event, so I'm not sure if this "fix" is the correct approach - happy to look at some other approach if anyone can offers hints.

(When using the open event in the Reader(), HTTP unit-tests fail, which confirms that reading HTTP streams does not emit the open event. Given that the unit-tests pass above, I assume writing HTTP streams via unit-tests is not done or does not invoke Writer().)

?

function Writer(_stream, charset) {
    var self = Object.create(Writer.prototype);

    if (charset && _stream.setEncoding) // TODO complain about inconsistency
        _stream.setEncoding(charset);

    var begin = Q.defer();
    var drained = Q.defer();

    _stream.on("error", function (reason) {
        begin.reject(reason);
    });

    _stream.on("open", function () {
        begin.resolve(self);
    });

    _stream.on("drain", function () {
        drained.resolve();
        drained = Q.defer();
    });

    :.............. (rest of code)................

    return begin.promise;
    //return Q(self); // todo returns the begin.promise
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.