diff --git a/ChangeLog b/ChangeLog index 26103fdd7d..a0ca933870 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,7 +18,7 @@ * Upgrade V8 to 2.0.6.1 - * Solaris port + * Solaris port (Erich Ocean) 2010.01.09, Version 0.1.25, 39ca93549af91575ca9d4cbafd1e170fbcef3dfa diff --git a/doc/api.txt b/doc/api.txt index 74a73b9db5..27cc1f14b6 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -6,7 +6,7 @@ Version, 0.1.26, 2010.01.20 == NAME -node - evented I/O for V8 javascript +node - evented I/O for V8 JavaScript @@ -39,7 +39,7 @@ Server running at http://127.0.0.1:8000/ Node supports 3 string encodings. UTF-8 (+"utf8"+), ASCII (+"ascii"+), and Binary (+"binary"+). +"ascii"+ and +"binary"+ only look at the first 8 bits -of the 16bit javascript string characters. Both are relatively fast--use +of the 16bit JavaScript string characters. Both are relatively fast--use them if you can. +"utf8"+ is slower and should be avoided when possible. Unless otherwise noted, functions are all asynchronous and do not block @@ -64,6 +64,9 @@ The search path for absolute path arguments to +require()+. +__filename+ :: The filename of the script being executed. ++__dirname+ :: +The dirname of the script being executed. + +module+ :: A reference to the current module (of type +process.Module+). In particular +module.exports+ is the same as the +exports+ object. See +src/process.js+ for @@ -79,7 +82,7 @@ more information. | +"exit"+ | +code+ | Made when the process exits. A listener on this event should not try to perform I/O since the process will forcibly exit in less - than microsecond. However, it is a good hook to + than a microsecond. However, it is a good hook to perform constant time checks of the module's state (like for unit tests). + @@ -212,7 +215,7 @@ in a promise callback. ---------------------------------------- var sys = require("sys"); sys.exec("ls /").addCallback(function (stdout, stderr) { - puts(stdout); + sys.puts(stdout); }); ---------------------------------------- + @@ -239,7 +242,7 @@ complete. ==== +events.EventEmitter+ -+require('events')+ to access the events module. ++require("events")+ to access the events module. All EventEmitters emit the event +"newListener"+ when new listeners are added. @@ -258,7 +261,7 @@ Adds a listener to the end of the listeners array for the specified event. + ---------------------------------------- server.addListener("connection", function (socket) { - puts("someone connected!"); + sys.puts("someone connected!"); }); ---------------------------------------- @@ -275,9 +278,9 @@ Execute each of the listeners in order with the supplied arguments. ==== +events.Promise+ -+require('events')+ to access the events module. ++require("events")+ to access the events module. -+events.Promise+ inherits from +process.eventEmitter+. A promise emits one of two ++events.Promise+ inherits from +process.EventEmitter+. A promise emits one of two events: +"success"+ or +"error"+. After emitting its event, it will not emit anymore events. @@ -304,7 +307,7 @@ If you created the promise (by doing +new events.Promise()+) then call the moment due to a bug; use +emitSuccess+ instead.) +promise.emitError(arg1, arg2, ...)+ :: -Emits the +"error"+ event. If a no error handler is attached to the promise +Emits the +"error"+ event. If no error handler is attached to the promise between +promise.emitError()+ and +process.nextTick()+, an exception is thrown. + @@ -348,10 +351,10 @@ setTimeout(function() { +promise.timeout(timeout = undefined)+ :: If the +timeout+ parameter is provided, the promise will emit an +"error"+ -event after the given amount of millseconds. The timeout is canceled by any -+"success"+ or +"error"+ event being emitted by the Promise. +event after the given amount of milliseconds. The timeout is canceled by any ++"success"+ or +"error"+ event being emitted by the promise. + -To tell apart a timeout from a regular "error" event, use the following test: +To tell a timeout apart from a regular "error" event, use the following test: + ---------------------------------------- promise.addErrback(function(e) { @@ -422,7 +425,7 @@ Close stdin. === Modules -Node uses the CommonJS module system +Node uses the CommonJS module system. Node has a simple module loading system. In Node, files and modules are in one-to-one correspondence. As an example, +foo.js+ loads the module @@ -495,14 +498,14 @@ Modules; see the section below about addons. +"index.js"+ allows one to package a module as a directory. +require.paths+ can be modified at runtime by simply unshifting new -paths on to it and at startup with the +NODE_PATH+ environmental +paths onto it, or at startup with the +NODE_PATH+ environmental variable (which should be a list of paths, colon separated). Use +process.mixin()+ to include modules into the global namespace. ---------------------------------------- process.mixin(GLOBAL, require("./circle"), require("sys")); -puts("The area of a cirlce of radius 4 is " + area(4)); +puts("The area of a circle of radius 4 is " + area(4)); ---------------------------------------- @@ -511,8 +514,8 @@ puts("The area of a cirlce of radius 4 is " + area(4)); === Timers -+setTimeout(callback, delay)+:: -To schedule execution of callback after delay milliseconds. Returns a ++setTimeout(callback, delay, [arg, ...])+:: +To schedule execution of +callback+ after +delay+ milliseconds. Returns a +timeoutId+ for possible use with +clearTimeout()+. Optionally, you can also pass arguments to the callback. @@ -522,8 +525,8 @@ Optionally, you can also pass arguments to the callback. Prevents said timeout from triggering. -+setInterval(callback, delay)+:: -To schedule the repeated execution of callback every +delay+ milliseconds. Returns ++setInterval(callback, delay, [arg, ...])+:: +To schedule the repeated execution of +callback+ every +delay+ milliseconds. Returns a +intervalId+ for possible use with +clearInterval()+. Optionally, you can also pass arguments to the callback. @@ -547,7 +550,7 @@ Node provides a tridirectional +popen(3)+ facility through the class | +"output"+ | +data+ | Each time the child process sends data to its +stdout+, this event is - emitted. +data+ is a string. + If the child + emitted. +data+ is a string. If the child process closes its +stdout+ stream (a common thing to do on exit), this event will be emitted with +data === null+. @@ -569,7 +572,7 @@ environmental variables. For example: ---------------------------------------- var ls = process.createChildProcess("ls", ["-lh", "/usr"]); ls.addListener("output", function (data) { - puts(data); + sys.puts(data); }); ---------------------------------------- + @@ -651,16 +654,21 @@ sys.puts("stats: " + JSON.stringify(stats)); - on error: no parameters. - +posix.stat(path)+ :: - See stat(2). - - on success: Returns +posix.Stats+ object. It looks like this: - +{ dev: 2049, ino: 305352, mode: 16877, nlink: 12, uid: 1000, gid: 1000, - rdev: 0, size: 4096, blksize: 4096, blocks: 8, atime: - "2009-06-29T11:11:55Z", mtime: "2009-06-29T11:11:40Z", ctime: - "2009-06-29T11:11:40Z" }+ - See the +posix.Stats+ section below for more information. - - on error: no parameters. +See stat(2). +- on success: Returns +posix.Stats+ object. It looks like this: ++ +------------------------------------------------------------------------------ + { dev: 2049, ino: 305352, mode: 16877, nlink: 12, uid: 1000, gid: 1000, + rdev: 0, size: 4096, blksize: 4096, blocks: 8, atime: + "2009-06-29T11:11:55Z", mtime: "2009-06-29T11:11:40Z", ctime: + "2009-06-29T11:11:40Z" }+ +------------------------------------------------------------------------------ ++ +See the +posix.Stats+ section below for more information. + +- on error: no parameters. + +posix.unlink(path)+ :: See unlink(2) @@ -673,11 +681,13 @@ sys.puts("stats: " + JSON.stringify(stats)); - on success: no parameters. - on error: no parameters. + +posix.mkdir(path, mode)+ :: See mkdir(2) - on success: no parameters. - on error: no parameters. + +posix.readdir(path)+ :: Reads the contents of a directory. - on success: One argument, an array containing the names (strings) of the @@ -706,25 +716,23 @@ sys.puts("stats: " + JSON.stringify(stats)); - on error: no parameters. +posix.read(fd, length, position, encoding)+:: - -Read data from the file specified by +fd+. -+ -+length+ is an integer specifying the number of -bytes to read. -+ -+position+ is an integer specifying where to begin -reading from in the file. -+ -- on success: returns +data, bytes_read+, what was read from the file. -- on error: no parameters. + Read data from the file specified by +fd+. + + + +length+ is an integer specifying the number of + bytes to read. + + + +position+ is an integer specifying where to begin + reading from in the file. + + + - on success: returns +data, bytes_read+, what was read from the file. + - on error: no parameters. +posix.cat(filename, encoding="utf8")+:: - Outputs the entire contents of a file. Example: + -------------------------------- posix.cat("/etc/passwd").addCallback(function (content) { - puts(content); + sys.puts(content); }); -------------------------------- + @@ -759,20 +767,22 @@ In particular, large, possibly chunk-encoded, messages. The interface is careful to never buffer entire requests or responses--the user is able to stream data. -HTTP message headers are represented by an object like this +HTTP message headers are represented by an object like this: ---------------------------------------- -{ "Content-Length": "123" -, "Content-Type": "text/plain" -, "Connection": "keep-alive" -, "Accept": "*/*" +{ "content-length": "123" +, "content-type": "text/plain" +, "connection": "keep-alive" +, "accept": "*/*" } ---------------------------------------- -In order to support the full spectrum of possible HTTP applications, Node"s +Keys are lowercased. Values are not modified. + +In order to support the full spectrum of possible HTTP applications, Node's HTTP API is very low-level. It deals with connection handling and message parsing only. It parses a message into headers and body but it does not -parse the actual headers or the body. +parse the actual headers or the body. ==== +http.Server+ @@ -799,12 +809,12 @@ parse the actual headers or the body. |========================================================= -+http.createServer(request_listener, options);+ :: ++http.createServer(request_listener, [options]);+ :: Returns a new web server object. + The +options+ argument is optional. The +options+ argument accepts the same values as the -options argument for +tcp.Server+ does. +options argument for +tcp.Server+. + The +request_listener+ is a function which is automatically added to the +"request"+ event. @@ -812,12 +822,12 @@ added to the +"request"+ event. +server.setSecure(format_type, ca_certs, crl_list, private_key, certificate)+ :: Enable TLS for all incoming connections, with the specified credentials. + -format_type currently has to be "X509_PEM", and each of the ca, crl, key and ++format_type+ currently has to be "X509_PEM", and each of the ca, crl, key and cert parameters are in the format of PEM strings. + -The ca_certs is a string that holds a number of CA certificates for use in accepting ++ca_certs+ is a string that holds a number of CA certificates for use in accepting client connections that authenticate themselves with a client certificate. -The private_key is a PEM string of the unencrypted key for the server. ++private_key+ is a PEM string of the unencrypted key for the server. +server.listen(port, hostname)+ :: Begin accepting connections on the specified port and hostname. @@ -842,7 +852,7 @@ the user--and passed as the first argument to a +"request"+ listener. message body is received. Example: A chunk of the body is given as the single argument. The transfer-encoding has been - decoded. The body chunk is a String. The + decoded. The body chunk is a string. The body encoding is set with +request.setBodyEncoding()+. @@ -858,7 +868,7 @@ The request method as a string. Read only. Example: +request.url+ :: Request URL string. This contains only the URL that is -present in the actual HTTP request. If the request is +present in the actual HTTP request. If the request is: + ---------------------------------------- GET /status?name=ryan HTTP/1.1\r\n @@ -866,7 +876,7 @@ Accept: text/plain\r\n \r\n ---------------------------------------- + -Then +request.url+ will be +Then +request.url+ will be: + ---------------------------------------- "/status?name=ryan" @@ -912,7 +922,7 @@ The HTTP protocol version as a string. Read only. Examples: +"1.1"+, +"1.0"+ -+request.setBodyEncoding(encoding)+ :: ++request.setBodyEncoding(encoding="binary")+ :: Set the encoding for the request body. Either +"utf8"+ or +"binary"+. Defaults to +"binary"+. @@ -937,7 +947,7 @@ passed as the second parameter to the +"request"+ event. +response.sendHeader(statusCode, headers)+ :: Sends a response header to the request. The status code is a 3-digit HTTP -status code, like +404+. The second argument, +headers+ are the response headers. +status code, like +404+. The second argument, +headers+, are the response headers. + Example: + @@ -1036,11 +1046,11 @@ for the user to stream a body to the server with +client.setSecure(format_type, ca_certs, crl_list, private_key, certificate)+ :: Enable TLS for the client connection, with the specified credentials. + -format_type currently has to be "X509_PEM", and each of the ca, crl, key and ++format_type+ currently has to be "X509_PEM", and each of the ca, crl, key and cert parameters are in the format of PEM strings, and optional. + -The ca_certs is a string that holds a number of CA certificates for use in deciding the -authenticity of the remote server. The private_key is a PEM string of the unencrypted ++ca_certs+ is a string that holds a number of CA certificates for use in deciding the +authenticity of the remote server. +private_key+ is a PEM string of the unencrypted key for the client, which together with the certificate allows the client to authenticate itself to the server. @@ -1095,7 +1105,7 @@ argument which is an instance of +http.ClientResponse+. + In the +responseListener+ callback, one can add more listeners to the response, in particular listening for the +"body"+ event. Note that -the +responseListener+ is called before any part of the body is receieved, +the +responseListener+ is called before any part of the body is received, so there is no need to worry about racing to catch the first part of the body. As long as a listener for +"body"+ is added during the +responseListener+ callback, the entire body will be caught. @@ -1104,7 +1114,7 @@ body. As long as a listener for +"body"+ is added during the // Good request.finish(function (response) { response.addListener("body", function (chunk) { - puts("BODY: " + chunk); + sys.puts("BODY: " + chunk); }); }); @@ -1112,7 +1122,7 @@ request.finish(function (response) { request.finish(function (response) { setTimeout(function () { response.addListener("body", function (chunk) { - puts("BODY: " + chunk); + sys.puts("BODY: " + chunk); }); }, 10); }); @@ -1241,7 +1251,7 @@ To use the TCP server and client one must +require("tcp")+. ==== +tcp.Server+ Here is an example of a echo server which listens for connections -on port 7000 +on port 7000: ---------------------------------------- var tcp = require("tcp"); @@ -1282,12 +1292,12 @@ the +"connection"+ event. +server.setSecure(format_type, ca_certs, crl_list, private_key, certificate)+ :: Enable TLS for all incoming connections, with the specified credentials. + -format_type currently has to be "X509_PEM", and each of the ca, crl, key and ++format_type+ currently has to be "X509_PEM", and each of the ca, crl, key and cert parameters are in the format of PEM strings. + -The ca_certs is a string that holds a number of CA certificates for use in accepting ++ca_certs+ is a string that holds a number of CA certificates for use in accepting client connections that authenticate themselves with a client certificate. -The private_key is a PEM string of the unencrypted key for the server. ++private_key+ is a PEM string of the unencrypted key for the server. +server.listen(port, host=null, backlog=128)+ :: Tells the server to listen for TCP connections to +port+ and +host+. @@ -1427,54 +1437,83 @@ in RFC2253. This function is synchronous. === DNS module -Use +require("dns")+ to access this module +Use +require("dns")+ to access this module. -Here is an example of which resolves +"www.google.com"+ then reverse +Here is an example which resolves +"www.google.com"+ then reverse resolves the IP addresses which are returned. ------------------------------------------------------------------------- -var dns = require("dns"); +var dns = require("dns"), + sys = require("sys"); var resolution = dns.resolve4("www.google.com"); resolution.addCallback(function (addresses, ttl, cname) { - puts("addresses: " + JSON.stringify(addresses)); - puts("ttl: " + JSON.stringify(ttl)); - puts("cname: " + JSON.stringify(cname)); + sys.puts("addresses: " + JSON.stringify(addresses)); + sys.puts("ttl: " + JSON.stringify(ttl)); + sys.puts("cname: " + JSON.stringify(cname)); for (var i = 0; i < addresses.length; i++) { var a = addresses[i]; var reversing = dns.reverse(a); reversing.addCallback( function (domains, ttl, cname) { - puts("reverse for " + a + ": " + JSON.stringify(domains)); + sys.puts("reverse for " + a + ": " + JSON.stringify(domains)); }); reversing.addErrback( function (code, msg) { - puts("reverse for " + a + " failed: " + msg); + sys.puts("reverse for " + a + " failed: " + msg); }); } }); resolution.addErrback(function (code, msg) { - puts("error: " + msg); + sys.puts("error: " + msg); }); ------------------------------------------------------------------------- ++dns.resolve(domain, rrtype = 'A')+:: -+dns.resolve4(domain)+:: - -Resolves a domain (e.g. +"google.com"+) into an array of IPv4 addresses (e.g. -+["74.125.79.104", "74.125.79.105", "74.125.79.106"]+). +Resolves a domain (e.g. +"google.com"+) into an array of the record types +specified by rrtype. Valid rrtypes are +A+ (IPV4 addresses), +AAAA+ (IPV6 +addresses), +MX+ (mail exchange records), +TXT+ (text records), +SRV+ +(SRV records), and +PTR+ (used for reverse IP lookups). This function returns a promise. - on success: returns +addresses, ttl, cname+. +ttl+ (time-to-live) is an integer specifying the number of seconds this result is valid for. +cname+ is the canonical name for the query. + The type of each item in +addresses+ is determined by the record type, and + described in the documentation for the corresponding lookup methods below. - on error: returns +code, msg+. +code+ is one of the error codes listed below and +msg+ is a string describing the error in English. ++dns.resolve4(domain)+:: + +The same as +dns.resolve()+, but only for IPv4 queries (+A+ records). ++addresses+ is an array of IPv4 addresses (e.g. +["74.125.79.104", +"74.125.79.105", "74.125.79.106"]+). + +dns.resolve6(domain)+:: The same as +dns.resolve4()+ except for IPv6 queries (an +AAAA+ query). ++dns.resolveMx(domain)+:: + +The same as +dns.resolve()+, but only for mail exchange queries (+MX+ records). ++addresses+ is an array of MX records, each with a priority and an exchange +attribute (e.g. +[{"priority": 10, "exchange": "mx.example.com"},...]+). + ++dns.resolveTxt(domain)+:: + +The same as +dns.resolve()+, but only for text queries (+TXT+ records). ++addresses+ is an array of the text records available for +domain+ (e.g., ++["v=spf1 ip4:0.0.0.0 ~all"]+). + ++dns.resolveSrv(domain)+:: + +The same as +dns.resolve()+, but only for service records (+SRV+ records). ++addresses+ is an array of the SRV records available for +domain+. Properties +of SRV records are priority, weight, port, and name (e.g., +[{"priority": 10, +{"weight": 5, "port": 21223, "name": "service.example.com"}, ...]+). + +dns.reverse(ip)+:: Reverse resolves an ip address to an array of domain names. @@ -1518,10 +1557,10 @@ Tests for deep equality. Tests for any deep inequality. +assert.strictEqual(actual, expected, message)+:: -Tests strict equality, as determined by bitwise equality operator ( +===+ ) +Tests strict equality, as determined by the strict equality operator ( +===+ ) +assert.notStrictEqual(actual, expected, message)+:: -Tests strict non-equality, as determined by bitwise not equal operator ( +!==+ ) +Tests strict non-equality, as determined by the strict not equal operator ( +!==+ ) +assert.throws(block, error, message)+:: Expects +block+ to throw an error. @@ -1533,7 +1572,7 @@ Expects +block+ not to throw an error. === Path Module This module contains utilities for dealing with file paths. Use -+require('path')+ to use it. It provides the following methods: ++require("path")+ to use it. It provides the following methods: +path.join(/* path1, path2, ... */)+:: Join all arguments together and resolve the resulting path. Example: @@ -1581,9 +1620,9 @@ node> require("path").dirname("/foo/bar/baz/asdf/quux") Return the last portion of a path. Similar to the Unix +basename+ command. Example: + ------------------------------------ -node> require("path").filename("/foo/bar/baz/asdf/quux.html") +node> require("path").basename("/foo/bar/baz/asdf/quux.html") "quux.html" -node> require("path").filename("/foo/bar/baz/asdf/quux.html", ".html") +node> require("path").basename("/foo/bar/baz/asdf/quux.html", ".html") "quux" ------------------------------------ + @@ -1650,7 +1689,7 @@ Either the "params" portion of the query string, or a querystring-parsed object. +"query=string"+ or +{"query":"string"}+ +hash+:: -The portion of the URL after the pound-sign. Example: +"#hash"+ +The "fragment" portion of the URL including the pound-sign. Example: +"#hash"+ The following methods are provided by the URL module: @@ -1697,10 +1736,10 @@ node> require("querystring").parse("foo=bar&baz%5Bquux%5D=asdf&baz%5Boof%5D=rab& ------------------------------------ + -+querystring.escape+ ++querystring.escape+:: The escape function used by +querystring.stringify+, provided so that it could be overridden if necessary. -+querystring.unescape+ ++querystring.unescape+:: The unescape function used by +querystring.parse+, provided so that it could be overridden if necessary. == REPL @@ -1746,7 +1785,7 @@ Addons are dynamically linked shared objects. They can provide glue to C and C++ libraries. The API (at the moment) is rather complex, involving knowledge of several libraries: - - V8 Javascript, a C++ library. Used for interfacing with Javascript: + - V8 JavaScript, a C++ library. Used for interfacing with JavaScript: creating objects, calling functions, etc. Documented mostly in the +v8.h+ header file (+deps/v8/include/v8.h+ in the Node source tree). diff --git a/lib/assert.js b/lib/assert.js index 70eda84ff7..4f6c46c9a7 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -47,6 +47,7 @@ assert.AssertionError = function AssertionError (options) { Error.captureStackTrace(this, stackStartFunction); } }; +process.inherits(assert.AssertionError, Error); assert.AssertionError.prototype.toString = function() { if (this.message) { diff --git a/lib/dns.js b/lib/dns.js index bfbfdf45c0..7aedab377c 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -10,6 +10,18 @@ function callback (promise) { } } +exports.resolve = function (domain, type) { + type = (type || 'a').toUpperCase(); + + var resolveFunc = resolveMap[type]; + + if (typeof(resolveFunc) == 'function') { + return resolveFunc(domain); + } else { + return undefined; + } +} + exports.resolve4 = function (domain) { var promise = new events.Promise(); process.dns.resolve4(domain, callback(promise)); @@ -22,6 +34,24 @@ exports.resolve6 = function (domain) { return promise; }; +exports.resolveMx = function (domain) { + var promise = new process.Promise(); + process.dns.resolveMx(domain, callback(promise)); + return promise; +}; + +exports.resolveTxt = function (domain) { + var promise = new process.Promise(); + process.dns.resolveTxt(domain, callback(promise)); + return promise; +}; + +exports.resolveSrv = function (domain) { + var promise = new process.Promise(); + process.dns.resolveSrv(domain, callback(promise)); + return promise; +} + exports.reverse = function (ip) { var promise = new events.Promise(); process.dns.reverse(ip, callback(promise)); @@ -47,3 +77,12 @@ exports.NOMEM = process.dns.NOMEM; // the query is malformed. exports.BADQUERY = process.dns.BADQUERY; + +resolveMap = { + 'A': exports.resolve4, + 'AAAA': exports.resolve6, + 'MX': exports.resolveMx, + 'TXT': exports.resolveTxt, + 'SRV': exports.resolveSrv, + 'PTR': exports.reverse, +}; diff --git a/lib/http.js b/lib/http.js index 32bec0d590..dcb152e987 100644 --- a/lib/http.js +++ b/lib/http.js @@ -418,6 +418,7 @@ exports.createClient = function (port, host) { var secure_credentials={ secure : false }; var requests = []; + var currentRequest; client._pushRequest = function (req) { req.addListener("flush", function () { @@ -434,7 +435,7 @@ exports.createClient = function (port, host) { return; } //sys.debug("client flush readyState = " + client.readyState); - if (req == requests[0]) flushMessageQueue(client, [req]); + if (req == currentRequest) flushMessageQueue(client, [req]); }); requests.push(req); }; @@ -451,7 +452,8 @@ exports.createClient = function (port, host) { client.addListener("connect", function () { client.resetParser(); - requests[0].flush(); + currentRequest = requests.shift(); + currentRequest.flush(); }); client.addListener("eof", function () { @@ -489,8 +491,7 @@ exports.createClient = function (port, host) { client.close(); }); - var req = requests.shift(); - req.emit("response", res); + currentRequest.emit("response", res); }); return client; diff --git a/lib/url.js b/lib/url.js index 71c9f9d14a..49c2e69a86 100644 --- a/lib/url.js +++ b/lib/url.js @@ -1,4 +1,3 @@ - exports.parse = url_parse; exports.resolve = url_resolve; exports.resolveObject = url_resolveObject; @@ -17,7 +16,7 @@ var protocolPattern = /^([a-z0-9]+:)/, "http:":true, "https:":true, "ftp:":true, "gopher:":true, "file:":true }, path = require("path"), // internal module, guaranteed to be loaded already. - querystring; // don't load unless necessary. + querystring = require('querystring'); function url_parse (url, parseQueryString) { if (url && typeof(url) === "object" && url.href) return url; @@ -75,9 +74,6 @@ function url_parse (url, parseQueryString) { out.search = rest.substr(qm); out.query = rest.substr(qm+1); if (parseQueryString) { - if (!querystring) { - querystring = require("querystring"); - } out.query = querystring.parse(out.query); } rest = rest.slice(0, qm); @@ -105,7 +101,7 @@ function url_format (obj) { search = obj.search || ( obj.query && ( "?" + ( typeof(obj.query) === "object" - ? require("querystring").stringify(obj.query) + ? querystring.stringify(obj.query) : String(obj.query) )) ) || "", diff --git a/src/node.js b/src/node.js index b905f17430..2ac3b54ca4 100644 --- a/src/node.js +++ b/src/node.js @@ -233,7 +233,7 @@ var eventsModule = createInternalModule('events', function (exports) { exports.Promise.prototype.emitSuccess = function() { if (this.hasFired) return; - this.hasFired = true; + this.hasFired = 'success'; this._clearTimeout(); this._values = Array.prototype.slice.call(arguments); @@ -242,7 +242,7 @@ var eventsModule = createInternalModule('events', function (exports) { exports.Promise.prototype.emitError = function() { if (this.hasFired) return; - this.hasFired = true; + this.hasFired = 'error'; this._clearTimeout(); this._values = Array.prototype.slice.call(arguments); @@ -261,7 +261,7 @@ var eventsModule = createInternalModule('events', function (exports) { }; exports.Promise.prototype.addCallback = function (listener) { - if (this.hasFired) { + if (this.hasFired === 'success') { return listener.apply(this, this._values); } @@ -269,7 +269,7 @@ var eventsModule = createInternalModule('events', function (exports) { }; exports.Promise.prototype.addErrback = function (listener) { - if (this.hasFired) { + if (this.hasFired === 'error') { listener.apply(this, this._values); } @@ -919,13 +919,13 @@ Module.prototype.loadScript = function (filename, loadPromise) { require.async = requireAsync; require.main = process.mainModule; // create wrapper function - var wrapper = "var __wrap__ = function (exports, require, module, __filename) { " + var wrapper = "var __wrap__ = function (exports, require, module, __filename, __dirname) { " + content + "\n}; __wrap__;"; try { var compiledWrapper = process.compile(wrapper, filename); - compiledWrapper.apply(self.exports, [self.exports, require, self, filename]); + compiledWrapper.apply(self.exports, [self.exports, require, self, filename, path.dirname(filename)]); } catch (e) { loadPromise.emitError(e); return; diff --git a/src/node_dns.cc b/src/node_dns.cc index f173e7e32c..22faa96e52 100644 --- a/src/node_dns.cc +++ b/src/node_dns.cc @@ -21,6 +21,11 @@ static ev_io io_watcher; static ev_timer timer_watcher; static Persistent errno_symbol; +static Persistent exchange_symbol; +static Persistent priority_symbol; +static Persistent weight_symbol; +static Persistent port_symbol; +static Persistent name_symbol; static inline void set_timeout() { int maxwait = 20; @@ -157,6 +162,145 @@ static void AfterResolveA6(struct dns_ctx *ctx, cb_destroy(cb); } +static void AfterResolveMX(struct dns_ctx *ctx, + struct dns_rr_mx *result, + void *data) { + assert(ctx == &dns_defctx); + + HandleScope scope; + + Persistent *cb = cb_unwrap(data); + + if (result == NULL) { + ResolveError(cb); + cb_destroy(cb); + return; + } + + /* canonical name */ + Local cname = String::New(result->dnsmx_cname); + + /* Time-To-Live (TTL) value */ + Local ttl = Integer::New(result->dnsmx_ttl); + + Local exchanges = Array::New(result->dnsmx_nrr); + for (int i = 0; i < result->dnsmx_nrr; i++) { + HandleScope loop_scope; + + Local exchange = Object::New(); + + struct dns_mx *mx = &(result->dnsmx_mx[i]); + exchange->Set(exchange_symbol, String::New(mx->name)); + exchange->Set(priority_symbol, Integer::New(mx->priority)); + + exchanges->Set(Integer::New(i), exchange); + } + + Local argv[3] = { exchanges, ttl, cname }; + + TryCatch try_catch; + + (*cb)->Call(Context::GetCurrent()->Global(), 3, argv); + + if (try_catch.HasCaught()) { + FatalException(try_catch); + } + + cb_destroy(cb); +} + +static void AfterResolveTXT(struct dns_ctx *ctx, + struct dns_rr_txt *result, + void *data) { + assert(ctx == &dns_defctx); + + HandleScope scope; + + Persistent *cb = cb_unwrap(data); + + if (result == NULL) { + ResolveError(cb); + cb_destroy(cb); + return; + } + + /* canonical name */ + Local cname = String::New(result->dnstxt_cname); + + /* Time-To-Live (TTL) value */ + Local ttl = Integer::New(result->dnstxt_ttl); + + Local records = Array::New(result->dnstxt_nrr); + for (int i = 0; i < result->dnstxt_nrr; i++) { + HandleScope loop_scope; + + struct dns_txt *record = &(result->dnstxt_txt[i]); + const char *txt = (const char *)record->txt; + records->Set(Integer::New(i), String::New(txt)); + } + + Local argv[3] = { records, ttl, cname }; + + TryCatch try_catch; + + (*cb)->Call(Context::GetCurrent()->Global(), 3, argv); + + if (try_catch.HasCaught()) { + FatalException(try_catch); + } + + cb_destroy(cb); +} + +static void AfterResolveSRV(struct dns_ctx *ctx, + struct dns_rr_srv *result, + void *data) { + assert(ctx == &dns_defctx); + + HandleScope scope; + + Persistent *cb = cb_unwrap(data); + + if (result == NULL) { + ResolveError(cb); + cb_destroy(cb); + return; + } + + /* canonical name */ + Local cname = String::New(result->dnssrv_cname); + + /* Time-To-Live (TTL) value */ + Local ttl = Integer::New(result->dnssrv_ttl); + + Local records = Array::New(result->dnssrv_nrr); + for (int i = 0; i < result->dnssrv_nrr; i++) { + HandleScope loop_scope; + + Local record = Object::New(); + + struct dns_srv *srv = &(result->dnssrv_srv[i]); + record->Set(priority_symbol, Integer::New(srv->priority)); + record->Set(weight_symbol, Integer::New(srv->weight)); + record->Set(port_symbol, Integer::New(srv->port)); + record->Set(name_symbol, String::New(srv->name)); + + records->Set(Integer::New(i), record); + } + + Local argv[3] = { records, ttl, cname }; + + TryCatch try_catch; + + (*cb)->Call(Context::GetCurrent()->Global(), 3, argv); + + if (try_catch.HasCaught()) { + FatalException(try_catch); + } + + cb_destroy(cb); +} + static Handle ResolveA(int type, const Arguments& args) { HandleScope scope; @@ -177,6 +321,18 @@ static Handle ResolveA(int type, const Arguments& args) { query = dns_submit_a6(NULL, *name, 0, AfterResolveA6, cb_persist(args[1])); break; + case DNS_T_MX: + query = dns_submit_mx(NULL, *name, 0, AfterResolveMX, cb_persist(args[1])); + break; + + case DNS_T_TXT: + query = dns_submit_txt(NULL, *name, DNS_C_IN, 0, AfterResolveTXT, cb_persist(args[1])); + break; + + case DNS_T_SRV: + query = dns_submit_srv(NULL, *name, NULL, NULL, 0, AfterResolveSRV, cb_persist(args[1])); + break; + default: return ThrowException(Exception::Error(String::New("Unsupported type"))); } @@ -196,6 +352,18 @@ static Handle ResolveA6(const Arguments& args) { return ResolveA(DNS_T_AAAA, args); } +static Handle ResolveMX(const Arguments& args) { + return ResolveA(DNS_T_MX, args); +} + +static Handle ResolveTXT(const Arguments& args) { + return ResolveA(DNS_T_TXT, args); +} + +static Handle ResolveSRV(const Arguments& args) { + return ResolveA(DNS_T_SRV, args); +} + static void AfterReverse(struct dns_ctx *ctx, struct dns_rr_ptr *result, void *data) { @@ -295,6 +463,12 @@ void DNS::Initialize(Handle target) { errno_symbol = NODE_PSYMBOL("errno"); + exchange_symbol = NODE_PSYMBOL("exchange"); + priority_symbol = NODE_PSYMBOL("priority"); + weight_symbol = NODE_PSYMBOL("weight"); + port_symbol = NODE_PSYMBOL("port"); + name_symbol = NODE_PSYMBOL("name"); + target->Set(String::NewSymbol("TEMPFAIL"), Integer::New(DNS_E_TEMPFAIL)); target->Set(String::NewSymbol("PROTOCOL"), Integer::New(DNS_E_PROTOCOL)); target->Set(String::NewSymbol("NXDOMAIN"), Integer::New(DNS_E_NXDOMAIN)); @@ -308,6 +482,15 @@ void DNS::Initialize(Handle target) { Local resolve6 = FunctionTemplate::New(ResolveA6); target->Set(String::NewSymbol("resolve6"), resolve6->GetFunction()); + Local resolveMx = FunctionTemplate::New(ResolveMX); + target->Set(String::NewSymbol("resolveMx"), resolveMx->GetFunction()); + + Local resolveTxt = FunctionTemplate::New(ResolveTXT); + target->Set(String::NewSymbol("resolveTxt"), resolveTxt->GetFunction()); + + Local resolveSrv = FunctionTemplate::New(ResolveSRV); + target->Set(String::NewSymbol("resolveSrv"), resolveSrv->GetFunction()); + Local reverse = FunctionTemplate::New(Reverse); target->Set(String::NewSymbol("reverse"), reverse->GetFunction()); } diff --git a/test/mjsunit/disabled/test-dns.js b/test/mjsunit/disabled/test-dns.js deleted file mode 100644 index 6226ba94f5..0000000000 --- a/test/mjsunit/disabled/test-dns.js +++ /dev/null @@ -1,29 +0,0 @@ -process.mixin(require("../common")); -var dns = require("dns"); - -for (var i = 2; i < process.ARGV.length; i++) { - var name = process.ARGV[i] - puts("looking up " + name); - var resolution = dns.resolve4(name); - - resolution.addCallback(function (addresses, ttl, cname) { - puts("addresses: " + JSON.stringify(addresses)); - puts("ttl: " + JSON.stringify(ttl)); - puts("cname: " + JSON.stringify(cname)); - - for (var i = 0; i < addresses.length; i++) { - var a = addresses[i]; - var reversing = dns.reverse(a); - reversing.addCallback( function (domains, ttl, cname) { - puts("reverse for " + a + ": " + JSON.stringify(domains)); - }); - reversing.addErrback( function (code, msg) { - puts("reverse for " + a + " failed: " + msg); - }); - } - }); - - resolution.addErrback(function (code, msg) { - puts("error: " + msg); - }); -} diff --git a/test/mjsunit/test-dns.js b/test/mjsunit/test-dns.js new file mode 100644 index 0000000000..47fa6e144c --- /dev/null +++ b/test/mjsunit/test-dns.js @@ -0,0 +1,119 @@ +process.mixin(require("./common")); + +var dns = require("dns"), + sys = require("sys"); + +var hosts = ['example.com', 'example.org', + 'ietf.org', // AAAA + 'google.com', // MX, multiple A records + '_xmpp-client._tcp.google.com', // SRV + 'oakalynhall.co.uk']; // Multiple PTR replies + +var records = ['A', 'AAAA', 'MX', 'TXT', 'SRV']; + +var i = hosts.length; +while (i--) { + + var j = records.length; + while (j--) { + var hostCmd = "dig -t " + records[j] + " " + hosts[i] + + "| grep '^" + hosts[i] + "\\.\\W.*IN.*" + records[j] + "'" + + "| sed -E 's/[[:space:]]+/ /g' | cut -d ' ' -f 5- " + + "| sed -e 's/\\.$//'"; + + sys.exec(hostCmd).addCallback(checkDnsRecord(hosts[i], records[j])); + } +} + +function checkDnsRecord(host, record) { + var myHost = host, + myRecord = record; + return function(stdout) { + var expected = stdout.substr(0, stdout.length - 1).split("\n"); + + var resolution = dns.resolve(myHost, myRecord); + + switch (myRecord) { + case "A": + case "AAAA": + resolution.addCallback(function (result, ttl, cname) { + cmpResults(expected, result, ttl, cname); + + // do reverse lookup check + var ll = result.length; + while (ll--) { + var ip = result[ll]; + + var reverseCmd = "host " + ip + + "| cut -d \" \" -f 5-" + + "| sed -e 's/\\.$//'"; + + sys.exec(reverseCmd).addCallback(checkReverse(ip)); + } + }); + break; + case "MX": + resolution.addCallback(function (result, ttl, cname) { + var strResult = []; + var ll = result.length; + while (ll--) { + strResult.push(result[ll].priority + " " + result[ll].exchange); + } + + cmpResults(expected, strResult, ttl, cname); + }); + break; + case "TXT": + resolution.addCallback(function (result, ttl, cname) { + var strResult = []; + var ll = result.length; + while (ll--) { + strResult.push('"' + result[ll] + '"'); + } + cmpResults(expected, strResult, ttl, cname); + }); + break; + case "SRV": + resolution.addCallback(function (result, ttl, cname) { + var strResult = []; + var ll = result.length; + while (ll--) { + strResult.push(result[ll].priority + " " + + result[ll].weight + " " + + result[ll].port + " " + + result[ll].name); + } + cmpResults(expected, strResult, ttl, cname); + }); + break; + } + } +} + +function checkReverse(ip) { + var myIp = ip; + + return function (stdout) { + var expected = stdout.substr(0, stdout.length - 1).split("\n"); + + var reversing = dns.reverse(myIp); + + reversing.addCallback( + function (domains, ttl, cname) { + cmpResults(expected, domains, ttl, cname); + }); + } +} + +function cmpResults(expected, result, ttl, cname) { + assert.equal(expected.length, result.length); + + expected.sort(); + result.sort(); + + ll = expected.length; + while (ll--) { + assert.equal(result[ll], expected[ll]); +// puts("Result " + result[ll] + " was equal to expected " + expected[ll]); + } +} diff --git a/test/mjsunit/test-http-client-reconnect-bug.js b/test/mjsunit/test-http-client-reconnect-bug.js new file mode 100644 index 0000000000..16a63f99ba --- /dev/null +++ b/test/mjsunit/test-http-client-reconnect-bug.js @@ -0,0 +1,42 @@ +process.mixin(require("./common")); + +var tcp = require("tcp"), + sys = require("sys"), + http = require("http"); + +var PORT = 2143; + +var errorCount = 0; +var eofCount = 0; + +var server = tcp.createServer(function(socket) { + socket.close(); +}); +server.listen(PORT); + +var client = http.createClient(PORT); + +client.addListener("error", function() { + sys.puts("ERROR!"); + errorCount++; +}); + +client.addListener("eof", function() { + sys.puts("EOF!"); + eofCount++; +}); + +var request = client.request("GET", "/", {"host": "localhost"}); +request.finish(function(response) { + sys.puts("STATUS: " + response.statusCode); +}); + +setTimeout(function () { + server.close(); +}, 500); + + +process.addListener('exit', function () { + assert.equal(0, errorCount); + assert.equal(1, eofCount); +}); diff --git a/test/mjsunit/test-module-loading.js b/test/mjsunit/test-module-loading.js index 09b58074f9..2ff9c41b41 100644 --- a/test/mjsunit/test-module-loading.js +++ b/test/mjsunit/test-module-loading.js @@ -6,7 +6,7 @@ var a = require("./fixtures/a"); var d = require("./fixtures/b/d"); var d2 = require("./fixtures/b/d"); // Absolute -var d3 = require(require('path').dirname(__filename)+"/fixtures/b/d"); +var d3 = require(__dirname+"/fixtures/b/d"); // Relative var d4 = require("../mjsunit/fixtures/b/d"); @@ -52,6 +52,8 @@ try { assert.equal("blah", e.message); } +assert.equal(require('path').dirname(__filename), __dirname); + process.addListener("exit", function () { assert.equal(true, a.A instanceof Function); assert.equal("A done", a.A()); diff --git a/test/mjsunit/test-promise.js b/test/mjsunit/test-promise.js index 9b314ea56e..2992f93665 100644 --- a/test/mjsunit/test-promise.js +++ b/test/mjsunit/test-promise.js @@ -19,12 +19,18 @@ a.addCallback(function(value) { assert.equal(TEST_VALUE, value); expectedCallbacks.a1--; }); +a.addErrback(function(error) { + assert.notEqual(TEST_VALUE, error, 'normal'); +}); a.emitSuccess(TEST_VALUE); a.addCallback(function(value) { assert.equal(TEST_VALUE, value); expectedCallbacks.a2--; }); +a.addErrback(function(error) { + assert.notEqual(TEST_VALUE, error, 'late'); +}); // Test regular & late errback binding var b = new Promise(); diff --git a/tools/osx-dist.sh b/tools/osx-dist.sh new file mode 100755 index 0000000000..d139bf4ac6 --- /dev/null +++ b/tools/osx-dist.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +TOOLS=`dirname $0` +ROOT=$TOOLS/.. + +VERSION=`git describe` +CONTENTS=dist-osx/nodejs-$VERSION + +# go build it in the root of the git repository +pushd $ROOT + +./configure --prefix=/usr/local/nodejs +make +make install DESTDIR="$CONTENTS" + +mkdir -p "$CONTENTS/usr/local/bin" +pushd "$CONTENTS/usr/local/bin" +ln -s ../nodejs/bin/* . +popd # $CONTENTS/usr/local/bin + +popd # $ROOT + +"$TOOLS/osx-pkg-dmg-create.sh" "$ROOT/$CONTENTS" NodeJS $VERSION 'org.nodejs' diff --git a/tools/osx-pkg-dmg-create.sh b/tools/osx-pkg-dmg-create.sh new file mode 100755 index 0000000000..e288d8f190 --- /dev/null +++ b/tools/osx-pkg-dmg-create.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Create a complete OS .dmg file (it needs the Apple Developers Tools installed) +# usage: +# pkg-create.sh +# + +CONTENTS=$1 +shift +NAME=$1 +shift +VERSION=$1 +shift +VENDOR=$1 + +PKGID="$VENDOR.$NAME-$VERSION" + +# unused pkg-info entries so far +# +# CFBundleExecutable +# $NAME +# CFBundleSignature +# ???? + +# +# Need the .plist file in order for the packagemaker to create a package which the +# pkgutil --packages later on would return an entry about. pkgutil can then --unlink +# and --forget about the package nicely. +# +cat > "$CONTENTS.plist" < + + + + CFBundleDevelopmentRegion + en + CFBundleIdentifier + $PKGID + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + $VERSION + CFBundleVersion + $VERSION + + +PINFO + +packagemaker=/Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker + +$packagemaker \ + --id "$PKGID" \ + --info "$CONTENTS.plist" \ + --root "$CONTENTS" \ + --target 10.5 \ + --out "$CONTENTS".pkg + +hdiutil create "$CONTENTS.dmg" \ + -format UDZO -ov \ + -volname "$NAME $VERSION" \ + -srcfolder $CONTENTS.pkg +