diff --git a/README.md b/README.md index 4d17274..da0731e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Compared to the built in HTTP[S] stack in Node.js, the httpsys module offers: ### Compatibility -The httpsys module requires Windows. It works with any stable version of Node.js, both 32- and 64-bit. The module was developed and tested with Node.js 0.6.20, 0.8.22, 0.10.15, each in 32 and 64 bit flavors. +The httpsys module requires Windows. It works with any stable version of Node.js, both 32- and 64-bit. The module was developed and tested with Node.js 8.9.4, 9.9.0, 10.13.0, each in 32 and 64 bit flavors. As it makes use of Native Abstractions (nan) for Node.js it should build for other versions of node. WebSockets functionality requires Windows 8 or Windows Server 2012 or later. @@ -24,7 +24,7 @@ Any and all feedback is welcome [here](https://github.com/tjanczuk/httpsys/issue ### Getting started -You must have Windows and a stable version of Node.js installed (0.6, 0.8, 0.10). Then: +You must have Windows and a recent version of Node.js installed (8, 9, 10). Then: ``` npm install httpsys @@ -217,9 +217,9 @@ The httpsys module exposes the following functions: ### Building -To build httpsys, you must have Visual Studio 2012 or Visual Studio Express 2012 for Windows Desktop. There are a few ways to build depending on what you want to achieve. +To build httpsys, you must have Visual Studio 2015 or Visual Studio Express 2015 for Windows Desktop. There are a few ways to build depending on what you want to achieve. -To build httpsys for all supported versions and flavors of Node.js: (0.6.20, 0.8.22, 0.10.15) x (x32, x64), call +To build httpsys for all supported versions and flavors of Node.js: (8.9.4, 9.9.0, 10.13.0) x (x32, x64), call ```text tools\buildall.bat diff --git a/binding.gyp b/binding.gyp index 69ebcbd..64f1f64 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,10 +1,13 @@ -{ - 'targets': [ - { - 'target_name': 'httpsys', - 'sources': [ - 'src/httpsys.cc' - ] - } - ] -} \ No newline at end of file +{ + 'targets': [ + { + 'target_name': 'httpsys', + 'sources': [ + 'src/httpsys.cc' + ], + "include_dirs": [ + "send->index + //to set the statusCode (206) is the res object. This needs to be synced to the top level _requestContext which + //is used by the http.sys driver when constructing the response back to the browser + // + //this is the only time it is copied. There is another scenario when servering socket.io.js where the two + //statusCodes are different, in this case we must respect the _requestContext statusCode + if (this._requestContext.res && this._requestContext.res.statusCode === 206) { + this._requestContext.statusCode = 206; + } + debug("response status code '" + this._requestContext.statusCode + "'"); + + // Upgrade reqeusts are never chunked. For regular requests assume chunking unless proven otherwise. + this._requestContext.chunkResponse = !this._requestContext.upgrade; + + this._requestContext.knownHeaders = []; + this._requestContext.unknownHeaders = {}; + for (var i in this._requestContext.headers) { + var id = knownResponseHeaders[i.toLowerCase()]; + //for an unknown reason 'Connection:upgrade' response header + //is stripped by http.sys api, so to get around this it is added + //to the unknown headers which gets passed through + if (id === undefined || id === 1) { + debug("unknown header: " + i + "=" + this._requestContext.headers[i]); + this._requestContext.unknownHeaders[i] = this._requestContext.headers[i]; + } else { + debug("known header: " + i + "=" + this._requestContext.headers[i]); + this._requestContext.knownHeaders.push({ id: id, value: this._requestContext.headers[i] }); + if (6 === id || 11 === id) { + // Either Content-Length or Transfer-Encoding headers were specified, + // chunked transfer encoding need not be applied. + this._requestContext.chunkResponse = false; + } + } - // This is a response to an upgrade request, but the application has not yet fully written out - // the HTTP response status line and headers to the socket. Return and wait for another call to write. - // At this point the chunk of the response has already been queued up in the requestContext.chunks. + } - return false; - } - } - - if (!this._requestContext.knownHeaders) { - // First call to write prepares the cached response headers - - // Upgrade reqeusts are never chunked. For regular requests assume chunking unless proven otherwise. - this._requestContext.chunkResponse = !this._requestContext.upgrade; - - this._requestContext.knownHeaders = []; - this._requestContext.unknownHeaders = {}; - for (var i in this._requestContext.headers) { - var id = knownResponseHeaders[i]; - if (id === undefined) - this._requestContext.unknownHeaders[i] = this._requestContext.headers[i]; - else { - this._requestContext.knownHeaders.push({id: id, value: this._requestContext.headers[i]}); - if (6 === id || 11 === id) { - // Either Content-Length or Transfer-Encoding headers were specified, - // chunked transfer encoding need not be applied. - this._requestContext.chunkResponse = false; - } + // Determine if chunked transfer encoding must be applied. + if (this._requestContext.chunkResponse) { + debug("know header: transfer-encoding=chunked"); + // Add Transfer-Encoding: chunked header if chunking will be applied. + this._requestContext.knownHeaders.push({ id: 6, value: 'chunked' }); } - } - // Determine if chunked transfer encoding must be applied. - - if (this._requestContext.chunkResponse) { - // Add Transfer-Encoding: chunked header if chunking will be applied. + // propagate cacheDuration from ServerResponse to _requestContext if it was set - this._requestContext.knownHeaders.push({id: 6, value: 'chunked'}); + if (!isNaN(this.cacheDuration)) { + this._requestContext.cacheDuration = this.cacheDuration; } - } - // propagate cacheDuration from ServerResponse to _requestContext if it was set + if (!this._requestContext.upgrade) { + // Queue up the chunk of the body to be sent after headers have been sent. + // For upgrade requests the chunk had already been added at the beginning of this function. + this._queue_body_chunk(chunk, encoding, isEnd); + } - if (!isNaN(this.cacheDuration)) { - this._requestContext.cacheDuration = this.cacheDuration; + sent = this._initiate_send_next(); + if (cb) { + if (!sent) { + this._wsDrain = cb; + } else { + cb(); + } + } + return sent; + } else { + debug("write ignored as socket is closed"); } - - if (!this._requestContext.upgrade) { - // Queue up the chunk of the body to be sent after headers have been sent. - // For upgrade requests the chunk had already been added at the beginning of this function. - this._queue_body_chunk(chunk, encoding, isEnd); - } - - return this._initiate_send_next(); + return false; }; Socket.prototype.end = function (chunk, encoding) { + debug("ending..."); if (chunk) { - this._ensureOpened(); - return this.write(chunk, encoding, true); + //this._ensureOpened(); + if (!this._closed) { + debug("we have chunked data, calling write"); + return this.write(chunk, encoding, true); + } else { + debug("we have chunked data, but socket is closed, ignoring write"); + return true; + } } else if (!this._closed) { + debug("calling write..."); return this.write(chunk, encoding, true); } else { + debug("socket is closed, ignoring write"); return true; } }; Socket.prototype._setHeader = function (name, value) { - this._ensureOpened(); - - if (typeof name !== 'string') - throw new Error('The name parameter must be a string HTTP header name.'); + if (!this._closed) { + if (typeof name !== 'string') + throw new Error('The name parameter must be a string HTTP header name.'); - if (Array.isArray(value)) - throw new Error('Array header values are not supported. The HTTP header value must be atomic.'); + if (Array.isArray(value)) + throw new Error('Array header values are not supported. The HTTP header value must be atomic.'); - if (typeof value !== 'string') - value += ''; + if (typeof value !== 'string') + value += ''; - // TODO: support for multiple headers with the same name + // TODO: support for multiple headers with the same name - if (this._requestContext.knownHeaders) - throw new Error('Response headers cannot be modified after they have been sent to the client.'); + if (this._requestContext.knownHeaders) + throw new Error('Response headers cannot be modified after they have been sent to the client.'); - this._requestContext.headers[name.toLowerCase()] = value.toString(); + this._requestContext.headers[name.toLowerCase()] = value.toString(); + } else { + debug("setHeader ignored as socket closed"); + } }; Socket.prototype._getHeader = function (name) { - this._ensureOpened(); - - if (typeof name !== 'string') - throw new Error('The name parameter must be a string HTTP header name.'); + if (!this._closed) { + if (typeof name !== 'string') + throw new Error('The name parameter must be a string HTTP header name.'); - return this._requestContext.headers[name.toLowerCase()]; + return this._requestContext.headers[name.toLowerCase()]; + } else { + debug("getHeader() ignored as socket closed"); + } + return ""; }; Socket.prototype._removeHeader = function (name) { - this._ensureOpened(); - - if (typeof name !== 'string') - throw new Error('The name parameter must be a string HTTP header name.'); + if (!this._closed) { + if (typeof name !== 'string') + throw new Error('The name parameter must be a string HTTP header name.'); - if (this._requestContext.knownHeaders) - throw new Error('Response headers cannot be modified after they have been sent to the client.'); + if (this._requestContext.knownHeaders) + throw new Error('Response headers cannot be modified after they have been sent to the client.'); - return delete this._requestContext.headers[name.toLowerCase()]; + return delete this._requestContext.headers[name.toLowerCase()]; + } else { + debug("removeHeader() ignored as socket closed"); + } }; Socket.prototype._queue_body_chunk = function (chunk, encoding, isEnd) { - this._ensureOpened(); - - if (this._requestContext.isLastChunk) - throw new Error('No more response data can be written after the end method had been called.'); - - if (!Buffer.isBuffer(chunk)) { - if (typeof chunk === 'string') { - chunk = new Buffer(chunk, encoding || 'utf8'); - } - else if (chunk === null && isEnd !== true) { - throw new Error('Chunk must be a string or a Buffer.'); + debug("queue body chunk isEnd '" + (isEnd || 'false') + "'"); + if (!this._closed) { + if (this._requestContext.isLastChunk) { + debug("no more response data can be written after the end method had been called"); + throw new Error('No more response data can be written after the end method had been called.'); } - } - if (!this._requestContext.chunks) - this._requestContext.chunks = []; + if (!Buffer.isBuffer(chunk)) { + if (typeof chunk === 'string') { + chunk = new Buffer(chunk, encoding || 'utf8'); + } + else if (chunk === null && isEnd !== true) { + throw new Error('Chunk must be a string or a Buffer.'); + } + } - if (chunk) { - if (this._requestContext.chunkResponse) - this._requestContext.chunks.push( - new Buffer(chunk.length.toString(16) + '\x0d\x0a'), - chunk, - crlf); - else - this._requestContext.chunks.push(chunk); - } + if (!this._requestContext.chunks) + this._requestContext.chunks = []; + + if (chunk) { + if (this._requestContext.chunkResponse) + this._requestContext.chunks.push( + new Buffer(chunk.length.toString(16) + '\x0d\x0a'), + chunk, + crlf); + else + this._requestContext.chunks.push(chunk); + } - if (isEnd) { - this._requestContext.isLastChunk = true; - if (this._requestContext.chunkResponse) - this._requestContext.chunks.push(lastChunk); + if (isEnd) { + this._requestContext.isLastChunk = true; + if (this._requestContext.chunkResponse) + this._requestContext.chunks.push(lastChunk); + } + } else { + debug("queue body chunk ignored as socket closed"); } }; Socket.prototype._on_written = function () { + debug("write complete"); if (this._requestContext.drainEventPending && !this._requestContext.chunks) { delete this._requestContext.drainEventPending; if (!this._closed) { + debug("emitting drain"); this.emit('drain'); + if (this._wsDrain) { + this._wsDrain(); + this._wsDrain = null; + } } } - if (!this._closed && this._requestContext.chunks) - this._initiate_send_next(); -}; - -Socket.prototype._initiate_send_next = function () { - this._ensureOpened(); + if (!this._closed && this._requestContext.chunks) { + // fix #40: httpsys native module has a problem in processing empty list of chunks, + // which happens when stream gets to an end, and .write(null, null, true) + // is issued to indicate it. The problem has to do with callback recursion, + // and so we get out of it by scheduling _initiate_send_next() to occur on + // next nodejs event-loop iteration. - if (this._requestContext.asyncPending('res')) { - // Another response async operation is pending. - // Postpone send until entire request had been read and no async operations are pending. + if (!this._requestContext.chunks.length) { + process.nextTick(this._initiate_send_next.bind(this)); + } else { + this._initiate_send_next(); + } + } - if (this._requestContext.chunks) { - // There is a chunk of the body to be sent, but it cannot be sent synchronously. - // The 'drain' event must therefore be emitted once the chunk is sent in the future. +}; - this._requestContext.drainEventPending = true; +Socket.prototype._initiate_send_next = function () { + debug("initiate sending next response"); + if (!this._closed) { + if (this._requestContext.asyncPending('res')) { + debug("response async pending, wait for write to complete before sending next"); + // Another response async operation is pending. + // Postpone send until entire request had been read and no async operations are pending. + if (this._requestContext.chunks) { + debug("we have request context chucks, setting drainEventPending"); + // There is a chunk of the body to be sent, but it cannot be sent synchronously. + // The 'drain' event must therefore be emitted once the chunk is sent in the future. + this._requestContext.drainEventPending = true; + } + return false; } - return false; - } + if (this._requestContext.knownHeaders && !this._requestContext.headersWritten) { + // Initiate sending HTTP response headers and body, if any. + debug("writing headers"); - if (this._requestContext.knownHeaders && !this._requestContext.headersWritten) { - // Initiate sending HTTP response headers and body, if any. + this._requestContext.headersWritten = true; - this._requestContext.headersWritten = true; + try { + this._requestContext.asyncPending('res', httpsys.httpsys_write_headers(this._requestContext)); + } + catch (e) { + this._error('res', e, "httpsys_write_headers has failed"); + } - try { - this._requestContext.asyncPending('res', httpsys.httpsys_write_headers(this._requestContext)); - } - catch (e) { - this._error('res', e); - } + if (!this._requestContext.asyncPending('res') && !this._requestContext.disconnect) { + // Synchronous completion + this._on_written(); + } - if (!this._requestContext.asyncPending('res') && !this._requestContext.disconnect) { - // Synchronous completion - this._on_written(); + return true; } + else if (this._requestContext.chunks) { + // Initiate sending HTTP response body. + debug("writing body"); - return true; - } - else if (this._requestContext.chunks) { - // Initiate sending HTTP response body. + try { + this._requestContext.asyncPending('res', httpsys.httpsys_write_body(this._requestContext)); + } + catch (e) { + this._error('res', e, "httpsys_write_body has failed"); + } - try { - this._requestContext.asyncPending('res', httpsys.httpsys_write_body(this._requestContext)); - } - catch (e) { - this._error('res', e); - } - - if (!this._requestContext.asyncPending('res') && !this._requestContext.disconnect) { - // Synchronous completion - this._on_written(); - } + if (!this._requestContext.asyncPending('res') && !this._requestContext.disconnect) { + // Synchronous completion + this._on_written(); + } - return true; + return true; + } + } else { + debug("cannot send next response as socket closed"); } - return false; }; Socket.prototype.pause = function () { - this._ensureOpened(); - - this._paused = true; + debug("pausing flow"); + if (!this._closed) { + this._paused = true; + } else { + debug("ignore pause as socket closed"); + } }; Socket.prototype.resume = function () { - this._ensureOpened(); - - if (this._paused) { - if (!this._requestContext.asyncPending('req')) { - httpsys.httpsys_resume(this._requestContext); + debug("resuming flow"); + if (!this._closed) { + if (this._paused) { + if (!this._requestContext.asyncPending('req')) { + httpsys.httpsys_resume(this._requestContext); + } + delete this._paused; } - - delete this._paused; + } else { + debug("ignored resume as socket closed"); } }; @@ -432,6 +544,7 @@ Socket.prototype.setEncoding = function (encoding) { }; Socket.prototype._on_request_body = function (args) { + debug("response body requested"); if (!this._closed) { if (this._requestContext.responseStarted) { // If response was started by the application asynchronously (e.g from @@ -446,10 +559,13 @@ Socket.prototype._on_request_body = function (args) { else { this.emit('data', args.data.toString(this._encoding)); } + } else { + debug("response body request ignored as socket closed"); } }; Socket.prototype._on_end_request = function () { + debug("end response requested"); if (!this._closed) { if (!this._requestContext.responseStarted) { // Only emit the 'end' event if no response was started by the application @@ -458,15 +574,16 @@ Socket.prototype._on_end_request = function () { this.emit('end'); } - // Signal the response to start sending cached response content if any // had been accumulated while the response was being received. - this._initiate_send_next(); + } else { + debug("end response request ignored as socket closed"); } }; Socket.prototype.setTimeout = function (timeout, callback) { + debug("setTimeout called, timeout '" + timeout + "'"); // TODO: implement full Socket.setTimeout semantics // http://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback @@ -476,12 +593,17 @@ Socket.prototype.setTimeout = function (timeout, callback) { }; Socket.prototype.setNoDelay = function (noDelay) { + debug("setNoDelay called, noDelay '" + noDelay + "'"); this._requestContext.noDelay = typeof noDelay === 'boolean' ? noDelay : true; }; -Socket.prototype._error = function (source, error) { +Socket.prototype._error = function (source, error, msg) { + debug("error called, typeof error '" + typeof error + "'"); + debug("error:" + source + ":" + error + ":" + msg); this._requestContext.asyncPending(source, false); if (!this._closed && !this._closing) { + this._closeError = error; + debug("socket closing.."); this._closing = true; try { // Ensure that all native resources are released. @@ -491,21 +613,16 @@ Socket.prototype._error = function (source, error) { catch (e) { // Ignore. } - this._closed = true; - this._closeError = error; - this.emit('error', error); + debug("emitting error"); + this.emit('error', new Error(msg + " " + error)); + debug("emitting close"); this.emit('close', true); } }; -Socket.prototype._ensureOpened = function () { - if (this._closed) { - throw new Error('Socket is closed.') - } -}; - Socket.prototype.destroy = function () { + debug("destory called"); if (!this._closed) { // Destroying a socket is implemented by calling HTTP.SYS APIs with @@ -523,16 +640,29 @@ Socket.prototype.destroy = function () { this._requestContext.disconnect = true; if (!this._requestContext.isLastChunk) { - // Only call end if it was not called before - this.end(); + // Only call end if it was not called before, + // JPW: ********************* + // if the socket has been closed with a 1229 error code, the native resources + // will have been freed so we need to prevent further writes in this screnaio. + // ************************** + if (this._closeError !== ERROR_CONNECTION_INVALID) { + debug("connect still valid, calling end..."); + //only write if connection is still valid + this.end(); + } else { + debug("socket connection invalid, do not respond with end..."); + } } if (!this._closed && !this.closing) { // this._closed can be true if the end() completed synchronously // this._closing is true if the socket._error was called and the socket is being destroyed + debug("emit close..."); this._closed = true; this.emit('close', false); } + } else { + debug("destory ignored as socket already closed"); } }; diff --git a/lib/httpsys.js b/lib/httpsys.js index f294c8c..e99c10c 100644 --- a/lib/httpsys.js +++ b/lib/httpsys.js @@ -1,127 +1 @@ -// Get a reference to Node's internal NativeModule, courtesy of Brandon Benvie: -// https://github.com/Benvie/Node.js-Ultra-REPL/blob/master/lib/ScopedModule.js -// The reference is then used in the *.slipstream methods to replace built-in -// HTTP and HTTPS modules with the HTTP.SYS one. - -var NativeModule; -(function (){ - process.moduleLoadList.push = function() { - // NativeModule.require('native_module') returns NativeModule - NativeModule = arguments.callee.caller('native_module'); - - // Delete the interceptor and re-expose normal functionality - delete process.moduleLoadList.push; - - return Array.prototype.push.apply(process.moduleLoadList, arguments); - }; - - // Force one module resolution to enter the push method above - require('vm'); -})(); - -var version = require('../package.json').version - , httpsys = require('./httpsys_native') - , HttpServer = require('./HttpServer') - , HttpsServer = require('./HttpsServer') - , Server = require('./Server') - , ServerRequest = require('./ServerRequest') - , ServerResponse = require('./ServerResponse') - , util = require('util'); - -httpsys.httpsys_init({ - initialBufferSize: (+process.env.HTTPSYS_BUFFER_SIZE) || 4096, - requestQueueLength: (+process.env.HTTPSYS_REQUEST_QUEUE_LENGTH) || 5000, - pendingReadCount: (+process.env.HTTPSYS_PENDING_READ_COUNT) || 1, - cacheDuration: isNaN(process.env.HTTPSYS_CACHE_DURATION) ? -1 : (+process.env.HTTPSYS_CACHE_DURATION), - callback: Server._dispatch -}); - -function addClientStack(target, source) { - [ 'STATUS_CODES', - 'IncomingMessage', - 'OutgoingMessage', - 'Agent', - 'globalAgent', - 'ClientRequest', - 'request', - 'get', - 'Client', - 'createClient' - ].forEach(function (api) { - if (source[api]) - target[api] = source[api]; - }); -} - -var http; -var https; - -exports.http = function () { - if (!http) { - http = { - Server: HttpServer, - ServerRequest: ServerRequest, - ServerResponse: ServerResponse, - createServer: function (requestListener) { - var server = new HttpServer(); - if (requestListener) { - server.on('request', requestListener) - } - - return server; - }, - httpsys_version: version - }; - - addClientStack(http, require('http')); - } - - return http; -}; - -exports.https = function () { - if (!https) { - https = { - Server: HttpsServer, - createServer: function (options, requestListener) { - - // `options` are ignored for API compatibility - // Keys and certificates in HTTP.SYS - // are configured with `netsh http add sslcert`. - - var server = new HttpsServer(); - if (requestListener) { - server.on('request', requestListener) - } - - return server; - }, - httpsys_version: version - }; - - addClientStack(https, require('https')); - } - - return https; -} - -exports.http.slipstream = function() { - // Make sure original HTTP module is loaded into native module cache - require('http'); - - // Replace the HTTP module implementation in the native module cache with HTTP.SYS - NativeModule._cache.http.exports = exports.http(); -}; - -exports.https.slipstream = function() { - // Make sure original HTTPS module is loaded into native module cache - require('https'); - - // Replace the HTTPS module implementation in the native module cache with HTTP.SYS - NativeModule._cache.https.exports = exports.https(); -}; - -exports.slipstream = function () { - exports.http.slipstream(); - exports.https.slipstream(); -}; +// moved to ../index.js diff --git a/lib/httpsys_native.js b/lib/httpsys_native.js index 91d4a3e..7efaf9f 100644 --- a/lib/httpsys_native.js +++ b/lib/httpsys_native.js @@ -1,7 +1,7 @@ var versionMap = [ - [ /^0\.6\./, '0.6.20' ], - [ /^0\.8\./, '0.8.22' ], - [ /^0\.10\./, '0.10.15' ] + [ /^8\.9\./, '8.9.4' ], + [ /^9\.9\./, '9.9.0' ], + [ /^10\.13\./, '10.13.0' ] ]; function determineVersion() { @@ -47,5 +47,5 @@ exports.nativeEvents = [ '_on_request_body', // 7 '_on_written', // 8 '_on_error_writing', // 9 - '_on_server_closed' // 10 + '_on_server_closed' ]; diff --git a/lib/native/win32/ia32/10.13.0/httpsys.node b/lib/native/win32/ia32/10.13.0/httpsys.node new file mode 100644 index 0000000..db85d91 Binary files /dev/null and b/lib/native/win32/ia32/10.13.0/httpsys.node differ diff --git a/lib/native/win32/ia32/8.9.4/httpsys.node b/lib/native/win32/ia32/8.9.4/httpsys.node new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/native/win32/ia32/8.9.4/httpsys.node @@ -0,0 +1 @@ + diff --git a/lib/native/win32/ia32/9.9.0/httpsys.node b/lib/native/win32/ia32/9.9.0/httpsys.node new file mode 100644 index 0000000..8ec5fc6 Binary files /dev/null and b/lib/native/win32/ia32/9.9.0/httpsys.node differ diff --git a/lib/native/win32/x64/10.13.0/httpsys.node b/lib/native/win32/x64/10.13.0/httpsys.node new file mode 100644 index 0000000..8abadca Binary files /dev/null and b/lib/native/win32/x64/10.13.0/httpsys.node differ diff --git a/lib/native/win32/x64/8.9.4/httpsys.node b/lib/native/win32/x64/8.9.4/httpsys.node new file mode 100644 index 0000000..8afb5dc Binary files /dev/null and b/lib/native/win32/x64/8.9.4/httpsys.node differ diff --git a/lib/native/win32/x64/9.9.0/httpsys.node b/lib/native/win32/x64/9.9.0/httpsys.node new file mode 100644 index 0000000..16c7e9d Binary files /dev/null and b/lib/native/win32/x64/9.9.0/httpsys.node differ diff --git a/package.json b/package.json index 6ded8c6..a791229 100644 --- a/package.json +++ b/package.json @@ -5,28 +5,35 @@ "url": "http://tomasz.janczuk.org", "twitter": "tjanczuk" }, - "version": "0.3.3-pre", + "version": "1.0.0", "description": "Native HTTP stack for Node.js on Windows", - "tags" : ["http", "https", "http.sys", "windows"], - "main": "./lib/httpsys.js", - "licenses": [ { "type": "Apache", "url": "http://www.apache.org/licenses/LICENSE-2.0" } ], - "os": [ "win32" ], + "tags": [ + "http", + "https", + "http.sys", + "windows" + ], + "main": "./index.js", + "licenses": [ + { + "type": "Apache", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ], + "os": [ + "win32" + ], "devDependencies": { - "mocha": "1.8.1", - "ws": "0.4.27", - "socket.io": "0.9.16", - "socket.io-client": "0.9.16" - }, - "homepage": "https://github.com/tjanczuk/httpsys", - "repository": { - "type": "git", - "url": "git@github.com:tjanczuk/httpsys.git" - }, - "bugs" : { - "url" : "http://github.com/tjanczuk/httpsys/issues" + "mocha": "^5.2.0", + "socket.io": "1.4.8", + "socket.io-client": "1.4.8" }, "scripts": { - "install": "tools\\install.bat", "test": "test\\test.bat" - } -} \ No newline at end of file + }, + "dependencies": { + "debug": "4.1.0", + "http-status-codes": "1.3.0", + "nan": "^2.11.1" + } +} diff --git a/samples/101_server.js b/samples/101_server.js index d8d6a70..05efd7f 100644 --- a/samples/101_server.js +++ b/samples/101_server.js @@ -1,5 +1,5 @@ //var http = require('http'); -var http = require('../lib/httpsys').http(); +var http = require('../index').http(); http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); diff --git a/samples/102_server_bar.js b/samples/102_server_bar.js index 3130a45..b7ef836 100644 --- a/samples/102_server_bar.js +++ b/samples/102_server_bar.js @@ -1,4 +1,4 @@ -var http = require('../lib/httpsys').http(); +var http = require('../index').http(); http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); diff --git a/samples/102_server_foo.js b/samples/102_server_foo.js index ebb2f0a..8b344eb 100644 --- a/samples/102_server_foo.js +++ b/samples/102_server_foo.js @@ -1,4 +1,4 @@ -var http = require('../lib/httpsys').http(); +var http = require('../index').http(); http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); diff --git a/samples/103_https.js b/samples/103_https.js index f2fafe9..398c690 100644 --- a/samples/103_https.js +++ b/samples/103_https.js @@ -1,4 +1,4 @@ -var https = require('../lib/httpsys').https(); +var https = require('../index').https(); var options = {}; https.createServer(options, function (req, res) { diff --git a/samples/104_output_caching.js b/samples/104_output_caching.js index 519b9d7..e4cbcbd 100644 --- a/samples/104_output_caching.js +++ b/samples/104_output_caching.js @@ -1,11 +1,11 @@ // set HTTPSYS_CACHE_DURATION=15 // netsh http show cachestate -var http = require('../lib/httpsys').http(); +var http = require('../index').http(); http.createServer(function (req, res) { console.log('Request for ' + req.url); res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' }); - // res.cacheDuration = 5; // cache this particular response for 5 seconds + //res.cacheDuration = 5; // cache this particular response for 5 seconds res.end('Hello, world! Time on server is ' + new Date()); }).listen(8080); \ No newline at end of file diff --git a/samples/105_cluster.js b/samples/105_cluster.js index e34c017..72a1b0b 100644 --- a/samples/105_cluster.js +++ b/samples/105_cluster.js @@ -1,4 +1,4 @@ -var http = require('../lib/httpsys').http(); +var http = require('../index').http(); var cluster = require('cluster'); var numCPUs = require('os').cpus().length; diff --git a/src/httpsys.cc b/src/httpsys.cc index ed77183..61878f1 100644 --- a/src/httpsys.cc +++ b/src/httpsys.cc @@ -1,1716 +1,1948 @@ #include "httpsys.h" - -/* -Design notes: -- Only one async operation per regular HTTP request should be outstanding at a time. JavaScript - must ensure not to initiate another async operation (e.g. httpsys_write_body) before - the ongoing one completes. This implies JavaScript must manage a state machine around a request - and buffer certain calls from user code (e.g. writing multiple chunks of response body before - previous write completes) -- Only up to two async operations per upgraded HTTP request should be outstanding at the time: one for reading - of the requst, and one for writing of the response. The native module supports separate event pumps for - the request and response of upgraded request. -- Native resources are released by native code if async operation completes with error. -- If JavaScript encounters an error it must explicitly request native resources to be released. - In particular there is no exception contract between JavaScript callback and native code. -- JavaScript cannot make any additional calls into native in the context of a particular request - after it has been called with an error event type; at that time all native resources had already - been cleaned up. -- The Socket underlying the HTTP request and response implements the allowHalfOpen=true semantics using - HTTP.SYS APIs, which is the Node.js behavior. This allows responses to be sent after the request has finished - and requests to be received after the response has finished. Native resources are only freed when both ends - of the connection are closed. -*/ +#include using namespace v8; -int initialBufferSize; -ULONG requestQueueLength; -int pendingReadCount; -Persistent callback; -Persistent bufferConstructor; -HTTP_CACHE_POLICY cachePolicy; -ULONG defaultCacheDuration; -Persistent httpsysObject; -RtlTimeToSecondsSince1970Func RtlTimeToSecondsSince1970Impl; -BOOL httpsys_export_client_cert; +#pragma comment(lib, "ws2_32.lib") + + /* + Design notes: + - Only one async operation per regular HTTP request should be outstanding at a time. JavaScript + must ensure not to initiate another async operation (e.g. httpsys_write_body) before + the ongoing one completes. This implies JavaScript must manage a state machine around a request + and buffer certain calls from user code (e.g. writing multiple chunks of response body before + previous write completes) + - Only up to two async operations per upgraded HTTP request should be outstanding at the time: one for reading + of the requst, and one for writing of the response. The native module supports separate event pumps for + the request and response of upgraded request. + - Native resources are released by native code if async operation completes with error. + - If JavaScript encounters an error it must explicitly request native resources to be released. + In particular there is no exception contract between JavaScript callback and native code. + - JavaScript cannot make any additional calls into native in the context of a particular request + after it has been called with an error event type; at that time all native resources had already + been cleaned up. + - The Socket underlying the HTTP request and response implements the allowHalfOpen=true semantics using + HTTP.SYS APIs, which is the Node.js behavior. This allows responses to be sent after the request has finished + and requests to be received after the response has finished. Native resources are only freed when both ends + of the connection are closed. + */ + +BOOL debugOut; +char logBuf[1024]; +int initialBufferSize; +ULONG requestQueueLength; +int pendingReadCount; + +Nan::Callback *callback; +Nan::Persistent bufferConstructor; + +HTTP_CACHE_POLICY cachePolicy; +ULONG defaultCacheDuration; +Nan::Persistent httpsysObject; +RtlTimeToSecondsSince1970Func RtlTimeToSecondsSince1970Impl; +BOOL httpsys_export_client_cert; // Global V8 strings reused across requests -Handle v8uv_httpsys_server; -Handle v8method; -Handle v8req; -Handle v8httpHeaders; -Handle v8httpVersionMajor; -Handle v8httpVersionMinor; -Handle v8eventType; -Handle v8code; -Handle v8url; -Handle v8uv_httpsys; -Handle v8data; -Handle v8statusCode; -Handle v8reason; -Handle v8knownHeaders; -Handle v8unknownHeaders; -Handle v8isLastChunk; -Handle v8chunks; -Handle v8id; -Handle v8value; -Handle v8cacheDuration; -Handle v8disconnect; -Handle v8noDelay; -Handle v8clientCertInfo; -Handle v8cert; -Handle v8authorizationError; -Handle v8subject; -Handle v8issuer; -Handle v8validFrom; -Handle v8validTo; -Handle v8fingerprint; -Handle v8encoded; +Nan::Persistent v8uv_httpsys_server; +Nan::Persistent v8method; +Nan::Persistent v8req; +Nan::Persistent v8httpHeaders; +Nan::Persistent v8httpVersionMajor; +Nan::Persistent v8httpVersionMinor; +Nan::Persistent v8eventType; +Nan::Persistent v8code; +Nan::Persistent v8url; +Nan::Persistent v8uv_httpsys; +Nan::Persistent v8data; +Nan::Persistent v8statusCode; +Nan::Persistent v8reason; +Nan::Persistent v8knownHeaders; +Nan::Persistent v8unknownHeaders; +Nan::Persistent v8isLastChunk; +Nan::Persistent v8chunks; +Nan::Persistent v8id; +Nan::Persistent v8value; +Nan::Persistent v8cacheDuration; +Nan::Persistent v8disconnect; +Nan::Persistent v8noDelay; +Nan::Persistent v8clientCertInfo; +Nan::Persistent v8cert; +Nan::Persistent v8authorizationError; +Nan::Persistent v8subject; +Nan::Persistent v8issuer; +Nan::Persistent v8validFrom; +Nan::Persistent v8validTo; +Nan::Persistent v8fingerprint; +Nan::Persistent v8encoded; +Nan::Persistent v8remoteAddress; // Maps HTTP_HEADER_ID enum to v8 string // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364526(v=vs.85).aspx -Handle v8httpRequestHeaderNames[HttpHeaderRequestMaximum]; + +Nan::Persistent v8httpRequestHeaderNames[HttpHeaderRequestMaximum]; + char* requestHeaders[] = { - "cache-control", - "connection", - "date", - "keep-alive", - "pragma", - "trailer", - "transfer-encoding", - "upgrade", - "via", - "warning", - "alive", - "content-length", - "content-type", - "content-encoding", - "content-language", - "content-location", - "content-md5", - "content-range", - "expires", - "last-modified", - "accept", - "accept-charset", - "accept-encoding", - "accept-language", - "authorization", - "cookie", - "expect", - "from", - "host", - "if-match", - "if-modified-since", - "if-none-match", - "if-range", - "if-unmodified-since", - "max-forwards", - "proxy-authorization", - "referer", - "range", - "te", - "translate", - "user-agent" + "cache-control", + "connection", + "date", + "keep-alive", + "pragma", + "trailer", + "transfer-encoding", + "upgrade", + "via", + "warning", + "alive", + "content-length", + "content-type", + "content-encoding", + "content-language", + "content-location", + "content-md5", + "content-range", + "expires", + "last-modified", + "accept", + "accept-charset", + "accept-encoding", + "accept-language", + "authorization", + "cookie", + "expect", + "from", + "host", + "if-match", + "if-modified-since", + "if-none-match", + "if-range", + "if-unmodified-since", + "max-forwards", + "proxy-authorization", + "referer", + "range", + "te", + "translate", + "user-agent" }; // Maps HTTP_VERB enum to V8 string // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364664(v=vs.85).aspx -Handle v8verbs[HttpVerbMaximum]; + +Nan::Persistent v8verbs[HttpVerbMaximum]; + char* verbs[] = { - NULL, - NULL, - NULL, - "OPTIONS", - "GET", - "HEAD", - "POST", - "PUT", - "DELETE", - "TRACE", - "CONNECT", - "TRACK", - "MOVE", - "COPY", - "PROPFIND", - "PROPPATCH", - "MKCOL", - "LOCK", - "UNLOCK", - "SEARCH" + NULL, + NULL, + NULL, + "OPTIONS", + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "TRACE", + "CONNECT", + "TRACK", + "MOVE", + "COPY", + "PROPFIND", + "PROPPATCH", + "MKCOL", + "LOCK", + "UNLOCK", + "SEARCH" }; -// Processing common to all callbacks from HTTP.SYS: -// - map the uv_async_t handle to uv_httpsys_t -// - clean up uv_async indicate completion of async operation -#define HTTPSYS_CALLBACK_PREAMBLE \ - HandleScope handleScope; \ - uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)handle->data; \ - NTSTATUS overlappedResult = (NTSTATUS)uv_httpsys->uv_async->async_req.overlapped.Internal; \ - ULONG overlappedLength = (ULONG)uv_httpsys->uv_async->async_req.overlapped.InternalHigh; \ - httpsys_uv_httpsys_close(uv_httpsys); \ - PHTTP_REQUEST request = (PHTTP_REQUEST)uv_httpsys->buffer; - // Processing common to most exported methods: // - declare handle scope and hr // - extract uv_httpsys_t from the internal field of the object passed as the first parameter +//uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)Handle::Cast(args[0])->GetPointerFromInternalField(0); + #define HTTPSYS_EXPORT_PREAMBLE \ - HandleScope handleScope; \ HRESULT hr; \ - uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)Handle::Cast(args[0])->GetPointerFromInternalField(0); + Handle o = Handle::Cast(info[0]); \ + uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)Nan::GetInternalFieldPointer(o, 0); HRESULT httpsys_uv_httpsys_init(uv_httpsys_t* uv_httpsys, uv_async_cb callback) { - HRESULT hr; - - ErrorIf(NULL != uv_httpsys->uv_async, E_FAIL); - ErrorIf(NULL == (uv_httpsys->uv_async = new uv_async_t), ERROR_NOT_ENOUGH_MEMORY); - RtlZeroMemory(uv_httpsys->uv_async, sizeof(uv_async_t)); - CheckError(uv_async_init(uv_default_loop(), uv_httpsys->uv_async, callback)); - uv_httpsys->uv_async->data = uv_httpsys; - uv_httpsys->uv_httpsys_server->refCount++; + HRESULT hr; - return S_OK; + ErrorIf(NULL != uv_httpsys->uv_async, E_FAIL); + ErrorIf(NULL == (uv_httpsys->uv_async = new uv_async_t), ERROR_NOT_ENOUGH_MEMORY); + RtlZeroMemory(uv_httpsys->uv_async, sizeof(uv_async_t)); + CheckError(uv_async_init(uv_default_loop(), uv_httpsys->uv_async, callback)); + uv_httpsys->uv_async->data = uv_httpsys; + uv_httpsys->uv_httpsys_server->refCount++; + Log("uv_async_init '%p', uv_httpsys_server->refCount '%d'\n", uv_httpsys->uv_async, uv_httpsys->uv_httpsys_server->refCount); + return S_OK; Error: - - return hr; + Log("uv_async_init FAILED as uv_httpsys->uv_async not null\n"); + return hr; } void httpsys_close_uv_async_cb(uv_handle_t* uv_handle) { - delete uv_handle; + Log("httpsys_close_uv_async_cb called '%p'\n", uv_handle); + delete uv_handle; } HRESULT httpsys_uv_httpsys_close(uv_httpsys_t* uv_httpsys) { - HRESULT hr; + HRESULT hr; - ErrorIf(NULL == uv_httpsys->uv_async, E_FAIL); - uv_close((uv_handle_t*)uv_httpsys->uv_async, httpsys_close_uv_async_cb); - uv_httpsys->uv_async = NULL; - uv_httpsys->uv_httpsys_server->refCount--; + Log("entered httpsys_uv_httpsys_close for request id '%I64u, uv_async '%p'\n", uv_httpsys->requestId, (void*)uv_httpsys->uv_async); + ErrorIf(NULL == uv_httpsys->uv_async, E_FAIL); + Log("calling uv_close"); + uv_close((uv_handle_t*)uv_httpsys->uv_async, httpsys_close_uv_async_cb); + uv_httpsys->uv_async = NULL; + uv_httpsys->uv_httpsys_server->refCount--; + Log("uv_async cleared and uv_httpsys_server->refCount '%d'\n", uv_httpsys->uv_httpsys_server->refCount); - return S_OK; + return S_OK; Error: - - return hr; + Log("httpsys_uv_httpsys_close failed as uv_async already null\n"); + return hr; } Handle httpsys_make_callback(Handle options) { - HandleScope handleScope; - Handle argv[] = { options }; - - TryCatch try_catch; + Nan::EscapableHandleScope handleScope; + Handle argv[] = { options }; - Handle result = callback->Call(Context::GetCurrent()->Global(), 1, argv); + Nan::TryCatch try_catch; - if (try_catch.HasCaught()) { - node::FatalException(try_catch); - } + Handle result; + Nan::Call(*callback, 1, argv).ToLocal(&result); - return handleScope.Close(result); + if (try_catch.HasCaught()) { + Nan::FatalException(try_catch); + } + return handleScope.Escape(result); } Handle httpsys_create_event(uv_httpsys_server_t* uv_httpsys_server, int eventType) { - HandleScope handleScope; - - uv_httpsys_server->event->Set(v8eventType, Integer::NewFromUnsigned(eventType)); - - return uv_httpsys_server->event; + Handle o = Nan::New(uv_httpsys_server->event); + o->Set(Nan::New(v8eventType), Nan::New(eventType)); + return Nan::New(uv_httpsys_server->event); } Handle httpsys_create_event(uv_httpsys_t* uv_httpsys, int eventType) { - HandleScope handleScope; - - uv_httpsys->event->Set(v8eventType, Integer::NewFromUnsigned(eventType)); - - return uv_httpsys->event; + Handle o = Nan::New(uv_httpsys->event); + o->Set(Nan::New(v8eventType), Nan::New(eventType)); + return Nan::New(uv_httpsys->event); } Handle httpsys_notify_error(uv_httpsys_server_t* uv_httpsys_server, uv_httpsys_event_type errorType, unsigned int code) { - HandleScope handleScope; - - Handle error = httpsys_create_event(uv_httpsys_server, errorType); - error->Set(v8code, Integer::NewFromUnsigned(code)); - - return handleScope.Close(httpsys_make_callback(error)); + Nan::EscapableHandleScope handleScope; + Log("_NOTIFY_ERROR: httpsys_server event type '%d', code '%d'\n", errorType, code); + Handle error = httpsys_create_event(uv_httpsys_server, errorType); + error->Set(Nan::New(v8code), Nan::New(code)); + return handleScope.Escape(httpsys_make_callback(error)); } Handle httpsys_notify_error(uv_httpsys_t* uv_httpsys, uv_httpsys_event_type errorType, unsigned int code) { - HandleScope handleScope; - - Handle error = httpsys_create_event(uv_httpsys, errorType); - error->Set(v8code, Integer::NewFromUnsigned(code)); - - return handleScope.Close(httpsys_make_callback(error)); + Nan::EscapableHandleScope handleScope; + Log("_NOTIFY_ERROR: httpsys event type '%d', code '%d'\n", errorType, code); + Handle error = httpsys_create_event(uv_httpsys, errorType); + error->Set(Nan::New(v8code), Nan::New(code)); + return handleScope.Escape(httpsys_make_callback(error)); } -void httpsys_new_request_callback(uv_async_t* handle, int status) +// get sockaddr, IPv4 or IPv6: +void *get_in_addr(struct sockaddr *sa) { - HTTPSYS_CALLBACK_PREAMBLE - BOOL isUpgrade = FALSE; - - // Copy the request ID assigned to the request by HTTP.SYS to uv_httpsys - // to start subsequent async operations related to this request - - uv_httpsys->requestId = request->RequestId; - - // Increase the count of new read requests to initialize to replace the one that just completed. - // Actual initialization will be done in the uv_prepare callback httpsys_prepare_new_requests - // associated with this server. - - uv_httpsys->uv_httpsys_server->readsToInitialize++; - - // Initialize the JavaScript representation of an event object that will be used - // to marshall data into JavaScript for the lifetime of this request. - - uv_httpsys->event = Persistent::New(httpsysObject->NewInstance()); - uv_httpsys->event->SetPointerInInternalField(0, (void*)uv_httpsys); - uv_httpsys->event->Set(v8uv_httpsys_server, uv_httpsys->uv_httpsys_server->event); - - // Process async completion + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*)sa)->sin_addr); + } + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} - if (S_OK != overlappedResult) - { - // Async completion failed - notify JavaScript - - httpsys_notify_error( - uv_httpsys, - HTTPSYS_ERROR_NEW_REQUEST, - (unsigned int)overlappedResult); - - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; - } - else - { - // New request received - notify JavaScript - - Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_NEW_REQUEST); - - // Create the 'req' object representing the request - - Handle req = Object::New(); - event->Set(v8req, req); - - // Add HTTP verb information - - if (HttpVerbUnknown == request->Verb) - { - req->Set(v8method, String::New(request->pUnknownVerb)); - } - else - { - req->Set(v8method, v8verbs[request->Verb]); - } - - // Add known HTTP header information - - Handle headers = Object::New(); - req->Set(v8httpHeaders, headers); - - for (int i = 0; i < HttpHeaderRequestMaximum; i++) - { - if (request->Headers.KnownHeaders[i].RawValueLength > 0) - { - if (7 == i) { - // This is an upgrade header indicatting a potential upgrade - - isUpgrade = TRUE; - } - - headers->Set(v8httpRequestHeaderNames[i], String::New( - request->Headers.KnownHeaders[i].pRawValue, - request->Headers.KnownHeaders[i].RawValueLength)); - } - } - - // Add custom HTTP header information - - for (int i = 0; i < request->Headers.UnknownHeaderCount; i++) - { - // Node expects header names in lowercase. - // In-place convert unknown header names to lowercase. - - for (int k = 0; k < request->Headers.pUnknownHeaders[i].NameLength; k++) { - ((PSTR)request->Headers.pUnknownHeaders[i].pName)[k] = - tolower(request->Headers.pUnknownHeaders[i].pName[k]); - } - - headers->Set( - String::New( - request->Headers.pUnknownHeaders[i].pName, - request->Headers.pUnknownHeaders[i].NameLength), - String::New( - request->Headers.pUnknownHeaders[i].pRawValue, - request->Headers.pUnknownHeaders[i].RawValueLength)); - } - - // TODO: process trailers - - // Add HTTP version information - - req->Set(v8httpVersionMajor, Integer::NewFromUnsigned(request->Version.MajorVersion)); - req->Set(v8httpVersionMinor, Integer::NewFromUnsigned(request->Version.MinorVersion)); - - // Add URL information - - req->Set(v8url, String::New(request->pRawUrl, request->RawUrlLength)); - - // Add client X.509 information - - if (NULL != request->pSslInfo && NULL != request->pSslInfo->pClientCertInfo) - { - req->Set( - v8clientCertInfo, - httpsys_create_client_cert_info(request->pSslInfo->pClientCertInfo)); - } - - // Invoke the JavaScript callback passing event as the only paramater - - Handle result = httpsys_make_callback(event); - if (result->IsBoolean() && result->BooleanValue()) - { - // If the callback response is 'true', proceed to process the request body. - // Otherwise request had been paused and will be resumed asynchronously from JavaScript - // with a call to httpsys_resume. - - if (0 == (request->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS) && !isUpgrade) - { - // This is a body-less request. Notify JavaScript the request is finished. - // Note that for HTTP upgrade paths this flag appears not to be set. - - Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_END_REQUEST); - httpsys_make_callback(event); - } - else - { - // Start synchronous body reading loop. - - httpsys_read_request_body_loop(uv_httpsys); - } - } - } +void httpsys_new_request_callback(uv_async_t* handle) +{ + Nan::HandleScope handleScope; + uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)handle->data; + NTSTATUS overlappedResult = (NTSTATUS)uv_httpsys->uv_async->async_req.u.io.overlapped.Internal; + ULONG overlappedLength = (ULONG)uv_httpsys->uv_async->async_req.u.io.overlapped.InternalHigh; + PHTTP_REQUEST request = (PHTTP_REQUEST)uv_httpsys->buffer; + + Log("REQ: httpsys_new_request_callback, request id '%I64u', result 0x%08x, buffer size '%d', required '%d'\n", + request->RequestId, + overlappedResult, + uv_httpsys->bufferSize, + overlappedLength); + + httpsys_uv_httpsys_close(uv_httpsys); + + //if the async callback fails check for server shutting down. Actual value returned + //overlappedResult for shut down is STATUS_CANCELLED (0xc0000120). + if (S_OK != overlappedResult) + { + //are we shutting down? + if (uv_httpsys->uv_httpsys_server->closing = TRUE) + { + Log("server is closing down, abort processing new request\n"); + //free resources + Log("freeing uv_httpsys request id = '%I64u'\n", uv_httpsys->requestId); + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + return; + } + } + + //if the http request header is bigger then the available buffer, + //the async receive request will fail + if (S_OK == overlappedResult && request->pRawUrl != NULL) { + Log("REQ: received raw url '%s'\n", + request->pRawUrl); + } + + BOOL isUpgrade = FALSE; + + // Copy the request ID assigned to the request by HTTP.SYS to uv_httpsys + // to start subsequent async operations related to this request + + uv_httpsys->requestId = request->RequestId; + + // Increase the count of new read requests to initialize to replace the one that just completed. + // Actual initialization will be done in the uv_prepare callback httpsys_prepare_new_requests + // associated with this server. + + uv_httpsys->uv_httpsys_server->readsToInitialize++; + Log("REQ: incrementing readsToInitialize to ready next request, new value '%d'\n", + uv_httpsys->uv_httpsys_server->readsToInitialize); + + // Initialize the JavaScript representation of an event object that will be used + // to marshall data into JavaScript for the lifetime of this request. + Local tpl = Nan::New(httpsysObject); + Local o = tpl->NewInstance(); + uv_httpsys->event.Reset(o); + + Nan::SetInternalFieldPointer(o, 0, (void*)uv_httpsys); + o->Set(Nan::New(v8uv_httpsys_server), Nan::New(uv_httpsys->uv_httpsys_server->event)); + + // Process async completion + if (S_OK != overlappedResult) + { + // Async completion failed - notify JavaScript + Log("REQ: creating new request error\n"); + + //JPW ADDED + uv_httpsys->refCount++; + + httpsys_notify_error( + uv_httpsys, + HTTPSYS_ERROR_NEW_REQUEST, + (unsigned int)overlappedResult); + + uv_httpsys->refCount--; + if (uv_httpsys->refCount == 0) { + Log("REQ: freeing uv_httpsys request id = '%I64u'\n", uv_httpsys->requestId); + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + } + } + else + { + Log("REQ: building new request, request id = '%I64u'\n", uv_httpsys->requestId); + // New request received, build event to pass to JavaScript + Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_NEW_REQUEST); + + // Create the 'req' object representing the request + Handle req = Nan::New(); + event->Set(Nan::New(v8req), req); + + char remoteAddress[INET6_ADDRSTRLEN]; + memset(remoteAddress, 0, INET6_ADDRSTRLEN); + if (request->Address.pRemoteAddress != NULL) { + //extract remote connection ip + inet_ntop( + request->Address.pRemoteAddress->sa_family, + get_in_addr((struct sockaddr *)request->Address.pRemoteAddress), + remoteAddress, + sizeof remoteAddress); + } + req->Set(Nan::New(v8remoteAddress), Nan::New(remoteAddress).ToLocalChecked()); + + // Add HTTP verb information + if (HttpVerbUnknown == request->Verb) + { + req->Set(Nan::New(v8method), Nan::New(request->pUnknownVerb).ToLocalChecked()); + } + else + { + req->Set(Nan::New(v8method), Nan::New(v8verbs[request->Verb])); + } + + // Add known HTTP header information + Handle headers = Nan::New(); + req->Set(Nan::New(v8httpHeaders), headers); + + for (int i = 0; i < HttpHeaderRequestMaximum; i++) + { + if (request->Headers.KnownHeaders[i].RawValueLength > 0) + { + if (7 == i) { + // This is an upgrade header indicating a potential upgrade + Log("REQ: received an upgrade request"); + isUpgrade = TRUE; + } + + headers->Set(Nan::New(v8httpRequestHeaderNames[i]), Nan::New( + request->Headers.KnownHeaders[i].pRawValue, + request->Headers.KnownHeaders[i].RawValueLength).ToLocalChecked()); + } + } + + // Add custom HTTP header information + for (int i = 0; i < request->Headers.UnknownHeaderCount; i++) + { + // Node expects header names in lowercase. + // In-place convert unknown header names to lowercase. + + for (int k = 0; k < request->Headers.pUnknownHeaders[i].NameLength; k++) { + ((PSTR)request->Headers.pUnknownHeaders[i].pName)[k] = + tolower(request->Headers.pUnknownHeaders[i].pName[k]); + } + + headers->Set( + Nan::New( + request->Headers.pUnknownHeaders[i].pName, + request->Headers.pUnknownHeaders[i].NameLength).ToLocalChecked(), + Nan::New( + request->Headers.pUnknownHeaders[i].pRawValue, + request->Headers.pUnknownHeaders[i].RawValueLength).ToLocalChecked()); + } + + // TODO: process trailers + + // Add HTTP version information + + req->Set(Nan::New(v8httpVersionMajor), Nan::New(request->Version.MajorVersion)); + req->Set(Nan::New(v8httpVersionMinor), Nan::New(request->Version.MinorVersion)); + + // Add URL information + + req->Set(Nan::New(v8url), Nan::New(request->pRawUrl, request->RawUrlLength).ToLocalChecked()); + + // Add client X.509 information + + if (NULL != request->pSslInfo && NULL != request->pSslInfo->pClientCertInfo) + { + req->Set( + Nan::New(v8clientCertInfo), + httpsys_create_client_cert_info(request->pSslInfo->pClientCertInfo)); + } + + // Invoke the JavaScript callback passing event as the only paramater + Log("REQ: passing event to javascript request id = '%I64u', uv_httpsys = '%p'\n", uv_httpsys->requestId, (void*)uv_httpsys); + Handle result = httpsys_make_callback(event); + Log("REQ: back from javascript uv_httpsys = '%p'\n", (void*)uv_httpsys); + + if (result->IsBoolean() && result->BooleanValue()) + { + Log("REQ: true returned from javascript, continue... checking for request body\n"); + // If the callback response is 'true', proceed to process the request body. + // Otherwise request had been paused and will be resumed asynchronously from JavaScript + // with a call to httpsys_resume. + + if (0 == (request->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS) && !isUpgrade) + { + Log("REQ: body-less request, create END request and pass to javascript, request id = '%I64u', uv_httpsys = '%p'\n", uv_httpsys->requestId, (void*)uv_httpsys); + // This is a body-less request. Notify JavaScript the request is finished. + // Note that for HTTP upgrade paths this flag appears not to be set. + + Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_END_REQUEST); + httpsys_make_callback(event); + Log("REQ: back from javascript uv_httpsys = '%p'\n", (void*)uv_httpsys); + } + else + { + // Start synchronous body reading loop. + Log("REQ: **Start synchronous body reading loop, isUpgrade = '%d'\n", isUpgrade); + httpsys_read_request_body_loop(uv_httpsys); + } + } + } + Log("REQ: exiting httpsys_new_request_callback\n"); } Handle httpsys_create_client_cert_info(PHTTP_SSL_CLIENT_CERT_INFO info) { - HandleScope scope; - - Handle certInfo = Object::New(); - - // Set the authentication result - - certInfo->Set(v8authorizationError, Number::New(info->CertFlags)); - - // Decode the certificate and create V8 representation - // http://msdn.microsoft.com/en-us/library/windows/desktop/aa381955(v=vs.85).aspx - - PCCERT_CONTEXT certContext = CertCreateCertificateContext( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - info->pCertEncoded, - info->CertEncodedSize); - DWORD size; - char* str = NULL; - ULONG time; - - if (NULL != certContext) - { - Handle cert = Object::New(); - certInfo->Set(v8cert, cert); - - // Set the Subject's X500 name - - size = CertNameToStr( - X509_ASN_ENCODING, - &certContext->pCertInfo->Subject, - CERT_X500_NAME_STR, - NULL, - 0); - - if (size > 0 && (NULL != (str = (char*)malloc(size)))) - { - CertNameToStr( - X509_ASN_ENCODING, - &certContext->pCertInfo->Subject, - CERT_X500_NAME_STR, - str, - size); - - cert->Set(v8subject, String::New(str)); - free(str); - str = NULL; - } - - // Set the Issuer's X500 name - - size = CertNameToStr( - X509_ASN_ENCODING, - &certContext->pCertInfo->Issuer, - CERT_X500_NAME_STR, - NULL, - 0); - - if (size > 0 && (NULL != (str = (char*)malloc(size)))) - { - CertNameToStr( - X509_ASN_ENCODING, - &certContext->pCertInfo->Issuer, - CERT_X500_NAME_STR, - str, - size); - - cert->Set(v8issuer, String::New(str)); - free(str); - str = NULL; - } - - // Set the validity period - - if (RtlTimeToSecondsSince1970Impl) - { - RtlTimeToSecondsSince1970Impl( - (PLARGE_INTEGER)&certContext->pCertInfo->NotBefore, - &time); - cert->Set(v8validFrom, Number::New(time)); - - RtlTimeToSecondsSince1970Impl( - (PLARGE_INTEGER)&certContext->pCertInfo->NotAfter, - &time); - cert->Set(v8validTo, Number::New(time)); - } - - // Set the thumbprint - - size = 0; - if (CertGetCertificateContextProperty(certContext, CERT_SHA1_HASH_PROP_ID, NULL, &size) - && (NULL != (str = (char*)malloc(size))) - && CertGetCertificateContextProperty(certContext, CERT_SHA1_HASH_PROP_ID, str, &size)) - { - node::Buffer* slowBuffer = node::Buffer::New(size); - memcpy(node::Buffer::Data(slowBuffer), str, size); - Handle args[] = { slowBuffer->handle_, Integer::New(size), Integer::New(0) }; - Handle fastBuffer = bufferConstructor->NewInstance(3, args); - cert->Set(v8fingerprint, fastBuffer); - - free(str); - str = NULL; - } - - // If HTTPSYS_EXPORT_CLIENT_CERT environment variable is set, - // export the raw X.509 certificate presented by the client - - if (httpsys_export_client_cert) - { - node::Buffer* slowBuffer = node::Buffer::New(certContext->cbCertEncoded); - memcpy(node::Buffer::Data(slowBuffer), certContext->pbCertEncoded, certContext->cbCertEncoded); - Handle args[] = { slowBuffer->handle_, Integer::New(certContext->cbCertEncoded), Integer::New(0) }; - Handle fastBuffer = bufferConstructor->NewInstance(3, args); - cert->Set(v8encoded, fastBuffer); - } - - CertFreeCertificateContext(certContext); - } - - return scope.Close(certInfo); + char* slowBuffer; + Nan::EscapableHandleScope scope; + Handle certInfo = Nan::New(); + // Set the authentication result + certInfo->Set(Nan::New(v8authorizationError), Nan::New(info->CertFlags)); + // Decode the certificate and create V8 representation + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa381955(v=vs.85).aspx + + PCCERT_CONTEXT certContext = CertCreateCertificateContext( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + info->pCertEncoded, + info->CertEncodedSize); + + DWORD size; + char* str = NULL; + ULONG time; + + if (NULL != certContext) + { + Handle cert = Nan::New(); + certInfo->Set(Nan::New(v8cert), cert); + // Set the Subject's X500 name + size = CertNameToStr( + X509_ASN_ENCODING, + &certContext->pCertInfo->Subject, + CERT_X500_NAME_STR, + NULL, + 0); + + if (size > 0 && (NULL != (str = (char*)malloc(size)))) + { + CertNameToStr( + X509_ASN_ENCODING, + &certContext->pCertInfo->Subject, + CERT_X500_NAME_STR, + str, + size); + + cert->Set(Nan::New(v8subject), Nan::New(str).ToLocalChecked()); + free(str); + str = NULL; + } + + // Set the Issuer's X500 name + + size = CertNameToStr( + X509_ASN_ENCODING, + &certContext->pCertInfo->Issuer, + CERT_X500_NAME_STR, + NULL, + 0); + + if (size > 0 && (NULL != (str = (char*)malloc(size)))) + { + CertNameToStr( + X509_ASN_ENCODING, + &certContext->pCertInfo->Issuer, + CERT_X500_NAME_STR, + str, + size); + + cert->Set(Nan::New(v8issuer), Nan::New(str).ToLocalChecked()); + free(str); + str = NULL; + } + + // Set the validity period + + if (RtlTimeToSecondsSince1970Impl) + { + RtlTimeToSecondsSince1970Impl( + (PLARGE_INTEGER)&certContext->pCertInfo->NotBefore, + &time); + + cert->Set(Nan::New(v8validFrom), Nan::New(time)); + + RtlTimeToSecondsSince1970Impl( + (PLARGE_INTEGER)&certContext->pCertInfo->NotAfter, + &time); + + cert->Set(Nan::New(v8validTo), Nan::New(time)); + } + + // Set the thumbprint + + size = 0; + if (CertGetCertificateContextProperty(certContext, CERT_SHA1_HASH_PROP_ID, NULL, &size) + && (NULL != (str = (char*)malloc(size))) + && CertGetCertificateContextProperty(certContext, CERT_SHA1_HASH_PROP_ID, str, &size)) + { + if (NULL != (slowBuffer = (char*)malloc(size))) + { + memcpy(slowBuffer, (char*)str, size); + Handle args[] = { Nan::NewBuffer(slowBuffer,size).ToLocalChecked(), Nan::New(size), Nan::New(0) }; + Local cons = Nan::New(bufferConstructor); + Handle fastBuffer; + cons->NewInstance(Nan::GetCurrentContext(), 3, args).ToLocal(&fastBuffer); + cert->Set(Nan::New(v8fingerprint), fastBuffer); + free(str); + str = NULL; + } + else + { + Log("CertGetCertificateContextProperty: failed to allocate memory\n"); + } + } + + // If HTTPSYS_EXPORT_CLIENT_CERT environment variable is set, + // export the raw X.509 certificate presented by the client + if (httpsys_export_client_cert) + { + if (NULL != (slowBuffer = (char*)malloc(certContext->cbCertEncoded))) + { + memcpy(slowBuffer, (char*)certContext->pbCertEncoded, certContext->cbCertEncoded); + Handle args[] = { Nan::NewBuffer(slowBuffer, certContext->cbCertEncoded).ToLocalChecked(), Nan::New(certContext->cbCertEncoded), Nan::New(0) }; + Local cons = Nan::New(bufferConstructor); + Handle fastBuffer; + cons->NewInstance(Nan::GetCurrentContext(), 3, args).ToLocal(&fastBuffer); + cert->Set(Nan::New(v8encoded), fastBuffer); + } + else + { + Log("httpsys_export_client_cert: failed to allocate memory\n"); + } + } + CertFreeCertificateContext(certContext); + } + + return scope.Escape(certInfo); } HRESULT httpsys_initiate_new_request(uv_httpsys_t* uv_httpsys) { - HRESULT hr; - - // Create libuv async handle and initialize it - - CheckError(httpsys_uv_httpsys_init(uv_httpsys, httpsys_new_request_callback)); - - // Allocate initial buffer to receice the HTTP request - - uv_httpsys->bufferSize = initialBufferSize; - ErrorIf(NULL == (uv_httpsys->buffer = malloc(uv_httpsys->bufferSize)), ERROR_NOT_ENOUGH_MEMORY); - RtlZeroMemory(uv_httpsys->buffer, uv_httpsys->bufferSize); - - // Initiate async receive of a new request with HTTP.SYS, using the OVERLAPPED - // associated with the default libuv event loop. - - hr = HttpReceiveHttpRequest( - uv_httpsys->uv_httpsys_server->requestQueue, - HTTP_NULL_ID, - 0, - (PHTTP_REQUEST)uv_httpsys->buffer, - uv_httpsys->bufferSize, - NULL, - &uv_httpsys->uv_async->async_req.overlapped); - - if (NO_ERROR == hr) - { - // Synchronous completion. - - httpsys_new_request_callback(uv_httpsys->uv_async, 0); - } - else - { - ErrorIf(ERROR_IO_PENDING != hr, hr); - } - - return S_OK; + HRESULT hr; + + // Create libuv async handle and initialize it + Log("REQ: entered httpsys_initiate_new_request request id = '%I64u'\n", uv_httpsys->requestId); + + CheckError(httpsys_uv_httpsys_init(uv_httpsys, (uv_async_cb)httpsys_new_request_callback)); + + // Allocate initial buffer to receice the HTTP request + + uv_httpsys->bufferSize = initialBufferSize; + ErrorIf(NULL == (uv_httpsys->buffer = malloc(uv_httpsys->bufferSize)), ERROR_NOT_ENOUGH_MEMORY); + RtlZeroMemory(uv_httpsys->buffer, uv_httpsys->bufferSize); + + // Initiate async receive of a new request with HTTP.SYS, using the OVERLAPPED + // associated with the default libuv event loop. + + hr = HttpReceiveHttpRequest( + uv_httpsys->uv_httpsys_server->requestQueue, + HTTP_NULL_ID, + 0, + (PHTTP_REQUEST)uv_httpsys->buffer, + uv_httpsys->bufferSize, + NULL, + &uv_httpsys->uv_async->async_req.u.io.overlapped); + + Log("REQ: result from HttpReceiveHttpRequest '%d'\n", hr); + + if (NO_ERROR == hr) + { + // Synchronous completion. + Log("REQ: synchronous completion, calling httpsys_new_request_callback\n"); + httpsys_new_request_callback(uv_httpsys->uv_async); + } + else + { + ErrorIf(ERROR_IO_PENDING != hr, hr); + } + Log("REQ: exited httpsys_initiate_new_request\n"); + return S_OK; Error: - - return hr; + Log("REQ: httpsys_initiate_new_request has errored hr = '%d'\n", hr); + return hr; } void httpsys_free_chunks(uv_httpsys_t* uv_httpsys) { - if (uv_httpsys->chunk.FromMemory.pBuffer) - { - free(uv_httpsys->chunk.FromMemory.pBuffer); - RtlZeroMemory(&uv_httpsys->chunk, sizeof(uv_httpsys->chunk)); - } + Log("entered httpsys_free_chunks for request id '%I64u\n", uv_httpsys->requestId); + if (uv_httpsys->chunk.FromMemory.pBuffer) + { + free(uv_httpsys->chunk.FromMemory.pBuffer); + RtlZeroMemory(&uv_httpsys->chunk, sizeof(uv_httpsys->chunk)); + } } void httpsys_free(uv_httpsys_t* uv_httpsys, BOOL error) { - if (NULL != uv_httpsys) - { - // For upgraded requests, two uv_httpsys instances exist: one for request and the other for response. - // The last one to close cleans up shared resources as well as disposes of the peer. - - uv_httpsys->closed = TRUE; - - httpsys_free_chunks(uv_httpsys); - - if (!uv_httpsys->event.IsEmpty()) - { - uv_httpsys->event.Dispose(); - uv_httpsys->event.Clear(); - } - - if (uv_httpsys->response.pReason) - { - free((void*)uv_httpsys->response.pReason); - } - - for (int i = 0; i < HttpHeaderResponseMaximum; i++) - { - if (uv_httpsys->response.Headers.KnownHeaders[i].pRawValue) - { - free((void*)uv_httpsys->response.Headers.KnownHeaders[i].pRawValue); - } - } - - if (uv_httpsys->response.Headers.pUnknownHeaders) - { - for (int i = 0; i < uv_httpsys->response.Headers.UnknownHeaderCount; i++) - { - if (uv_httpsys->response.Headers.pUnknownHeaders[i].pName) - { - free((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pName); - } - - if (uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue) - { - free((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue); - } - } - - free(uv_httpsys->response.Headers.pUnknownHeaders); - } - - RtlZeroMemory(&uv_httpsys->response, sizeof (uv_httpsys->response)); - - if (uv_httpsys->uv_async) - { - httpsys_uv_httpsys_close(uv_httpsys); - } - - if (NULL != uv_httpsys->buffer) - { - free(uv_httpsys->buffer); - uv_httpsys->buffer = NULL; - } - - if (NULL != uv_httpsys->uv_httpsys_peer) { - - // The uv_httpsys structure is paired with another in the HTTP upgrade scenario to - // support concurrent reads and writes. Disposal logic: - // 1. During normal operation (no error) the second uv_httpsys to be freed disposes the pair. - // 2. If an error occurrs: - // 2.1. If there is an async operation pending against the second uv_httpsys, it is marked - // for disposal. The async completion callback will re-enter httpsys_free for the second - // uv_httpsys structure in order to finish the cleanup. - // 2.2. If there is no async operation pending against the second uv_httpsys, the pair - // is disposed immediately. - - if (uv_httpsys->uv_httpsys_peer->closed) { - // #1 - delete uv_httpsys->uv_httpsys_peer; - uv_httpsys->uv_httpsys_peer = NULL; - delete uv_httpsys; - uv_httpsys = NULL; - } - else if (error) { - if (uv_httpsys->uv_httpsys_peer->uv_async) { - // #2.1 - uv_httpsys->uv_httpsys_peer->disconnect = TRUE; - } - else { - // #2.2 - httpsys_free(uv_httpsys->uv_httpsys_peer, FALSE); - } - } - } - else { - - // The regular HTTP request scenario - single uv_httpsys instance. - - delete uv_httpsys; - uv_httpsys = NULL; - } - } + if (NULL != uv_httpsys) + { + Log("httpsys_free called request id ='%I64u', uv_httpsys = '%p', uv_httpsys_peer = '%p', refcount = '%d'\n", uv_httpsys->requestId, (void*)uv_httpsys, uv_httpsys->uv_httpsys_peer, uv_httpsys->refCount); + + // For upgraded requests, two uv_httpsys instances exist: one for request and the other for response. + // The last one to close cleans up shared resources as well as disposes of the peer. + uv_httpsys->closed = TRUE; + + Log("freeing chunks...\n"); + httpsys_free_chunks(uv_httpsys); + + if (!uv_httpsys->event.IsEmpty()) { + Log("freeing uv_httpsys->event...\n"); + uv_httpsys->event.Reset(); + } + + if (uv_httpsys->response.pReason) { + Log("freeing uv_httpsys->response.pReason...\n"); + free((void*)uv_httpsys->response.pReason); + } + + for (int i = 0; i < HttpHeaderResponseMaximum; i++) { + if (uv_httpsys->response.Headers.KnownHeaders[i].pRawValue) { + Log("freeing known header '%.*s'\n", + uv_httpsys->response.Headers.KnownHeaders[i].RawValueLength, + uv_httpsys->response.Headers.KnownHeaders[i].pRawValue); + + free((void*)uv_httpsys->response.Headers.KnownHeaders[i].pRawValue); + } + } + + if (uv_httpsys->response.Headers.pUnknownHeaders) { + for (int i = 0; i < uv_httpsys->response.Headers.UnknownHeaderCount; i++) { + if (uv_httpsys->response.Headers.pUnknownHeaders[i].pName) { + Log("freeing unknown header name '%.*s'\n", + uv_httpsys->response.Headers.pUnknownHeaders[i].NameLength, + uv_httpsys->response.Headers.pUnknownHeaders[i].pName); + + free((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pName); + } + if (uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue) { + Log("freeing unknown header value '%.*s'\n", + uv_httpsys->response.Headers.pUnknownHeaders[i].RawValueLength, + uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue); + + free((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue); + } + } + Log("freeing header memory '%p'\n", (void*)uv_httpsys->response.Headers.pUnknownHeaders); + free(uv_httpsys->response.Headers.pUnknownHeaders); + } + + RtlZeroMemory(&uv_httpsys->response, sizeof(uv_httpsys->response)); + + if (uv_httpsys->uv_async) { + Log("requesting close of uv_async '%p\n", (void*)uv_httpsys->uv_async); + httpsys_uv_httpsys_close(uv_httpsys); + } + + if (NULL != uv_httpsys->buffer) { + Log("freeing uv_httpsys->buffer '%p'\n", (void*)uv_httpsys->buffer); + free(uv_httpsys->buffer); + uv_httpsys->buffer = NULL; + } + + if (NULL != uv_httpsys->uv_httpsys_peer) { + Log("this is an upgraded connection, freeing...\n") + // The uv_httpsys structure is paired with another in the HTTP upgrade scenario to + // support concurrent reads and writes. Disposal logic: + // 1. During normal operation (no error) the second uv_httpsys to be freed disposes the pair. + // 2. If an error occurrs: + // 2.1. If there is an async operation pending against the second uv_httpsys, it is marked + // for disposal. The async completion callback will re-enter httpsys_free for the second + // uv_httpsys structure in order to finish the cleanup. + // 2.2. If there is no async operation pending against the second uv_httpsys, the pair + // is disposed immediately. + + if (uv_httpsys->uv_httpsys_peer->closed) { + // #1 + Log("scenario #1, (no error) the second uv_httpsys to be freed disposes the pair\n"); + delete uv_httpsys->uv_httpsys_peer; + uv_httpsys->uv_httpsys_peer = NULL; + delete uv_httpsys; + uv_httpsys = NULL; + } + else if (error) { + if (uv_httpsys->uv_httpsys_peer->uv_async) { + // #2.1 + Log("scenario #2.1, If there is an async operation pending against the second uv_httpsys, it is marked for disposal. The async completion callback will re-enter httpsys_free for the second uv_httpsys structure in order to finish the cleanup\n"); + uv_httpsys->uv_httpsys_peer->disconnect = TRUE; + } + else { + // #2.2 + Log("scenario #2.2, If there is no async operation pending against the second uv_httpsys, the pair is disposed immediately\n"); + httpsys_free(uv_httpsys->uv_httpsys_peer, FALSE); + } + } + } + else { + + // The regular HTTP request scenario - single uv_httpsys instance. + Log("regular http request scenario, delete uv_httpsys instance\n"); + delete uv_httpsys; + uv_httpsys = NULL; + } + } + else { + Log("httpsys_free ignored as uv_httpsys is already NULL, uv_httpsys = '%p'\n", (void*)uv_httpsys); + } } -void httpsys_read_request_body_callback(uv_async_t* handle, int status) +void httpsys_read_request_body_callback(uv_async_t* handle) { - HTTPSYS_CALLBACK_PREAMBLE - - // The "status" parameter is 0 if the callback is an async completion from libuv. - // Otherwise, the parameter is a uv_httpsys_t** that the callback is supposed to set to the - // uv_httpsys that just completed if reading of the body should continue synchronously - // (i.e. there is no error during the callback and the application does not pause the request), - // or to NULL if the body should not be read any more. - - uv_httpsys_t** uv_httpsys_result = (uv_httpsys_t**)status; - if (uv_httpsys_result) - { - *uv_httpsys_result = NULL; - } - - // Process async completion - - if (uv_httpsys->disconnect) - { - // A request was made to disconnect the client when an async operation was in progress. - // Now that the async operation completed, disregard the results and free up resources. - - httpsys_free(uv_httpsys, FALSE); - uv_httpsys = NULL; - } - else if (ERROR_HANDLE_EOF == overlappedResult || 0 == overlappedLength) - { - // End of request body - notify JavaScript - - BOOL freePending = NULL != uv_httpsys->uv_httpsys_peer; - - if (!uv_httpsys->responseStarted) { - // Do not emit the `end` event if the app already started writing the response - - Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_END_REQUEST); - httpsys_make_callback(event); - } - - if (freePending) { - // This is an upgraded request which has a peer uv_httpsys to handle the response. - // Since the request uv_httpsys is no longer needed, deallocate it. - - httpsys_free(uv_httpsys, FALSE); - uv_httpsys = NULL; - } - } - else if (S_OK != overlappedResult) - { - // Async completion failed - notify JavaScript - - if (!uv_httpsys->responseStarted) { - // Do not emit the `error` event if the app already started writing the response - - httpsys_notify_error( - uv_httpsys, - HTTPSYS_ERROR_READ_REQUEST_BODY, - (unsigned int)overlappedResult); - } - - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; - } - else - { - // Successful completion - send body chunk to JavaScript as a Buffer - - BOOL continueReading = TRUE; - - // Good explanation of native Buffers at - // http://sambro.is-super-awesome.com/2011/03/03/creating-a-proper-buffer-in-a-node-c-addon/ - - if (!uv_httpsys->responseStarted) { - // Do not emit the `data` event if the app already started writing the response - - Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_REQUEST_BODY); - ULONG length = overlappedLength; - node::Buffer* slowBuffer = node::Buffer::New(length); - memcpy(node::Buffer::Data(slowBuffer), uv_httpsys->buffer, length); - Handle args[] = { slowBuffer->handle_, Integer::New(length), Integer::New(0) }; - Handle fastBuffer = bufferConstructor->NewInstance(3, args); - event->Set(v8data, fastBuffer); - - Handle result = httpsys_make_callback(event); - continueReading = result->IsBoolean() && result->BooleanValue(); - } - - if (continueReading) - { - // If the callback response is 'true', proceed to read more of the request body. - // Otherwise request had been paused and will be resumed asynchronously from JavaScript - // with a call to httpsys_resume. - - if (uv_httpsys_result) - { - // This is a synchronous completion of a read. Indicate to the caller the reading should continue - // and unwind the stack. - - *uv_httpsys_result = uv_httpsys; - } - else - { - // This is an asynchronous completion of a read. Restart the reading loop. - - httpsys_read_request_body_loop(uv_httpsys); - } - } - } + //HTTPSYS_CALLBACK_PREAMBLE + Nan::HandleScope handleScope; + uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)handle->data; + Log("REQ: entered httpsys_read_request_body_callback, synchronous '%d', request id '%I64u'\n", uv_httpsys->synchronous, uv_httpsys->requestId); + + NTSTATUS overlappedResult = (NTSTATUS)uv_httpsys->uv_async->async_req.u.io.overlapped.Internal; + ULONG overlappedLength = (ULONG)uv_httpsys->uv_async->async_req.u.io.overlapped.InternalHigh; + httpsys_uv_httpsys_close(uv_httpsys); + PHTTP_REQUEST request = (PHTTP_REQUEST)uv_httpsys->buffer; + int lastError = 0; + char* slowBuffer; + + // Process async completion + if (uv_httpsys->disconnect) + { + Log("REQ: disconnection received during operation, free request resources\n"); + // A request was made to disconnect the client when an async operation was in progress. + // Now that the async operation completed, disregard the results and free up resources. + + httpsys_free(uv_httpsys, FALSE); + uv_httpsys = NULL; + } + else if (ERROR_HANDLE_EOF == overlappedResult || 0 == overlappedLength) + { + Log("REQ: end of body\n"); + // End of request body - notify JavaScript + BOOL freePending = NULL != uv_httpsys->uv_httpsys_peer; + + if (!uv_httpsys->responseStarted) { + // Do not emit the `end` event if the app already started writing the response + Log("REQ: sending END event to javascript request id = '%I64u', uv_httpsys = '%p'\n", uv_httpsys->requestId, (void*)uv_httpsys); + Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_END_REQUEST); + Handle result = httpsys_make_callback(event); + //assume the worst, if an error code is not returned + lastError = ERROR_CONNECTION_INVALID; + //extract any errors + if (result->IsNumber()) { + lastError = result->Int32Value(); + Log("REQ: result from httpsys_end_request error = '%d'\n", lastError); + } + else { + Log("REQ: result from httpsys_end_request invalid number returned\n"); + } + + Log("REQ: back from javascript uv_httpsys = '%p'\n", (void*)uv_httpsys); + //during this callback a failed response write can occur + //resulting in native resources being freed + } + + //if the last error was an invalid connection, all native resources + //would have already been cleaned up so do not do it again + if (freePending && lastError != ERROR_CONNECTION_INVALID && lastError != ERROR_INVALID_HANDLE) { + // This is an upgraded request which has a peer uv_httpsys to handle the response. + // Since the request uv_httpsys is no longer needed, deallocate it. + Log("REQ: free pending, this is an upgraded request which has a peer uv_httpsys to handle the response\n"); + httpsys_free(uv_httpsys, FALSE); + uv_httpsys = NULL; + } + } + else if (S_OK != overlappedResult) + { + // Async completion failed - notify JavaScript + Log("REQ: async completion failed\n"); + + if (!uv_httpsys->responseStarted) { + // Do not emit the `error` event if the app already started writing the response + Log("REQ: read body request async completion failed\n"); + httpsys_notify_error( + uv_httpsys, + HTTPSYS_ERROR_READ_REQUEST_BODY, + (unsigned int)overlappedResult); + } + + Log("REQ: free uv_httpsys\n"); + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + } + else + { + Log("REQ: send body chunk to javascript\n"); + // Successful completion - send body chunk to JavaScript as a Buffer + + BOOL continueReading = TRUE; + + // Good explanation of native Buffers at + // http://sambro.is-super-awesome.com/2011/03/03/creating-a-proper-buffer-in-a-node-c-addon/ + + if (!uv_httpsys->responseStarted) { + // Do not emit the `data` event if the app already started writing the response + Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_REQUEST_BODY); + ULONG length = overlappedLength; + Log("REQ: response not started, build body buffer\n"); + + //slow buffer is a pointer to an HTTP request structure + if (NULL != (slowBuffer = (char*)malloc(length))) + { + memcpy(slowBuffer, (char*)uv_httpsys->buffer, length); + Handle args[] = { Nan::CopyBuffer(slowBuffer, length).ToLocalChecked(), Nan::New(length), Nan::New(0) }; + + Local cons = Nan::New(bufferConstructor); + Handle fastBuffer; + cons->NewInstance(Nan::GetCurrentContext(), 3, args).ToLocal(&fastBuffer); + event->Set(Nan::New(v8data), fastBuffer); + Log("REQ: calling javascript: httpsys_request_body request id = '%I64u', uv_httpsys = '%p'\n", uv_httpsys->requestId, (void*)uv_httpsys); + Handle result = httpsys_make_callback(event); + Log("REQ: back from javascript uv_httpsys = '%p'\n", (void*)uv_httpsys); + + free(slowBuffer); + + continueReading = result->IsBoolean() && result->BooleanValue(); + } + else { + continueReading = FALSE; + Log("REQ: failed to allocate memory will creating slow buffer\n"); + } + } + + if (continueReading) + { + Log("REQ: continuing reading body\n"); + // If the callback response is 'true', proceed to read more of the request body. + // Otherwise request had been paused and will be resumed asynchronously from JavaScript + // with a call to httpsys_resume. + if (!uv_httpsys->synchronous) + { + Log("REQ: synchronous body reading, calling httpsys_read_request_body_loop\n"); + httpsys_read_request_body_loop(uv_httpsys); + } + } + } + Log("REQ: exiting httpsys_read_request_body_callback\n"); } HRESULT httpsys_read_request_body_loop(uv_httpsys_t* uv_httpsys) { - HRESULT hr = S_OK; - - // Continue reading the request body synchronously until EOF, and error, - // request is paused or async completion is expected. - while (NULL != uv_httpsys && NO_ERROR == (hr = httpsys_initiate_read_request_body(uv_httpsys))) - { - // Use the "status" parameter to the callback as a mechanism to return data - // from the callback. If upon return the uv_httpsys is still not NULL, - // it means there was no error and the request was not paused by the application. - - httpsys_read_request_body_callback(uv_httpsys->uv_async, (int)&uv_httpsys); - } - - return (NO_ERROR == hr || ERROR_HANDLE_EOF == hr || ERROR_IO_PENDING == hr) ? S_OK : hr; + HRESULT hr = S_OK; + + Log("REQ: entered httpsys_read_request_body_loop\n"); + + // Continue reading the request body synchronously until EOF, and error, + // request is paused or async completion is expected. + while (NULL != uv_httpsys && NO_ERROR == (hr = httpsys_initiate_read_request_body(uv_httpsys))) + { + Log("REQ: synchronous read body completion\n"); + // Use the "status" parameter to the callback as a mechanism to return data + // from the callback. If upon return the uv_httpsys is still not NULL, + // it means there was no error and the request was not paused by the application. + uv_httpsys->synchronous = 1; /*JPW Added*/ + httpsys_read_request_body_callback(uv_httpsys->uv_async); + } + + Log("REQ: exited httpsys_read_request_body_loop\n"); + return (NO_ERROR == hr || ERROR_HANDLE_EOF == hr || ERROR_IO_PENDING == hr) ? S_OK : hr; } HRESULT httpsys_initiate_read_request_body(uv_httpsys_t* uv_httpsys) { - HandleScope handleScope; - HRESULT hr; - - // Initialize libuv handle representing this async operation - - CheckError(httpsys_uv_httpsys_init(uv_httpsys, httpsys_read_request_body_callback)); - - // Initiate async receive of the HTTP request body - - hr = HttpReceiveRequestEntityBody( - uv_httpsys->uv_httpsys_server->requestQueue, - uv_httpsys->requestId, - 0, - uv_httpsys->buffer, - uv_httpsys->bufferSize, - NULL, - &uv_httpsys->uv_async->async_req.overlapped); - - if (ERROR_HANDLE_EOF == hr) - { - // End of request body, decrement libuv loop ref count since no async completion will follow - // and generate JavaScript event - - httpsys_uv_httpsys_close(uv_httpsys); - if (!uv_httpsys->responseStarted) { - // Do not emit the `end` event if the app already started writing the response - - Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_END_REQUEST); - httpsys_make_callback(event); - } - } - else if (ERROR_IO_PENDING != hr && NO_ERROR != hr) - { - // Initiation failed - notify JavaScript - if (!uv_httpsys->responseStarted) { - // Do not emit the `error` event if the app already started writing the response - httpsys_notify_error(uv_httpsys, HTTPSYS_ERROR_INITIALIZING_READ_REQUEST_BODY, hr); - } - - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; - } - - // Result of NO_ERROR at this point means synchronous completion that must be handled by the caller - // since the IO completion port of libuv will not receive a completion. + Nan::HandleScope handleScope; + HRESULT hr; + + Log("REQ: entered httpsys_initiate_read_request_body, request id = '%I64u'\n", uv_httpsys->requestId); + // Initialize libuv handle representing this async operation + uv_httpsys->synchronous = 0; /*JPW Added*/ + CheckError(httpsys_uv_httpsys_init(uv_httpsys, (uv_async_cb)httpsys_read_request_body_callback)); + + // Initiate async receive of the HTTP request body + + hr = HttpReceiveRequestEntityBody( + uv_httpsys->uv_httpsys_server->requestQueue, + uv_httpsys->requestId, + 0, + uv_httpsys->buffer, + uv_httpsys->bufferSize, + NULL, + &uv_httpsys->uv_async->async_req.u.io.overlapped); + + Log("REQ: HttpReceiveRequestEntityBody request id '%I64u', result '%d'\n", uv_httpsys->requestId, hr); + + if (ERROR_HANDLE_EOF == hr) + { + // End of request body, decrement libuv loop ref count since no async completion will follow + // and generate JavaScript event + Log("REQ: end of body received\n"); + + httpsys_uv_httpsys_close(uv_httpsys); + if (!uv_httpsys->responseStarted) { + // Do not emit the `end` event if the app already started writing the response + Log("REQ: sending END to javascript request id = '%I64u', uv_httpsys = '%p'\n", uv_httpsys->requestId, (void*)uv_httpsys); + Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_END_REQUEST); + httpsys_make_callback(event); + Log("REQ: back from javascript, uv_httpsys = '%p'\n", (void*)uv_httpsys); + } + else { + Log("REQ: response already started, not sending END to javascript\n"); + } + } + else if (ERROR_IO_PENDING != hr && NO_ERROR != hr) + { + Log("REQ: reading request body has failed...\n"); + // Initiation failed - notify JavaScript + if (!uv_httpsys->responseStarted) { + // Do not emit the `error` event if the app already started writing the response + httpsys_notify_error(uv_httpsys, HTTPSYS_ERROR_INITIALIZING_READ_REQUEST_BODY, hr); + } + + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + } + + // Result of NO_ERROR at this point means synchronous completion that must be handled by the caller + // since the IO completion port of libuv will not receive a completion. Error: - - return hr; + Log("REQ: exiting httpsys_initiate_read_request_body, hr = '%d'\n", hr); + return hr; } -Handle httpsys_init(const Arguments& args) +void httpsys_init(const Nan::FunctionCallbackInfo& info) { - HandleScope handleScope; - - Handle options = args[0]->ToObject(); - - callback.Dispose(); - callback.Clear(); - callback = Persistent::New( - Handle::Cast(options->Get(String::New("callback")))); - initialBufferSize = options->Get(String::New("initialBufferSize"))->Int32Value(); - requestQueueLength = options->Get(String::New("requestQueueLength"))->Int32Value(); - pendingReadCount = options->Get(String::New("pendingReadCount"))->Int32Value(); - int cacheDuration = options->Get(String::New("cacheDuration"))->Int32Value(); - if (0 > cacheDuration) - { - cachePolicy.Policy = HttpCachePolicyNocache; - cachePolicy.SecondsToLive = 0; - } - else - { - cachePolicy.Policy = HttpCachePolicyTimeToLive; - defaultCacheDuration = cacheDuration; - } - - return handleScope.Close(Undefined()); + v8::Handle options = info[0]->ToObject(); + + callback = new Nan::Callback(options->Get(Nan::New("callback").ToLocalChecked()).As()); + initialBufferSize = options->Get(Nan::New("initialBufferSize").ToLocalChecked())->Int32Value(); + requestQueueLength = options->Get(Nan::New("requestQueueLength").ToLocalChecked())->Int32Value(); + pendingReadCount = options->Get(Nan::New("pendingReadCount").ToLocalChecked())->Int32Value(); + int cacheDuration = options->Get(Nan::New("cacheDuration").ToLocalChecked())->Int32Value(); + + if (0 > cacheDuration) + { + cachePolicy.Policy = HttpCachePolicyNocache; + cachePolicy.SecondsToLive = 0; + } + else + { + cachePolicy.Policy = HttpCachePolicyTimeToLive; + defaultCacheDuration = cacheDuration; + } + + debugOut = getenv("HTTP_SYS_DEBUG") ? TRUE : FALSE; + info.GetReturnValue().SetUndefined(); } -void httpsys_prepare_new_requests(uv_prepare_t* handle, int status) -{ - HandleScope scope; - uv_httpsys_server_t* uv_httpsys_server = CONTAINING_RECORD(handle, uv_httpsys_server_t, uv_prepare); - HRESULT hr; - uv_httpsys_t* uv_httpsys = NULL; - - if (uv_httpsys_server->closing && 0 == uv_httpsys_server->refCount) - { - // The HTTP.SYS server is closing as a result of a call to Server.close(). - // The HTTP.SYS request queue has already been closed in httpsys_stop_listen. - // Given that the refCount of pending async operatoins has reached zero, we can - // now perform final cleanup of the server, including notifying JavaScript that - // closing has completed. - // Stop this callback from executing again. - - uv_prepare_stop(&uv_httpsys_server->uv_prepare); - - // Emit the close event to JavaScript. - - httpsys_make_callback( - httpsys_create_event(uv_httpsys_server, HTTPSYS_SERVER_CLOSED)); - - // Clean up data structures - - uv_httpsys_server->event.Dispose(); - uv_httpsys_server->event.Clear(); - delete uv_httpsys_server; - uv_httpsys_server = NULL; - - // Terminate HTTP Server. The corresponding HttpInitiate call was made in - // httpsys_listen. - - CheckError(HttpTerminate( - HTTP_INITIALIZE_SERVER, - NULL)); - - return; - } - - while (uv_httpsys_server->readsToInitialize) - { - // TODO: address a situation when some new requests fail while others not - cancel them? - ErrorIf(NULL == (uv_httpsys = new uv_httpsys_t), ERROR_NOT_ENOUGH_MEMORY); - RtlZeroMemory(uv_httpsys, sizeof(uv_httpsys_t)); - uv_httpsys->uv_httpsys_server = uv_httpsys_server; - CheckError(httpsys_initiate_new_request(uv_httpsys)); - uv_httpsys = NULL; - uv_httpsys_server->readsToInitialize--; - } - - return; +void httpsys_prepare_new_requests(uv_prepare_t* handle) +{ + Nan::HandleScope handleScope; + uv_httpsys_server_t* uv_httpsys_server = CONTAINING_RECORD(handle, uv_httpsys_server_t, uv_prepare); + HRESULT hr; + uv_httpsys_t* uv_httpsys = NULL; + + if (uv_httpsys_server->closing && 0 == uv_httpsys_server->refCount) + { + Log("REQ: httpsys_prepare_new_requests server closing with refcount === 0\n"); + // The HTTP.SYS server is closing as a result of a call to Server.close(). + // The HTTP.SYS request queue has already been closed in httpsys_stop_listen. + // Given that the refCount of pending async operatoins has reached zero, we can + // now perform final cleanup of the server, including notifying JavaScript that + // closing has completed. + + // Stop this callback from executing again. + Log("uv_prepare_stop\n"); + uv_prepare_stop(&uv_httpsys_server->uv_prepare); + + Log("informing javascript of server closing\n"); + httpsys_make_callback(httpsys_create_event(uv_httpsys_server, HTTPSYS_SERVER_CLOSED)); + + Log("releasing server resources\n"); + // Clean up data structures + uv_httpsys_server->event.Reset(); + + //delete uv_httpsys_server; + uv_httpsys_server = NULL; + + Log("terminating http server\n"); + // Terminate HTTP Server. The corresponding HttpInitiate call was made in + // httpsys_listen. + CheckError(HttpTerminate( + HTTP_INITIALIZE_SERVER, + NULL)); + + return; + } + + while (uv_httpsys_server->readsToInitialize) + { + Log("\nREQ: preparing new request: current reads to initialise '%d'\n", uv_httpsys_server->readsToInitialize); + // TODO: address a situation when some new requests fail while others not - cancel them? + ErrorIf(NULL == (uv_httpsys = new uv_httpsys_t), ERROR_NOT_ENOUGH_MEMORY); + RtlZeroMemory(uv_httpsys, sizeof(uv_httpsys_t)); + uv_httpsys->uv_httpsys_server = uv_httpsys_server; + CheckError(httpsys_initiate_new_request(uv_httpsys)); + uv_httpsys = NULL; + uv_httpsys_server->readsToInitialize--; + Log("REQ: finished current request initialisation, decrementing readsToInitialize count, new value '%d'\n", uv_httpsys_server->readsToInitialize); + } + + return; Error: - if (NULL != uv_httpsys) - { - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; - } - - httpsys_notify_error(uv_httpsys_server, HTTPSYS_ERROR_INITIALIZING_REQUEST, hr); - - return; + Log("REQ: httpsys_prepare_new_requests has errored, freeing uv_httpsys\n"); + if (NULL != uv_httpsys) + { + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + } + httpsys_notify_error(uv_httpsys_server, HTTPSYS_ERROR_INITIALIZING_REQUEST, hr); + return; } -Handle httpsys_listen(const Arguments& args) +void httpsys_listen(const Nan::FunctionCallbackInfo& info) { - HandleScope handleScope; - HRESULT hr; - HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_2; - WCHAR url[MAX_PATH + 1]; - WCHAR requestQueueName[MAX_PATH + 1]; - HTTP_BINDING_INFO bindingInfo; - uv_loop_t* loop; - uv_httpsys_t* uv_httpsys = NULL; - uv_httpsys_server_t* uv_httpsys_server = NULL; - - // Process arguments - - Handle options = args[0]->ToObject(); - - // Lazy initialization of HTTP.SYS - - CheckError(HttpInitialize( - HttpApiVersion, - HTTP_INITIALIZE_SERVER, - NULL)); - - // Create uv_httpsys_server_t - - ErrorIf(NULL == (uv_httpsys_server = new uv_httpsys_server_t), ERROR_NOT_ENOUGH_MEMORY); - RtlZeroMemory(uv_httpsys_server, sizeof(uv_httpsys_server_t)); - - // Create the request queue name by replacing slahes in the URL with _ - // to make it a valid file name. - - options->Get(String::New("url"))->ToString()->Write((uint16_t*)requestQueueName, 0, MAX_PATH); - - for (WCHAR* current = requestQueueName; *current; current++) - { - if (L'/' == *current) - { - *current = L'_'; - } - } - - // Create HTTP.SYS request queue (one per URL). Request queues are named - // to allow sharing between processes and support cluster. - // First try to obtain a handle to a pre-existing queue with the name - // based on the listen URL. If that fails, create a new named request queue. - - hr = HttpCreateRequestQueue( - HttpApiVersion, - requestQueueName, - NULL, - HTTP_CREATE_REQUEST_QUEUE_FLAG_OPEN_EXISTING, - &uv_httpsys_server->requestQueue); - - if (ERROR_FILE_NOT_FOUND == hr) - { - // Request queue by that name does not exist yet, try to create it - - CheckError(HttpCreateRequestQueue( - HttpApiVersion, - requestQueueName, - NULL, - 0, - &uv_httpsys_server->requestQueue)); - - // Create HTTP.SYS session and associate it with URL group containing the - // single listen URL. - - CheckError(HttpCreateServerSession( - HttpApiVersion, - &uv_httpsys_server->sessionId, - NULL)); - - CheckError(HttpCreateUrlGroup( - uv_httpsys_server->sessionId, - &uv_httpsys_server->groupId, - NULL)); - - options->Get(String::New("url"))->ToString()->Write((uint16_t*)url, 0, MAX_PATH); - - CheckError(HttpAddUrlToUrlGroup( - uv_httpsys_server->groupId, - url, - 0, - NULL)); - - // Set the request queue length - - CheckError(HttpSetRequestQueueProperty( - uv_httpsys_server->requestQueue, - HttpServerQueueLengthProperty, - &requestQueueLength, - sizeof(requestQueueLength), - 0, - NULL)); - - // Bind the request queue with the URL group to enable receiving - // HTTP traffic on the request queue. - - RtlZeroMemory(&bindingInfo, sizeof(HTTP_BINDING_INFO)); - bindingInfo.RequestQueueHandle = uv_httpsys_server->requestQueue; - bindingInfo.Flags.Present = 1; - - CheckError(HttpSetUrlGroupProperty( - uv_httpsys_server->groupId, - HttpServerBindingProperty, - &bindingInfo, - sizeof(HTTP_BINDING_INFO))); - } - else - { - CheckError(hr); - } - - // Configure the request queue to prevent queuing a completion to the libuv - // IO completion port when an async operation completes synchronously. - - ErrorIf(!SetFileCompletionNotificationModes( - uv_httpsys_server->requestQueue, - FILE_SKIP_COMPLETION_PORT_ON_SUCCESS | FILE_SKIP_SET_EVENT_ON_HANDLE), - GetLastError()); - - // Associate the HTTP.SYS request queue handle with the IO completion port - // of the default libuv event loop used by node. This will cause - // async completions related to the HTTP.SYS request queue to execute - // on the node.js thread. The event loop will process these events as - // UV_ASYNC handle types, beacuse a call to uv_async_init will be made - // every time an async operation is started with HTTP.SYS. On Windows, - // uv_async_init associates the OVERLAPPED structure representing the - // async operation with the uv_async_t handle that embeds it, which allows - // the event loop to map the OVERLAPPED instance back to the async - // callback to invoke when the IO completion port is signaled. - - loop = uv_default_loop(); - ErrorIf(NULL == CreateIoCompletionPort( - uv_httpsys_server->requestQueue, - loop->iocp, - (ULONG_PTR)uv_httpsys_server->requestQueue, - 0), - GetLastError()); - - // Initiate uv_prepare associated with this server that will be responsible for - // initializing new pending receives of new HTTP reqests against HTTP.SYS - // to replace completed ones. This logic will run once per iteration of the libuv event loop. - // The first execution of the callback will initiate the first batch of reads. - - uv_prepare_init(loop, &uv_httpsys_server->uv_prepare); - uv_prepare_start(&uv_httpsys_server->uv_prepare, httpsys_prepare_new_requests); - uv_httpsys_server->readsToInitialize = pendingReadCount; - - // The result wraps the native pointer to the uv_httpsys_server_t structure. - // It also doubles as an event parameter to JavaScript callbacks scoped to the entire server. - - uv_httpsys_server->event = Persistent::New(httpsysObject->NewInstance()); - uv_httpsys_server->event->SetPointerInInternalField(0, (void*)uv_httpsys_server); - uv_httpsys_server->event->Set(v8uv_httpsys_server, uv_httpsys_server->event); - - return uv_httpsys_server->event; + Isolate* isolate = Isolate::GetCurrent(); + EscapableHandleScope handleScope(isolate); + HRESULT hr; + HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_2; + WCHAR url[MAX_PATH + 1]; + WCHAR requestQueueName[MAX_PATH + 1]; + HTTP_BINDING_INFO bindingInfo; + uv_loop_t* loop; + uv_httpsys_t* uv_httpsys = NULL; + uv_httpsys_server_t* uv_httpsys_server = NULL; + BOOL success = 0; + HANDLE handle = NULL; + Local tpl = Nan::New(httpsysObject); + Local o = tpl->NewInstance(); + + Log("start listen called\n"); + // Process arguments + + Local urls = Local::Cast(info[0]); + + // Lazy initialization of HTTP.SYS + + CheckError(HttpInitialize( + HttpApiVersion, + HTTP_INITIALIZE_SERVER, + NULL)); + + // Create uv_httpsys_server_t + + ErrorIf(NULL == (uv_httpsys_server = new uv_httpsys_server_t), ERROR_NOT_ENOUGH_MEMORY); + RtlZeroMemory(uv_httpsys_server, sizeof(uv_httpsys_server_t)); + + // use the first url path as the request queue name by replacing slahes in the URL with _ + // to make it a valid file name. + urls->Get(0)->ToString()->Write((uint16_t*)requestQueueName, 0, MAX_PATH); + + for (WCHAR* current = requestQueueName; *current; current++) + { + if (L'/' == *current) + { + *current = L'_'; + } + } + + // Create HTTP.SYS request queue (one per URL). Request queues are named + // to allow sharing between processes and support cluster. + // First try to obtain a handle to a pre-existing queue with the name + // based on the listen URL. If that fails, create a new named request queue. + Log("checking if request queue exists '%ws'\n", requestQueueName); + + hr = HttpCreateRequestQueue( + HttpApiVersion, + requestQueueName, + NULL, + HTTP_CREATE_REQUEST_QUEUE_FLAG_OPEN_EXISTING, + &uv_httpsys_server->requestQueue); + + Log("result from HttpCreateRequestQueue = %d\n", hr); + + if (ERROR_FILE_NOT_FOUND == hr) + { + // Request queue by that name does not exist yet, try to create it + + Log("attempting to create request queue\n"); + + CheckError(HttpCreateRequestQueue( + HttpApiVersion, + requestQueueName, + NULL, + 0, + &uv_httpsys_server->requestQueue)); + + Log("created request queue\n"); + + // Create HTTP.SYS session and associate it with URL group containing the + // single listen URL. + + CheckError(HttpCreateServerSession( + HttpApiVersion, + &uv_httpsys_server->sessionId, + NULL)); + + Log("done HttpCreateServerSession\n"); + + CheckError(HttpCreateUrlGroup( + uv_httpsys_server->sessionId, + &uv_httpsys_server->groupId, + NULL)); + + Log("done HttpCreateUrlGroup\n"); + + //repeat for all specified url's + for (unsigned int i = 0; i < urls->Length(); i++) { + urls->Get(i)->ToString()->Write((uint16_t*)url, 0, MAX_PATH); + + Log("adding url to group '%ws'\n", url); + + HRESULT hres = HttpAddUrlToUrlGroup( + uv_httpsys_server->groupId, + url, + 0, + NULL); + + Log("result = '%d'\n", hres); + + CheckError(hres); + } + // Set the request queue length + CheckError(HttpSetRequestQueueProperty( + uv_httpsys_server->requestQueue, + HttpServerQueueLengthProperty, + &requestQueueLength, + sizeof(requestQueueLength), + 0, + NULL)); + + Log("done HttpSetRequestQueueProperty\n"); + + // Bind the request queue with the URL group to enable receiving + // HTTP traffic on the request queue. + RtlZeroMemory(&bindingInfo, sizeof(HTTP_BINDING_INFO)); + bindingInfo.RequestQueueHandle = uv_httpsys_server->requestQueue; + bindingInfo.Flags.Present = 1; + + CheckError(HttpSetUrlGroupProperty( + uv_httpsys_server->groupId, + HttpServerBindingProperty, + &bindingInfo, + sizeof(HTTP_BINDING_INFO))); + + Log("done HttpSetUrlGroupProperty\n"); + } + else + { + CheckError(hr); + } + // Associate the HTTP.SYS request queue handle with the IO completion port + // of the default libuv event loop used by node. This will cause + // async completions related to the HTTP.SYS request queue to execute + // on the node.js thread. The event loop will process these events as + // UV_ASYNC handle types, beacuse a call to uv_async_init will be made + // every time an async operation is started with HTTP.SYS. On Windows, + // uv_async_init associates the OVERLAPPED structure representing the + // async operation with the uv_async_t handle that embeds it, which allows + // the event loop to map the OVERLAPPED instance back to the async + // callback to invoke when the IO completion port is signaled. + + loop = uv_default_loop(); + handle = CreateIoCompletionPort( + uv_httpsys_server->requestQueue, + loop->iocp, + (ULONG_PTR)uv_httpsys_server->requestQueue, + 0); + + ErrorIf(handle == NULL, GetLastError()); + + Log("done CreateIoCompletionPort\n"); + + // Configure the request queue to prevent queuing a completion to the libuv + // IO completion port when an async operation completes synchronously. + + success = SetFileCompletionNotificationModes( + uv_httpsys_server->requestQueue, + FILE_SKIP_COMPLETION_PORT_ON_SUCCESS | FILE_SKIP_SET_EVENT_ON_HANDLE); + + ErrorIf(!success, GetLastError()); + + Log("done SetFileCompletionNotificationModes\n"); + + // Initiate uv_prepare associated with this server that will be responsible for + // initializing new pending receives of new HTTP reqests against HTTP.SYS + // to replace completed ones. This logic will run once per iteration of the libuv event loop. + // The first execution of the callback will initiate the first batch of reads. + + uv_prepare_init(loop, &uv_httpsys_server->uv_prepare); + uv_prepare_start(&uv_httpsys_server->uv_prepare, (uv_prepare_cb)httpsys_prepare_new_requests); + + Log("REQ: initialising readsToInitialize to '%d'\n", pendingReadCount); + uv_httpsys_server->readsToInitialize = pendingReadCount; + + // The result wraps the native pointer to the uv_httpsys_server_t structure. + // It also doubles as an event parameter to JavaScript callbacks scoped to the entire server. + uv_httpsys_server->event.Reset(o); + Nan::SetInternalFieldPointer(o, 0, (void*)uv_httpsys_server); + o->Set(Nan::New(v8uv_httpsys_server), Nan::New(uv_httpsys_server->event)); + info.GetReturnValue().Set(Nan::New(uv_httpsys_server->event)); + return; Error: - if (NULL != uv_httpsys_server) - { - if (HTTP_NULL_ID != uv_httpsys_server->groupId) - { - HttpCloseUrlGroup(uv_httpsys_server->groupId); - } - - if (NULL != uv_httpsys_server->requestQueue) - { - HttpCloseRequestQueue(uv_httpsys_server->requestQueue); - } - - if (HTTP_NULL_ID != uv_httpsys_server->sessionId) - { - HttpCloseServerSession(uv_httpsys_server->sessionId); - } - - delete uv_httpsys_server; - uv_httpsys_server = NULL; - } - - if (NULL != uv_httpsys) - { - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; - } - - return handleScope.Close(ThrowException(Int32::New(hr))); + if (NULL != uv_httpsys_server) { + if (HTTP_NULL_ID != uv_httpsys_server->groupId) { + HttpCloseUrlGroup(uv_httpsys_server->groupId); + } + if (NULL != uv_httpsys_server->requestQueue) { + HttpCloseRequestQueue(uv_httpsys_server->requestQueue); + } + if (HTTP_NULL_ID != uv_httpsys_server->sessionId) { + HttpCloseServerSession(uv_httpsys_server->sessionId); + } + delete uv_httpsys_server; + uv_httpsys_server = NULL; + } + + if (NULL != uv_httpsys) { + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + } + info.GetReturnValue().Set(isolate->ThrowException(Nan::New(hr))); } -Handle httpsys_stop_listen(const Arguments& args) -{ - HandleScope handleScope; - HRESULT hr; - - uv_httpsys_server_t* uv_httpsys_server = (uv_httpsys_server_t*)Handle::Cast(args[0])->GetPointerFromInternalField(0); - - // Mark the HTTP.SYS listener as closing. Next time the httpsys_prepare_new_requests - // callback is entered, and the pending async operations associated with the server have - // drained (as indicated by the uv_httpsys_server->refCount), it will have a chance to - // perform final cleanup. - - uv_httpsys_server->closing = TRUE; - - // Close the HTTP.SYS URL group - - if (HTTP_NULL_ID != uv_httpsys_server->groupId) - { - CheckError(HttpCloseUrlGroup(uv_httpsys_server->groupId)); - } - - // Perform graceful shutdown of the HTTP.SYS request queue, then close the queue. - // This will cause all pending async operations to be cancelled, which the system - // will be notified about via the IO completion port of the main libuv event loop. - // Appropriate async callbacks will be executed to react to these cancellations. - - if (NULL != uv_httpsys_server->requestQueue) - { - CheckError(HttpShutdownRequestQueue(uv_httpsys_server->requestQueue)); - CheckError(HttpCloseRequestQueue(uv_httpsys_server->requestQueue)); - } - - // Close the HTTP.SYS server session - - if (HTTP_NULL_ID != uv_httpsys_server->sessionId) - { - CheckError(HttpCloseServerSession(uv_httpsys_server->sessionId)); - } - return handleScope.Close(Undefined()); +void httpsys_stop_listen(const Nan::FunctionCallbackInfo& info) +{ + HRESULT hr; + Handle o = Handle::Cast(info[0]); + uv_httpsys_server_t* uv_httpsys_server = (uv_httpsys_server_t*)Nan::GetInternalFieldPointer(o, 0); + Log("stop listen called\n"); + + // Mark the HTTP.SYS listener as closing. Next time the httpsys_prepare_new_requests + // callback is entered, and the pending async operations associated with the server have + // drained (as indicated by the uv_httpsys_server->refCount), it will have a chance to + // perform final cleanup. + uv_httpsys_server->closing = TRUE; + + // Close the HTTP.SYS URL group + if (HTTP_NULL_ID != uv_httpsys_server->groupId) { + Log("closing url group\n"); + CheckError(HttpCloseUrlGroup(uv_httpsys_server->groupId)); + } + // Perform graceful shutdown of the HTTP.SYS request queue, then close the queue. + // This will cause all pending async operations to be cancelled, which the system + // will be notified about via the IO completion port of the main libuv event loop. + // Appropriate async callbacks will be executed to react to these cancellations. + + if (NULL != uv_httpsys_server->requestQueue) + { + Log("shutting down request queue\n"); + CheckError(HttpShutdownRequestQueue(uv_httpsys_server->requestQueue)); + Log("closing request queue\n"); + CheckError(HttpCloseRequestQueue(uv_httpsys_server->requestQueue)); + } + + // Close the HTTP.SYS server session + if (HTTP_NULL_ID != uv_httpsys_server->sessionId) { + Log("closing server session %llu\n", uv_httpsys_server->sessionId); + CheckError(HttpCloseServerSession(uv_httpsys_server->sessionId)); + } + Log("returning from stop listen\n"); + info.GetReturnValue().SetUndefined(); + return; Error: - - return handleScope.Close(ThrowException(Int32::New(hr))); + Log("RESP: error during httpsys_stop_listen hr = '%ld'\n", hr); + Isolate* isolate = Isolate::GetCurrent(); + info.GetReturnValue().Set(isolate->ThrowException(Nan::New(hr))); } -Handle httpsys_resume(const Arguments& args) +void httpsys_resume(const Nan::FunctionCallbackInfo& info) { - HTTPSYS_EXPORT_PREAMBLE; - - CheckError(httpsys_read_request_body_loop(uv_httpsys)); - - return handleScope.Close(Undefined()); + HTTPSYS_EXPORT_PREAMBLE; + Log("REQ: httpsys_resume called from javascript request id = '%I64u'\n", uv_httpsys->requestId); + CheckError(httpsys_read_request_body_loop(uv_httpsys)); + info.GetReturnValue().SetUndefined(); + return; Error: - - // uv_httpsys had been freed alredy - - return handleScope.Close(ThrowException(Int32::New(hr))); + Log("RESP: error during httpsys_resume hr = '%d'\n", hr); + // uv_httpsys had been freed already + Isolate* isolate = Isolate::GetCurrent(); + EscapableHandleScope handleScope(isolate); + info.GetReturnValue().Set(isolate->ThrowException(Nan::New(hr))); } -Handle httpsys_write_headers(const Arguments& args) +void httpsys_write_headers(const Nan::FunctionCallbackInfo& info) { - HTTPSYS_EXPORT_PREAMBLE; - Handle options = args[0]->ToObject(); - String::Utf8Value reason(options->Get(v8reason)); - Handle unknownHeaders; - Handle headerNames; - Handle headerName; - Handle knownHeaders; - Handle knownHeader; - Handle cacheDuration; - ULONG flags = 0; - unsigned int statusCode; - uv_httpsys_t* uv_httpsys_req = NULL; - - // Enable NAGLE if requested - - if (!options->Get(v8noDelay)->BooleanValue()) { - flags |= HTTP_SEND_RESPONSE_FLAG_ENABLE_NAGLING; - } - - // Get response status code - - statusCode = options->Get(v8statusCode)->Uint32Value(); - - // If this is an accepted upgrade response, create another uv_httpsys intance - // to allow processing request and response concurrently. Use the new uv_httpsys instance - // for writing of the response, inluding sending back the HTTP response headers. The old - // uv_httpsys instance will continue to be used for reading of the request. - - if (101 == statusCode) { - // Instruct HTTP.SYS to treat subsequent reads and writes of the HTTP request and response as - // opaque. This allows higher level protocols like WebSockets to implement custom framing. - - flags |= HTTP_SEND_RESPONSE_FLAG_OPAQUE; - - // Create an initialize uv_httpsys for writing of the response - - ErrorIf(NULL == (uv_httpsys->uv_httpsys_peer = new uv_httpsys_t), ERROR_NOT_ENOUGH_MEMORY); - RtlZeroMemory(uv_httpsys->uv_httpsys_peer, sizeof(uv_httpsys_t)); - uv_httpsys->uv_httpsys_peer->uv_httpsys_server = uv_httpsys->uv_httpsys_server; - uv_httpsys->uv_httpsys_peer->requestId = uv_httpsys->requestId; - uv_httpsys->uv_httpsys_peer->event = Persistent::New(uv_httpsys->event); - uv_httpsys->uv_httpsys_peer->uv_httpsys_peer = uv_httpsys; - - // Switch to using the newly created uv_httpsys for the rest of this function - - uv_httpsys_req = uv_httpsys; - uv_httpsys = uv_httpsys->uv_httpsys_peer; - } - else { - // For regular HTTP requests, once the response had been initiated, further - // `data` and `end` events will not be emitted even if the request had - // not been read entirely. This flag is used to stop issuing read requests - // against HTTP.SYS for this request. - - uv_httpsys->responseStarted = TRUE; - } - - uv_httpsys->response.StatusCode = statusCode; - - // Initialize libuv handle representing this async operation - - CheckError(httpsys_uv_httpsys_init(uv_httpsys, httpsys_write_callback)); - - // If the request is to be disconnected, it indicates a rejected HTTP upgrade request. - // In that case the request is closed and native resources deallocated. - - if (options->Get(v8disconnect)->BooleanValue()) { - uv_httpsys->disconnect = TRUE; - } - - if (uv_httpsys->disconnect) { - uv_httpsys->disconnectProcessed = TRUE; - flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT; - hr = HttpSendHttpResponse( - uv_httpsys->uv_httpsys_server->requestQueue, - uv_httpsys->requestId, - flags, - &uv_httpsys->response, - NULL, - NULL, - NULL, - 0, - &uv_httpsys->uv_async->async_req.overlapped, - NULL); - - if (NO_ERROR == hr) - { - // Synchronous completion. - - httpsys_write_callback(uv_httpsys->uv_async, 1); - } - else - { - ErrorIf(ERROR_IO_PENDING != hr, hr); - } - - return handleScope.Close(Boolean::New(ERROR_IO_PENDING == hr)); - } - - // Set reason - - ErrorIf(NULL == (uv_httpsys->response.pReason = (PCSTR)malloc(reason.length())), - ERROR_NOT_ENOUGH_MEMORY); - uv_httpsys->response.ReasonLength = reason.length(); - memcpy((void*)uv_httpsys->response.pReason, *reason, reason.length()); - - // Set known headers - - knownHeaders = Handle::Cast(options->Get(v8knownHeaders)); - for (unsigned int i = 0; i < knownHeaders->Length(); i++) - { - knownHeader = Handle::Cast(knownHeaders->Get(i)); - int headerIndex = knownHeader->Get(v8id)->Int32Value(); - String::Utf8Value header(knownHeader->Get(v8value)); - ErrorIf(NULL == (uv_httpsys->response.Headers.KnownHeaders[headerIndex].pRawValue = - (PCSTR)malloc(header.length())), - ERROR_NOT_ENOUGH_MEMORY); - uv_httpsys->response.Headers.KnownHeaders[headerIndex].RawValueLength = header.length(); - memcpy((void*)uv_httpsys->response.Headers.KnownHeaders[headerIndex].pRawValue, - *header, header.length()); - } - - // Set unknown headers - - unknownHeaders = Handle::Cast(options->Get(v8unknownHeaders)); - headerNames = unknownHeaders->GetOwnPropertyNames(); - if (headerNames->Length() > 0) - { - ErrorIf(NULL == (uv_httpsys->response.Headers.pUnknownHeaders = - (PHTTP_UNKNOWN_HEADER)malloc(headerNames->Length() * sizeof (HTTP_UNKNOWN_HEADER))), - ERROR_NOT_ENOUGH_MEMORY); - RtlZeroMemory(uv_httpsys->response.Headers.pUnknownHeaders, - headerNames->Length() * sizeof (HTTP_UNKNOWN_HEADER)); - uv_httpsys->response.Headers.UnknownHeaderCount = headerNames->Length(); - for (int i = 0; i < uv_httpsys->response.Headers.UnknownHeaderCount; i++) - { - headerName = headerNames->Get(i)->ToString(); - String::Utf8Value headerNameUtf8(headerName); - String::Utf8Value headerValueUtf8(unknownHeaders->Get(headerName)); - uv_httpsys->response.Headers.pUnknownHeaders[i].NameLength = headerNameUtf8.length(); - ErrorIf(NULL == (uv_httpsys->response.Headers.pUnknownHeaders[i].pName = - (PCSTR)malloc(headerNameUtf8.length())), - ERROR_NOT_ENOUGH_MEMORY); - memcpy((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pName, - *headerNameUtf8, headerNameUtf8.length()); - uv_httpsys->response.Headers.pUnknownHeaders[i].RawValueLength = headerValueUtf8.length(); - ErrorIf(NULL == (uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue = - (PCSTR)malloc(headerValueUtf8.length())), - ERROR_NOT_ENOUGH_MEMORY); - memcpy((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue, - *headerValueUtf8, headerValueUtf8.length()); - } - } - - // Prepare response body and determine flags - - CheckError(httpsys_initialize_body_chunks(options, uv_httpsys, &flags)); - if (uv_httpsys->chunk.FromMemory.pBuffer) - { - uv_httpsys->response.EntityChunkCount = 1; - uv_httpsys->response.pEntityChunks = &uv_httpsys->chunk; - } - - // Determine cache policy - - if (HttpCachePolicyTimeToLive == cachePolicy.Policy) - { - // If HTTP.SYS output caching is enabled, establish the duration to cache for - // based on the setting on the message or the global default, in that order of precedence - - cacheDuration = options->Get(v8cacheDuration); - cachePolicy.SecondsToLive = cacheDuration->IsUint32() ? cacheDuration->Uint32Value() : defaultCacheDuration; - } - - // TOOD: support response trailers - - // Initiate async send of the HTTP response headers and optional body - - hr = HttpSendHttpResponse( - uv_httpsys->uv_httpsys_server->requestQueue, - uv_httpsys->requestId, - flags, - &uv_httpsys->response, - &cachePolicy, - NULL, - NULL, - 0, - &uv_httpsys->uv_async->async_req.overlapped, - NULL); - - if (NO_ERROR == hr) - { - // Synchronous completion. - - httpsys_write_callback(uv_httpsys->uv_async, 1); - } - else - { - ErrorIf(ERROR_IO_PENDING != hr, hr); - } - - // Return true if async completion is pending and an event will be generated once completed - return handleScope.Close(Boolean::New(ERROR_IO_PENDING == hr)); + HRESULT hr; + Handle o = Handle::Cast(info[0]); + uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)Nan::GetInternalFieldPointer(o, 0); + + Handle options = info[0]->ToObject(); + String::Utf8Value reason(options->Get(Nan::New(v8reason))); + Handle unknownHeaders; + Handle headerNames; + v8::Handle headerName; + Handle knownHeaders; + Handle knownHeader; + Handle cacheDuration; + ULONG flags = 0; + unsigned int statusCode; + uv_httpsys_t* uv_httpsys_req = NULL; + + // Enable NAGLE if requested + if (!options->Get(Nan::New(v8noDelay))->BooleanValue()) { + flags |= HTTP_SEND_RESPONSE_FLAG_ENABLE_NAGLING; + } + + // Get response status code + statusCode = options->Get(Nan::New(v8statusCode))->Uint32Value(); + Log("\nRESP: httpsys_write_headers called from javascript, status '%d', request id '%I64u'\n", statusCode, uv_httpsys->requestId); + + // If this is an accepted upgrade response, create another uv_httpsys intance + // to allow processing request and response concurrently. Use the new uv_httpsys instance + // for writing of the response, inluding sending back the HTTP response headers. The old + // uv_httpsys instance will continue to be used for reading of the request. + + if (101 == statusCode) { + // Instruct HTTP.SYS to treat subsequent reads and writes of the HTTP request and response as + // opaque. This allows higher level protocols like WebSockets to implement custom framing. + Log("RESP: socket upgraded requested\n"); + flags |= HTTP_SEND_RESPONSE_FLAG_OPAQUE; + + // Create an initialize uv_httpsys for writing of the response + ErrorIf(NULL == (uv_httpsys->uv_httpsys_peer = new uv_httpsys_t), ERROR_NOT_ENOUGH_MEMORY); + RtlZeroMemory(uv_httpsys->uv_httpsys_peer, sizeof(uv_httpsys_t)); + uv_httpsys->uv_httpsys_peer->uv_httpsys_server = uv_httpsys->uv_httpsys_server; + uv_httpsys->uv_httpsys_peer->requestId = uv_httpsys->requestId; + + uv_httpsys->uv_httpsys_peer->event.Reset(uv_httpsys->event); + uv_httpsys->uv_httpsys_peer->uv_httpsys_peer = uv_httpsys; + // Switch to using the newly created uv_httpsys for the rest of this function + uv_httpsys_req = uv_httpsys; + uv_httpsys = uv_httpsys->uv_httpsys_peer; + + Log("RESP: ***upgrade response: swap uv_httpsys_peer = '%p', with uv_httpsys = '%p'\n", (void*)uv_httpsys, (void*)uv_httpsys_req); + } + else { + // For regular HTTP requests, once the response had been initiated, further + // `data` and `end` events will not be emitted even if the request had + // not been read entirely. This flag is used to stop issuing read requests + // against HTTP.SYS for this request. + uv_httpsys->responseStarted = TRUE; + } + uv_httpsys->response.StatusCode = statusCode; + + // Initialize libuv handle representing this async operation + uv_httpsys->synchronousWrite = FALSE; /*JPW ADDED*/ + CheckError(httpsys_uv_httpsys_init(uv_httpsys, (uv_async_cb)httpsys_write_callback)); + + // If the request is to be disconnected, it indicates a rejected HTTP upgrade request. + // In that case the request is closed and native resources deallocated. + if (options->Get(Nan::New(v8disconnect))->BooleanValue()) { + Log("RESP: disconnect set from javascript, it indicates a rejected HTTP upgrade request\n"); + uv_httpsys->disconnect = TRUE; + } + + if (uv_httpsys->disconnect) { + Log("RESP: disconnect flag set, responding with DISCONNECT response\n"); + + uv_httpsys->disconnectProcessed = TRUE; + flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT; + hr = HttpSendHttpResponse( + uv_httpsys->uv_httpsys_server->requestQueue, + uv_httpsys->requestId, + flags, + &uv_httpsys->response, + NULL, + NULL, + NULL, + 0, + &uv_httpsys->uv_async->async_req.u.io.overlapped, + NULL); + + Log("RESP: HttpSendHttpResponse result '%d'\n", hr); + + if (NO_ERROR == hr) + { + //Synchronous completion. + uv_httpsys->synchronousWrite = TRUE; /*JPW Added*/ + httpsys_write_callback(uv_httpsys->uv_async); + + //uv_httpsys may have been freed inside this routine if the write completed + Log("RESP: back from httpsys_write_callback uv_httpsys = '%p'\n", (void*)uv_httpsys); + } + else + { + ErrorIf(ERROR_IO_PENDING != hr, hr); + } + info.GetReturnValue().Set(ERROR_IO_PENDING == hr ? true : false); + return; + } + + //make sure uv_httpsys is still valid + if (uv_httpsys != NULL) { + // Set reason + ErrorIf(NULL == (uv_httpsys->response.pReason = (PCSTR)malloc(reason.length())), + ERROR_NOT_ENOUGH_MEMORY); + uv_httpsys->response.ReasonLength = reason.length(); + memcpy((void*)uv_httpsys->response.pReason, *reason, reason.length()); + + // Set known headers + + knownHeaders = Handle::Cast(options->Get(Nan::New(v8knownHeaders))); + for (unsigned int i = 0; i < knownHeaders->Length(); i++) + { + knownHeader = Handle::Cast(knownHeaders->Get(i)); + int headerIndex = knownHeader->Get(Nan::New(v8id))->Int32Value(); + String::Utf8Value header(knownHeader->Get(Nan::New(v8value))); + ErrorIf(NULL == (uv_httpsys->response.Headers.KnownHeaders[headerIndex].pRawValue = + (PCSTR)malloc(header.length())), + ERROR_NOT_ENOUGH_MEMORY); + uv_httpsys->response.Headers.KnownHeaders[headerIndex].RawValueLength = header.length(); + memcpy((void*)uv_httpsys->response.Headers.KnownHeaders[headerIndex].pRawValue, + *header, header.length()); + } + + // Set unknown headers + + unknownHeaders = Handle::Cast(options->Get(Nan::New(v8unknownHeaders))); + headerNames = unknownHeaders->GetOwnPropertyNames(); + if (headerNames->Length() > 0) + { + ErrorIf(NULL == (uv_httpsys->response.Headers.pUnknownHeaders = + (PHTTP_UNKNOWN_HEADER)malloc(headerNames->Length() * sizeof(HTTP_UNKNOWN_HEADER))), + ERROR_NOT_ENOUGH_MEMORY); + RtlZeroMemory(uv_httpsys->response.Headers.pUnknownHeaders, + headerNames->Length() * sizeof(HTTP_UNKNOWN_HEADER)); + uv_httpsys->response.Headers.UnknownHeaderCount = headerNames->Length(); + for (int i = 0; i < uv_httpsys->response.Headers.UnknownHeaderCount; i++) + { + headerName = headerNames->Get(i)->ToString(); + String::Utf8Value headerNameUtf8(headerName); + String::Utf8Value headerValueUtf8(unknownHeaders->Get(headerName)); + uv_httpsys->response.Headers.pUnknownHeaders[i].NameLength = headerNameUtf8.length(); + ErrorIf(NULL == (uv_httpsys->response.Headers.pUnknownHeaders[i].pName = + (PCSTR)malloc(headerNameUtf8.length())), + ERROR_NOT_ENOUGH_MEMORY); + memcpy((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pName, + *headerNameUtf8, headerNameUtf8.length()); + uv_httpsys->response.Headers.pUnknownHeaders[i].RawValueLength = headerValueUtf8.length(); + ErrorIf(NULL == (uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue = + (PCSTR)malloc(headerValueUtf8.length())), + ERROR_NOT_ENOUGH_MEMORY); + memcpy((void*)uv_httpsys->response.Headers.pUnknownHeaders[i].pRawValue, + *headerValueUtf8, headerValueUtf8.length()); + } + } + + // Prepare response body and determine flags + + CheckError(httpsys_initialize_body_chunks(options, uv_httpsys, &flags)); + if (uv_httpsys->chunk.FromMemory.pBuffer) + { + uv_httpsys->response.EntityChunkCount = 1; + uv_httpsys->response.pEntityChunks = &uv_httpsys->chunk; + } + + // Determine cache policy + + if (HttpCachePolicyTimeToLive == cachePolicy.Policy) + { + // If HTTP.SYS output caching is enabled, establish the duration to cache for + // based on the setting on the message or the global default, in that order of precedence + + cacheDuration = options->Get(Nan::New(v8cacheDuration)); + cachePolicy.SecondsToLive = cacheDuration->IsUint32() ? cacheDuration->Uint32Value() : defaultCacheDuration; + } + // TOOD: support response trailers + + // Initiate async send of the HTTP response headers and optional body + hr = HttpSendHttpResponse( + uv_httpsys->uv_httpsys_server->requestQueue, + uv_httpsys->requestId, + flags, + &uv_httpsys->response, + &cachePolicy, + NULL, + NULL, + 0, + &uv_httpsys->uv_async->async_req.u.io.overlapped, + NULL); + + Log("RESP: sending response data: HttpSendHttpResponse result '%d'\n", hr); + + if (NO_ERROR == hr) + { + // Synchronous completion. + //httpsys_write_callback(uv_httpsys->uv_async, 1); + uv_httpsys->synchronousWrite = TRUE; + httpsys_write_callback(uv_httpsys->uv_async); + Log("RESP: back from httpsys_write_callback uv_httpsys = '%p'\n", (void*)uv_httpsys); + } + else + { + ErrorIf(ERROR_IO_PENDING != hr, hr); + } + } + info.GetReturnValue().Set(ERROR_IO_PENDING == hr ? true : false); + return; Error: - - httpsys_free(uv_httpsys_req, TRUE); - uv_httpsys_req = NULL; - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; - - return handleScope.Close(ThrowException(Int32::New(hr))); + Log("RESP: error during httpsys_write_headers\n"); + if (uv_httpsys_req != NULL) { + Log("we have a uv_httpsys_req so we have an upgraded connection, clearing original req uv_httpsys_req = '%I64u'\n", uv_httpsys_req->requestId); + } + httpsys_free(uv_httpsys_req, TRUE); + uv_httpsys_req = NULL; + + Log("call httpsys_free for 'uv_httpsys' request '%I64u'\n", uv_httpsys->requestId); + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + + Isolate* isolate = Isolate::GetCurrent(); + info.GetReturnValue().Set(isolate->ThrowException(Nan::New((int)hr))); } -void httpsys_write_callback(uv_async_t* handle, int status) +void httpsys_write_callback(uv_async_t* handle) { - HTTPSYS_CALLBACK_PREAMBLE; - HRESULT hr = S_OK; - - // Process async completion - - if (uv_httpsys->disconnectProcessed) { - // This was a best-effort termination of a client connection after an unaccepted - // HTTP upgrade request or an error. Free up native resources regardless of the outcome - // of the async operation. - - httpsys_free(uv_httpsys, FALSE); - uv_httpsys = NULL; - } - else if (uv_httpsys->disconnect) { - // A request was made to disconnect the client when an async operation was in progress. - // Now that the async operation completed, initiate the disconnection again. - - uv_httpsys->disconnectProcessed = TRUE; - - CheckError(httpsys_uv_httpsys_init(uv_httpsys, httpsys_write_callback)); - - hr = HttpSendResponseEntityBody( - uv_httpsys->uv_httpsys_server->requestQueue, - uv_httpsys->requestId, - HTTP_SEND_RESPONSE_FLAG_DISCONNECT, - 0, - NULL, - NULL, - NULL, - 0, - &uv_httpsys->uv_async->async_req.overlapped, - NULL); - - if (ERROR_IO_PENDING != hr) - { - // Synchronous completion or an error - execute callback manually to release the uv_httpsys. - httpsys_write_callback(uv_httpsys->uv_async, 1); - } - } - else if (S_OK != overlappedResult) - { - // Async completion failed - notify JavaScript - - httpsys_notify_error( - uv_httpsys, - HTTPSYS_ERROR_WRITING, - (unsigned int)overlappedResult); - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; - } - else - { - // Successful completion - - if (0 == status) - { - // Call completed asynchronously - send notification to JavaScript. - - Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_WRITTEN); - httpsys_make_callback(event); - } - - if (uv_httpsys->lastChunkSent) - { - // Response is completed - clean up resources - httpsys_free(uv_httpsys, FALSE); - uv_httpsys = NULL; - } - } - - return; + Nan::HandleScope handleScope; + uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)handle->data; + NTSTATUS overlappedResult = (NTSTATUS)uv_httpsys->uv_async->async_req.u.io.overlapped.Internal; + ULONG overlappedLength = (ULONG)uv_httpsys->uv_async->async_req.u.io.overlapped.InternalHigh; + + Log("RESP: httpsys_write_callback called for request '%I64u'\n", uv_httpsys->requestId); + BOOL calledSynchronously = uv_httpsys->synchronousWrite; + // Process async completion + if (calledSynchronously) { + Log("**SYNCHRONOUS**\n"); + } + else { + Log("**ASYNCHRONOUSLY**\n"); + } + httpsys_uv_httpsys_close(uv_httpsys); + PHTTP_REQUEST request = (PHTTP_REQUEST)uv_httpsys->buffer; + HRESULT hr = S_OK; + + if (uv_httpsys->disconnectProcessed) { + Log("RESP: client termination due to upgrade or an error, freeing resources\n"); + // This was a best-effort termination of a client connection after an unaccepted + // HTTP upgrade request or an error. Free up native resources regardless of the outcome + // of the async operation. + + httpsys_free(uv_httpsys, FALSE); + uv_httpsys = NULL; + } + else if (uv_httpsys->disconnect) { + Log("RESP: client disconnect request when async in progress\n"); + // A request was made to disconnect the client when an async operation was in progress. + // Now that the async operation completed, initiate the disconnection again. + + uv_httpsys->disconnectProcessed = TRUE; + + uv_httpsys->synchronousWrite = FALSE; + CheckError(httpsys_uv_httpsys_init(uv_httpsys, (uv_async_cb)httpsys_write_callback)); + + hr = HttpSendResponseEntityBody( + uv_httpsys->uv_httpsys_server->requestQueue, + uv_httpsys->requestId, + HTTP_SEND_RESPONSE_FLAG_DISCONNECT, + 0, + NULL, + NULL, + NULL, + 0, + &uv_httpsys->uv_async->async_req.u.io.overlapped, + NULL); + + Log("RESP: HttpSendResponseEntityBody returned hr = '%ld', request '%I64u'\n", hr, uv_httpsys->requestId); + + if (ERROR_IO_PENDING != hr) + { + // Synchronous completion or an error - execute callback manually to release the uv_httpsys. + uv_httpsys->synchronousWrite = TRUE; + httpsys_write_callback(uv_httpsys->uv_async); + Log("RESP: back from httpsys_write_callback uv_httpsys = '%p'\n", (void*)uv_httpsys); + } + } + else if (S_OK != overlappedResult) + { + Log("RESP: write async completion failed\n"); + // Async completion failed - notify JavaScript + uv_httpsys->refCount++; + + httpsys_notify_error( + uv_httpsys, + HTTPSYS_ERROR_WRITING, + (unsigned int)overlappedResult); + + uv_httpsys->refCount--; + Log("RESP: ref count = '%d'\n", uv_httpsys->refCount); + + if (uv_httpsys->refCount == 0) + { + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; + } + } + else + { + // Successful completion + if (!calledSynchronously) + { + // Call completed asynchronously - send notification to JavaScript. + //added ref count as the httpsys_make_callback can result in re-entrance into + //this function which results in memory being de-allocating more than once + Log("RESP: informing javascript that response has been written, request id = '%I64u', uv_httpsys = '%p'\n", uv_httpsys->requestId, (void*)uv_httpsys); + uv_httpsys->refCount++; + Handle event = httpsys_create_event(uv_httpsys, HTTPSYS_WRITTEN); + httpsys_make_callback(event); + Log("RESP: back from javascript uv_httpsys = '%p'\n", (void*)uv_httpsys); + uv_httpsys->refCount--; + } + + //this original logic was NOT correct for the following scenario: + //async write completion calls this routine + //callback to javascript resulting a further pending response write which is the last chunk + //returns to this function with last chunk flag set + //the req resources freed even though there is a pending async write callback + //causing uv_loop to go bang + // + //solution is to check the uv_async handle, if not NULL do not free resources as their is + //a pending sync callback + if (!uv_httpsys->uv_async && uv_httpsys->lastChunkSent && uv_httpsys->refCount == 0) + { + Log("RESP: response is complete, free uv_httpsys\n"); + // Response is completed - clean up resources + httpsys_free(uv_httpsys, FALSE); + uv_httpsys = NULL; + } + } + Log("RESP: exiting httpsys_write_callback "); + if (calledSynchronously) { + Log("**SYNCHRONOUS**\n"); + } + else { + Log("**ASYNCHRONOUSLY**\n"); + } + return; Error: - // The best-effort termination of a client connection failed. Free up the uv_httpsys. + Log("RESP: error during httpsys_write_callback, calling httpsys_free for request '%I64u'\n", uv_httpsys->requestId); + // The best-effort termination of a client connection failed. Free up the uv_httpsys. - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; } HRESULT httpsys_initialize_body_chunks(Handle options, uv_httpsys_t* uv_httpsys, ULONG* flags) { - HRESULT hr; - HandleScope handleScope; - Handle chunks; - - httpsys_free_chunks(uv_httpsys); - - // Copy JavaScript buffers representing response body chunks into a single - // continuous memory block in an HTTP_DATA_CHUNK. - - chunks = Handle::Cast(options->Get(v8chunks)); - if (chunks->Length() > 0) - { - for (unsigned int i = 0; i < chunks->Length(); i++) { - Handle buffer = chunks->Get(i)->ToObject(); - uv_httpsys->chunk.FromMemory.BufferLength += (ULONG)node::Buffer::Length(buffer); - } - - ErrorIf(NULL == (uv_httpsys->chunk.FromMemory.pBuffer = - malloc(uv_httpsys->chunk.FromMemory.BufferLength)), - ERROR_NOT_ENOUGH_MEMORY); - - char* position = (char*)uv_httpsys->chunk.FromMemory.pBuffer; - for (unsigned int i = 0; i < chunks->Length(); i++) - { - Handle buffer = chunks->Get(i)->ToObject(); - memcpy(position, node::Buffer::Data(buffer), node::Buffer::Length(buffer)); - position += node::Buffer::Length(buffer); - } - } - - // Remove the 'chunks' propert from the options object to indicate they have been - // consumed. - - ErrorIf(!options->Set(v8chunks, Undefined()), E_FAIL); - - // Determine whether the last of the response body is to be written out. - - if (options->Get(v8isLastChunk)->BooleanValue()) - { - uv_httpsys->lastChunkSent = 1; - if (uv_httpsys->uv_httpsys_peer) { - // For upgraded requests, the connection must be manually terminated. - *flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT; - } - } - else - { - *flags |= HTTP_SEND_RESPONSE_FLAG_MORE_DATA; - } - - return S_OK; + HRESULT hr; + Handle chunks; + + Log("RESP: freeing any previous chunks\n"); + httpsys_free_chunks(uv_httpsys); + + // Copy JavaScript buffers representing response body chunks into a single + // continuous memory block in an HTTP_DATA_CHUNK. + chunks = Handle::Cast(options->Get(Nan::New(v8chunks))); + Log("RESP: initialise response body chunks, number of chunks '%d'\n", chunks->Length()); + + if (chunks->Length() > 0) + { + for (unsigned int i = 0; i < chunks->Length(); i++) { + Handle buffer = chunks->Get(i)->ToObject(); + uv_httpsys->chunk.FromMemory.BufferLength += (ULONG)node::Buffer::Length(buffer); + } + + Log("RESP: total buffer length '%d'\n", uv_httpsys->chunk.FromMemory.BufferLength); + + ErrorIf(NULL == (uv_httpsys->chunk.FromMemory.pBuffer = + malloc(uv_httpsys->chunk.FromMemory.BufferLength)), + ERROR_NOT_ENOUGH_MEMORY); + + //initialise buffer write position to start + char* position = (char*)uv_httpsys->chunk.FromMemory.pBuffer; + for (unsigned int i = 0; i < chunks->Length(); i++) + { + Handle buffer = chunks->Get(i)->ToObject(); + //copy chunk to buffer + memcpy(position, node::Buffer::Data(buffer), node::Buffer::Length(buffer)); + //move buffer write position + position += node::Buffer::Length(buffer); + } + } + + // Remove the 'chunks' property from the options object to indicate they have been + // consumed. + + ErrorIf(!options->Set(Nan::New(v8chunks), Nan::Undefined()), E_FAIL); + + // Determine whether the last of the response body is to be written out. + + if (options->Get(Nan::New(v8isLastChunk))->BooleanValue()) + { + Log("RESP: this was the last chunk, setting last chunk sent\n"); + + uv_httpsys->lastChunkSent = 1; + if (uv_httpsys->uv_httpsys_peer) { + Log("RESP: setting disconnect flag\n"); + // For upgraded requests, the connection must be manually terminated. + *flags |= HTTP_SEND_RESPONSE_FLAG_DISCONNECT; + } + } + else + { + Log("RESP: more data to follow, set more data response flag\n"); + *flags |= HTTP_SEND_RESPONSE_FLAG_MORE_DATA; + } + + Log("exiting httpsys_initialize_body_chunks\n"); + return S_OK; Error: - httpsys_free_chunks(uv_httpsys); + Log("RESP: error during initialise body chunks, calling httpsys_free_chunks for request '%I64u'\n", uv_httpsys->requestId); + httpsys_free_chunks(uv_httpsys); - return hr; + return hr; } -Handle httpsys_write_body(const Arguments& args) +void httpsys_write_body(const Nan::FunctionCallbackInfo& info) { - HTTPSYS_EXPORT_PREAMBLE; - Handle options = args[0]->ToObject(); - ULONG flags = 0; - - // Enable NAGLE if requested - - if (!options->Get(v8noDelay)->BooleanValue()) { - flags |= HTTP_SEND_RESPONSE_FLAG_ENABLE_NAGLING; - } - - // If this is an upgraded HTTP request, use the peer uv_httpsys for the write operation - - if (uv_httpsys->uv_httpsys_peer) { - uv_httpsys = uv_httpsys->uv_httpsys_peer; - } - - // Initialize libuv handle representing this async operation - - CheckError(httpsys_uv_httpsys_init(uv_httpsys, httpsys_write_callback)); - - // Prepare response body and determine flags - - CheckError(httpsys_initialize_body_chunks(options, uv_httpsys, &flags)); - - // Initiate async send of the HTTP response body - - hr = HttpSendResponseEntityBody( - uv_httpsys->uv_httpsys_server->requestQueue, - uv_httpsys->requestId, - flags, - uv_httpsys->chunk.FromMemory.pBuffer ? 1 : 0, - uv_httpsys->chunk.FromMemory.pBuffer ? &uv_httpsys->chunk : NULL, - NULL, - NULL, - 0, - &uv_httpsys->uv_async->async_req.overlapped, - NULL); - - if (NO_ERROR == hr) - { - // Synchronous completion. - - httpsys_write_callback(uv_httpsys->uv_async, 1); - } - else - { - ErrorIf(ERROR_IO_PENDING != hr, hr); - } - - // Return true if async completion is pending and an event will be generated once completed - return handleScope.Close(Boolean::New(ERROR_IO_PENDING == hr)); + HRESULT hr; + Handle o = Handle::Cast(info[0]); + uv_httpsys_t* uv_httpsys = (uv_httpsys_t*)Nan::GetInternalFieldPointer(o, 0); + + Handle options = info[0]->ToObject(); + ULONG flags = 0; + + Log("RESP: httpsys_write_body called from javascript for request id '%I64u'\n", uv_httpsys->requestId); + // Enable NAGLE if requested + + if (!options->Get(Nan::New(v8noDelay))->BooleanValue()) { + flags |= HTTP_SEND_RESPONSE_FLAG_ENABLE_NAGLING; + } + + // If this is an upgraded HTTP request, use the peer uv_httpsys for the write operation + if (uv_httpsys->uv_httpsys_peer) { + uv_httpsys = uv_httpsys->uv_httpsys_peer; + Log("RESP: this is an upgraded connection, using uv_httpsys(peer) = '%p'\n", (void*)uv_httpsys); + } + + // Initialize libuv handle representing this async operation + uv_httpsys->synchronousWrite = FALSE; + CheckError(httpsys_uv_httpsys_init(uv_httpsys, (uv_async_cb)httpsys_write_callback)); + + // Prepare response body and determine flags + CheckError(httpsys_initialize_body_chunks(options, uv_httpsys, &flags)); + + // Initiate async send of the HTTP response body + hr = HttpSendResponseEntityBody( + uv_httpsys->uv_httpsys_server->requestQueue, + uv_httpsys->requestId, + flags, + uv_httpsys->chunk.FromMemory.pBuffer ? 1 : 0, + uv_httpsys->chunk.FromMemory.pBuffer ? &uv_httpsys->chunk : NULL, + NULL, + NULL, + 0, + &uv_httpsys->uv_async->async_req.u.io.overlapped, + NULL); + + Log("RESP: HttpSendResponseEntityBody: hr = '%d' for request '%I64u'\n", hr, uv_httpsys->requestId); + + if (NO_ERROR == hr) + { + // Synchronous completion. + uv_httpsys->synchronousWrite = TRUE; + httpsys_write_callback(uv_httpsys->uv_async); + } + else + { + ErrorIf(ERROR_IO_PENDING != hr, hr); + } + // Return true if async completion is pending and an event will be generated once completed + info.GetReturnValue().Set(ERROR_IO_PENDING == hr ? true : false); + return; Error: - httpsys_free(uv_httpsys, TRUE); - uv_httpsys = NULL; + Log("RESP: error during write body, performing clean-up of request '%I64u'\n", uv_httpsys->requestId); + httpsys_free(uv_httpsys, TRUE); + uv_httpsys = NULL; - return handleScope.Close(ThrowException(Int32::New(hr))); + Isolate* isolate = Isolate::GetCurrent(); + info.GetReturnValue().Set(isolate->ThrowException(Nan::New(hr))); } -void init(Handle target) + +void init(v8::Local target) { - HandleScope handleScope; - - // Create V8 representation of HTTP verb strings to reuse across requests - - for (int i = 0; i < HttpVerbMaximum; i++) - { - if (verbs[i]) - { - v8verbs[i] = Persistent::New(String::New(verbs[i])); - } - } - - // Create V8 representation of HTTP header names to reuse across requests - - for (int i = 0; i < HttpHeaderRequestMaximum; i++) - { - if (requestHeaders[i]) - { - v8httpRequestHeaderNames[i] = Persistent::New(String::New(requestHeaders[i])); - } - } - - // Create global V8 strings to reuse across requests - - v8method = Persistent::New(String::NewSymbol("method")); - v8uv_httpsys_server = Persistent::New(String::NewSymbol("uv_httpsys_server")); - v8req = Persistent::New(String::NewSymbol("req")); - v8httpHeaders = Persistent::New(String::NewSymbol("headers")); - v8httpVersionMinor = Persistent::New(String::NewSymbol("httpVersionMinor")); - v8httpVersionMajor = Persistent::New(String::NewSymbol("httpVersionMajor")); - v8eventType = Persistent::New(String::NewSymbol("eventType")); - v8code = Persistent::New(String::NewSymbol("code")); - v8url = Persistent::New(String::NewSymbol("url")); - v8uv_httpsys = Persistent::New(String::NewSymbol("uv_httpsys")); - v8data = Persistent::New(String::NewSymbol("data")); - v8statusCode = Persistent::New(String::NewSymbol("statusCode")); - v8reason = Persistent::New(String::NewSymbol("reason")); - v8knownHeaders = Persistent::New(String::NewSymbol("knownHeaders")); - v8unknownHeaders = Persistent::New(String::NewSymbol("unknownHeaders")); - v8isLastChunk = Persistent::New(String::NewSymbol("isLastChunk")); - v8chunks = Persistent::New(String::NewSymbol("chunks")); - v8id = Persistent::New(String::NewSymbol("id")); - v8value = Persistent::New(String::NewSymbol("value")); - v8cacheDuration = Persistent::New(String::NewSymbol("cacheDuration")); - v8disconnect = Persistent::New(String::NewSymbol("disconnect")); - v8noDelay = Persistent::New(String::NewSymbol("noDelay")); - v8clientCertInfo = Persistent::New(String::NewSymbol("clientCertInfo")); - v8cert = Persistent::New(String::NewSymbol("cert")); - v8authorizationError = Persistent::New(String::NewSymbol("authorizationError")); - v8subject = Persistent::New(String::NewSymbol("subject")); - v8issuer = Persistent::New(String::NewSymbol("issuer")); - v8validFrom = Persistent::New(String::NewSymbol("valid_from")); - v8validTo = Persistent::New(String::NewSymbol("valid_to")); - v8fingerprint = Persistent::New(String::NewSymbol("fingerprint")); - v8encoded = Persistent::New(String::NewSymbol("encoded")); - - // Capture the constructor function of JavaScript Buffer implementation - - bufferConstructor = Persistent::New(Handle::Cast( - Context::GetCurrent()->Global()->Get(String::New("Buffer")))); - - // Create an object template of an object to roundtrip a native pointer to JavaScript - - httpsysObject = Persistent::New(ObjectTemplate::New()); - httpsysObject->SetInternalFieldCount(1); - - // Obtain reference to RtlTimeToSecondsSince1970 function - - HMODULE ntdll = LoadLibrary("Ntdll.dll"); - RtlTimeToSecondsSince1970Impl = - (RtlTimeToSecondsSince1970Func)GetProcAddress(ntdll, "RtlTimeToSecondsSince1970"); - - // Determine whether to propagate raw client X.509 certificate to the application with HTTPS - - httpsys_export_client_cert = (0 < GetEnvironmentVariable("HTTPSYS_EXPORT_CLIENT_CERT", NULL, 0)); - - // Create exports - - NODE_SET_METHOD(target, "httpsys_init", httpsys_init); - NODE_SET_METHOD(target, "httpsys_listen", httpsys_listen); - NODE_SET_METHOD(target, "httpsys_stop_listen", httpsys_stop_listen); - NODE_SET_METHOD(target, "httpsys_resume", httpsys_resume); - NODE_SET_METHOD(target, "httpsys_write_headers", httpsys_write_headers); - NODE_SET_METHOD(target, "httpsys_write_body", httpsys_write_body); + // Create V8 representation of HTTP verb strings to reuse across requests + for (int i = 0; i < HttpVerbMaximum; i++) { + if (verbs[i]) { + v8verbs[i].Reset(Nan::New(verbs[i]).ToLocalChecked()); + } + } + + // Create V8 representation of HTTP header names to reuse across requests + for (int i = 0; i < HttpHeaderRequestMaximum; i++) { + if (requestHeaders[i]) { + v8httpRequestHeaderNames[i].Reset(Nan::New(requestHeaders[i]).ToLocalChecked()); + } + } + + // Create global V8 strings to reuse across requests + v8remoteAddress.Reset(Nan::New("remoteAddress").ToLocalChecked()); + v8method.Reset(Nan::New("method").ToLocalChecked()); + v8uv_httpsys_server.Reset(Nan::New("uv_httpsys_server").ToLocalChecked()); + v8req.Reset(Nan::New("req").ToLocalChecked()); + v8httpHeaders.Reset(Nan::New("headers").ToLocalChecked()); + v8httpVersionMinor.Reset(Nan::New("httpVersionMinor").ToLocalChecked()); + v8httpVersionMajor.Reset(Nan::New("httpVersionMajor").ToLocalChecked()); + v8eventType.Reset(Nan::New("eventType").ToLocalChecked()); + v8code.Reset(Nan::New("code").ToLocalChecked()); + v8url.Reset(Nan::New("url").ToLocalChecked()); + v8uv_httpsys.Reset(Nan::New("uv_httpsys").ToLocalChecked()); + v8data.Reset(Nan::New("data").ToLocalChecked()); + v8statusCode.Reset(Nan::New("statusCode").ToLocalChecked()); + v8reason.Reset(Nan::New("reason").ToLocalChecked()); + v8knownHeaders.Reset(Nan::New("knownHeaders").ToLocalChecked()); + v8unknownHeaders.Reset(Nan::New("unknownHeaders").ToLocalChecked()); + v8isLastChunk.Reset(Nan::New("isLastChunk").ToLocalChecked()); + v8chunks.Reset(Nan::New("chunks").ToLocalChecked()); + v8id.Reset(Nan::New("id").ToLocalChecked()); + v8value.Reset(Nan::New("value").ToLocalChecked()); + v8cacheDuration.Reset(Nan::New("cacheDuration").ToLocalChecked()); + v8disconnect.Reset(Nan::New("disconnect").ToLocalChecked()); + v8noDelay.Reset(Nan::New("noDelay").ToLocalChecked()); + v8clientCertInfo.Reset(Nan::New("clientCertInfo").ToLocalChecked()); + v8cert.Reset(Nan::New("cert").ToLocalChecked()); + v8authorizationError.Reset(Nan::New("authorizationError").ToLocalChecked()); + v8subject.Reset(Nan::New("subject").ToLocalChecked()); + v8issuer.Reset(Nan::New("issuer").ToLocalChecked()); + v8validFrom.Reset(Nan::New("valid_from").ToLocalChecked()); + v8validTo.Reset(Nan::New("valid_to").ToLocalChecked()); + v8fingerprint.Reset(Nan::New("fingerprint").ToLocalChecked()); + v8encoded.Reset(Nan::New("encoded").ToLocalChecked()); + + // Capture the constructor function of JavaScript Buffer implementation + bufferConstructor.Reset(Nan::GetCurrentContext()->Global()->Get(Nan::New("Buffer").ToLocalChecked()).As()); + + // Create an object template of an object to roundtrip a native pointer to JavaScript + Local o = Nan::New(); + o->SetInternalFieldCount(1); + httpsysObject.Reset(o); + + // Obtain reference to RtlTimeToSecondsSince1970 function + HMODULE ntdll = LoadLibrary("Ntdll.dll"); + RtlTimeToSecondsSince1970Impl = + (RtlTimeToSecondsSince1970Func)GetProcAddress(ntdll, "RtlTimeToSecondsSince1970"); + + // Determine whether to propagate raw client X.509 certificate to the application with HTTPS + + httpsys_export_client_cert = (0 < GetEnvironmentVariable("HTTPSYS_EXPORT_CLIENT_CERT", NULL, 0)); + + // Create exports + target->Set(Nan::New("httpsys_init").ToLocalChecked(), Nan::New(httpsys_init)->GetFunction()); + target->Set(Nan::New("httpsys_listen").ToLocalChecked(), Nan::New(httpsys_listen)->GetFunction()); + target->Set(Nan::New("httpsys_stop_listen").ToLocalChecked(), Nan::New(httpsys_stop_listen)->GetFunction()); + target->Set(Nan::New("httpsys_resume").ToLocalChecked(), Nan::New(httpsys_resume)->GetFunction()); + target->Set(Nan::New("httpsys_write_headers").ToLocalChecked(), Nan::New(httpsys_write_headers)->GetFunction()); + target->Set(Nan::New("httpsys_write_body").ToLocalChecked(), Nan::New(httpsys_write_body)->GetFunction()); } -NODE_MODULE(httpsys, init); +NODE_MODULE(httpsys, init) diff --git a/src/httpsys.h b/src/httpsys.h index 12e46ef..3b1b883 100644 --- a/src/httpsys.h +++ b/src/httpsys.h @@ -1,8 +1,14 @@ +#pragma once + #ifndef __HTTPSYS_H #define __HTTPSYS_H -// TODO: implement httpsys_resume +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif +// TODO: implement httpsys_resume +#include #include #include #include @@ -15,6 +21,12 @@ using namespace v8; #pragma comment(lib, "httpapi.lib") #pragma comment(lib, "crypt32.lib") +#define Log(format, ...) \ + memset(logBuf, 0, 1023); \ + _snprintf(logBuf, 1023, format, __VA_ARGS__); \ + for (int i = 0; i < 1024; i++) { if (logBuf[i] == '%') { logBuf[i] = ' '; } } \ + if (debugOut) { fprintf(stderr, logBuf); } + #define ErrorIf(expr, hresult) \ if (expr) \ { \ @@ -34,57 +46,62 @@ using namespace v8; // RtlTimeToSecondsSince1970 declaration -typedef BOOLEAN (WINAPI *RtlTimeToSecondsSince1970Func)(PLARGE_INTEGER, PULONG); +typedef BOOLEAN(WINAPI *RtlTimeToSecondsSince1970Func)(PLARGE_INTEGER, PULONG); // Wrapper of the uv_prepare_t associated with an active server struct uv_httpsys_s; typedef struct uv_httpsys_server_s { - uv_prepare_t uv_prepare; - HTTP_SERVER_SESSION_ID sessionId; - HTTP_URL_GROUP_ID groupId; - HANDLE requestQueue; - unsigned int readsToInitialize; - int refCount; - BOOL closing; - Persistent event; + uv_prepare_t uv_prepare; + HTTP_SERVER_SESSION_ID sessionId; + HTTP_URL_GROUP_ID groupId; + HANDLE requestQueue; + unsigned int readsToInitialize; + int refCount; + BOOL closing; + Nan::Persistent event; } uv_httpsys_server_t; // Wrapper of the uv_async_t with HTTP.SYS specific data typedef struct uv_httpsys_s { - uv_async_t* uv_async; - HTTP_REQUEST_ID requestId; - HTTP_RESPONSE response; - void* buffer; - unsigned int bufferSize; - HTTP_DATA_CHUNK chunk; - int lastChunkSent; - BOOL responseStarted; - BOOL disconnect; - BOOL disconnectProcessed; - BOOL closed; - uv_httpsys_server_t* uv_httpsys_server; - struct uv_httpsys_s* uv_httpsys_peer; - Persistent event; + uv_async_t* uv_async; + HTTP_REQUEST_ID requestId; + HTTP_RESPONSE response; + void* buffer; + unsigned int bufferSize; + HTTP_DATA_CHUNK chunk; + int lastChunkSent; + BOOL responseStarted; + BOOL disconnect; + BOOL disconnectProcessed; + BOOL closed; + BOOL synchronous; + BOOL synchronousWrite; + int refCount; + uv_httpsys_server_t* uv_httpsys_server; + struct uv_httpsys_s* uv_httpsys_peer; + Nan::Persistent event; } uv_httpsys_t; // Types of events passed to the JavaScript callback from native typedef enum { - HTTPSYS_ERROR_INITIALIZING_REQUEST = 1, - HTTPSYS_ERROR_NEW_REQUEST, - HTTPSYS_NEW_REQUEST, - HTTPSYS_ERROR_INITIALIZING_READ_REQUEST_BODY, - HTTPSYS_END_REQUEST, - HTTPSYS_ERROR_READ_REQUEST_BODY, - HTTPSYS_REQUEST_BODY, - HTTPSYS_WRITTEN, - HTTPSYS_ERROR_WRITING, - HTTPSYS_SERVER_CLOSED + HTTPSYS_ERROR_INITIALIZING_REQUEST = 1, + HTTPSYS_ERROR_NEW_REQUEST, + HTTPSYS_NEW_REQUEST, + HTTPSYS_ERROR_INITIALIZING_READ_REQUEST_BODY, + HTTPSYS_END_REQUEST, + HTTPSYS_ERROR_READ_REQUEST_BODY, + HTTPSYS_REQUEST_BODY, + HTTPSYS_WRITTEN, + HTTPSYS_ERROR_WRITING, + HTTPSYS_SERVER_CLOSED } uv_httpsys_event_type; +#define HTTPSYS_HTTP_TRACE 99 + // Utility functions Handle httpsys_create_event(uv_httpsys_t* uv_httpsys, int eventType); @@ -102,23 +119,24 @@ Handle httpsys_create_client_cert_info(PHTTP_SSL_CLIENT_CERT_INFO info); // HTTP processing state machine actions and events -void httpsys_new_request_callback(uv_async_t* handle, int status); -void httpsys_prepare_new_requests(uv_prepare_t* handle, int status); +void httpsys_new_request_callback(uv_async_t* handle); +void httpsys_prepare_new_requests(uv_prepare_t* handle); +void httpsys_read_request_body_callback(uv_async_t* handle); +void httpsys_write_callback(uv_async_t* handle); + HRESULT httpsys_initiate_new_request(uv_httpsys_t* uv_httpsys); -void httpsys_read_request_body_callback(uv_async_t* handle, int status); HRESULT httpsys_read_request_body_loop(uv_httpsys_t* uv_httpsys); HRESULT httpsys_initiate_read_request_body(uv_httpsys_t* uv_httpsys); -void httpsys_write_callback(uv_async_t* handle, int status); // Exports -Handle httpsys_init(const Arguments& args); -Handle httpsys_listen(const Arguments& args); -Handle httpsys_stop_listen(const Arguments& args); -Handle httpsys_resume(const Arguments& args); -Handle httpsys_write_headers(const Arguments& args); -Handle httpsys_write_body(const Arguments& args); +void httpsys_init(const Nan::FunctionCallbackInfo& info); +void httpsys_listen(const Nan::FunctionCallbackInfo& info); +void httpsys_stop_listen(const Nan::FunctionCallbackInfo& info); +void httpsys_resume(const Nan::FunctionCallbackInfo& info); +void httpsys_write_headers(const Nan::FunctionCallbackInfo& info); +void httpsys_write_body(const Nan::FunctionCallbackInfo& info); -void init(Handle target); +void init(v8::Local target); #endif diff --git a/test/102_hello.js b/test/102_hello.js index d9d6eca..c47eec2 100644 --- a/test/102_hello.js +++ b/test/102_hello.js @@ -1,5 +1,8 @@ -var http = require('../lib/httpsys.js').http() - , https = require('../lib/httpsys.js').https() +//allow self signed certificates +//process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +var http = require('../index').http() + , https = require('../index').https() , fs = require('fs') , assert = require('assert'); diff --git a/test/103_slipstream.js b/test/103_slipstream.js index 3b39166..628a798 100644 --- a/test/103_slipstream.js +++ b/test/103_slipstream.js @@ -1,4 +1,4 @@ -require('../lib/httpsys').slipstream(); +require('../index').slipstream(); var http = require('http') , assert = require('assert'); diff --git a/test/201_ws.js b/test/201_ws.js index 8f14b9d..7d4b701 100644 --- a/test/201_ws.js +++ b/test/201_ws.js @@ -1,5 +1,10 @@ -var http = require('../lib/httpsys.js').http() - , https = require('../lib/httpsys.js').https() +//allow self signed certificates +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + + + +var http = require('../index').http() + , https = require('../index').https() , WebSocket = require('ws') , WebSocketServer = require('ws').Server , fs = require('fs') @@ -67,7 +72,7 @@ describe('201_ws.js: einaros/ws', function () { // when SSL is used, reject all server certificates except the one used in the test: ws = new WebSocket('wss://localhost:' + sslport + '/', { agent: false, - rejectUnauthorized: true, + rejectUnauthorized: false, ca: [ serverCert ] }); } diff --git a/test/401_socketio.js b/test/401_socketio.js index 89b84b1..91069a1 100644 --- a/test/401_socketio.js +++ b/test/401_socketio.js @@ -1,16 +1,19 @@ -var http = require('../lib/httpsys.js').http() - , https = require('../lib/httpsys.js').https() +//allow self signed certificates +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +var http = require('../index').http() + , https = require('../index').https() , socketio = require('socket.io') , socketio_client = require('socket.io-client') + , fs = require('fs') , assert = require('assert'); +var domain = "http://localhost"; var port = process.env.PORT || 3401; var sslport = process.env.PORT || 3421; -var server; -// Enable node.js v0.10 to accept self-signed X.509 server certificates as per -// http://stackoverflow.com/questions/15365772/socket-io-ssl-self-signed-ca-certificate-gives-error-when-connecting -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; +var server; +var serverCert = fs.readFileSync(__dirname + '\\..\\performance\\x509-sha1.pem'); describe('401_socketio.js: socket.io', function () { @@ -31,7 +34,7 @@ describe('401_socketio.js: socket.io', function () { }); it('works with HTTP', function (done) { - testTransport('xhr-polling', false, done); + testTransport('polling', false, done); }); it('works with WSS', function (done) { @@ -39,32 +42,44 @@ describe('401_socketio.js: socket.io', function () { }); it('works with HTTPS', function (done) { - testTransport('xhr-polling', true, done); + testTransport('polling', true, done); }); function testTransport(transport, secure, done) { + + //create and listen on sever port if (secure) { server = https.createServer({}, function (req, res) { throw new Error('Regular HTTPS request detected.'); }); + + server.on('error', function (err) { + throw new Error('failed to listen on ' + sslport); + }).listen(domain + ":" + sslport + "/", function () { + //console.log('listening on ' + sslport); + }); } else { server = http.createServer(function (req, res) { throw new Error('Regular HTTP request detected.'); }); + + server.on('error', function (err) { + throw new Error('failed to listen on ' + port); + }).listen(domain + ":" + port + "/", function () { + + }); } + //configure socketio to listen on server port var wss = socketio.listen(server, { log: false }); var refCount = 2; - wss.configure(function() { - wss.set('transports', [ transport ]); - }); - var serverLog = []; var clientLog = []; var toSend = ['one', 'two', 'three', 'four']; + //configure socketio server event listeners wss.sockets.on('connection', function(ws) { serverLog.push('connection'); ws.on('message', function(message) { @@ -76,17 +91,17 @@ describe('401_socketio.js: socket.io', function () { }); }); + //perform client connection to listening server if (secure) { - server.listen(sslport); - var ws = socketio_client.connect('https://localhost:' + sslport); - sslport++; // for subsequent tests + var ws = socketio_client.connect(domain + ":" + sslport); + sslport++; // for subsequent tests } else { - server.listen(port); - var ws = socketio_client.connect('http://localhost:' + port); - port++; // for subsequent tests + var ws = socketio_client.connect(domain + ":" + port); + port++; // for subsequent tests } + //configure socketio client event listeners ws.on('connect', function() { clientLog.push('connect'); sendNext(); @@ -111,5 +126,4 @@ describe('401_socketio.js: socket.io', function () { } } } - }); \ No newline at end of file diff --git a/test/501_https_mutual.js b/test/501_https_mutual.js index 75eee6d..80adf0b 100644 --- a/test/501_https_mutual.js +++ b/test/501_https_mutual.js @@ -1,4 +1,7 @@ -var https = require('../lib/httpsys.js').https() +//allow self signed certificates +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +var https = require('../index.js').https() , fs = require('fs') , assert = require('assert'); diff --git a/tools/build.bat b/tools/build.bat index e9ce0c9..022e1d9 100644 --- a/tools/build.bat +++ b/tools/build.bat @@ -1,76 +1,76 @@ -@echo off -set SELF=%~dp0 -if "%1" equ "" ( - echo Usage: build.bat debug^|release "{version} {version}" ... - echo e.g. build.bat release "0.8.22 0.10.0" - exit /b -1 -) - -SET FLAVOR=%1 -shift -if "%FLAVOR%" equ "" set FLAVOR=release -for %%i in (node.exe) do set NODEEXE=%%~$PATH:i -if not exist "%NODEEXE%" ( - echo Cannot find node.exe - popd - exit /b -1 -) -for %%i in ("%NODEEXE%") do set NODEDIR=%%~dpi -SET DESTDIRROOT=%SELF%\..\lib\native\win32 -set VERSIONS= -:harvestVersions -if "%1" neq "" ( - set VERSIONS=%VERSIONS% %1 - shift - goto :harvestVersions -) -if "%VERSIONS%" equ "" set VERSIONS=0.10.0 -pushd %SELF%\.. -for %%V in (%VERSIONS%) do call :build ia32 x86 %%V -for %%V in (%VERSIONS%) do call :build x64 x64 %%V -popd - -exit /b 0 - -:build - -set DESTDIR=%DESTDIRROOT%\%1\%3 -if exist "%DESTDIR%\node.exe" goto gyp -if not exist "%DESTDIR%\NUL" mkdir "%DESTDIR%" -echo Downloading node.exe %2 %3... -node %SELF%\download.js %2 %3 "%DESTDIR%" -if %ERRORLEVEL% neq 0 ( - echo Cannot download node.exe %2 v%3 - exit /b -1 -) - -:gyp - -echo Building httpsys.node %FLAVOR% for node.js %2 v%3 -set NODEEXE=%DESTDIR%\node.exe -set GYP=%APPDATA%\npm\node_modules\node-gyp\bin\node-gyp.js -if not exist "%GYP%" ( - echo Cannot find node-gyp at %GYP%. Make sure to install with npm install node-gyp -g - exit /b -1 -) - -"%NODEEXE%" "%GYP%" configure build --msvs_version=2012 -%FLAVOR% -if %ERRORLEVEL% neq 0 ( - echo Error building httpsys.node %FLAVOR% for node.js %2 v%3 - exit /b -1 -) - -echo %DESTDIR% -copy /y .\build\%FLAVOR%\httpsys.node "%DESTDIR%" -if %ERRORLEVEL% neq 0 ( - echo Error copying httpsys.node %FLAVOR% for node.js %2 v%3 - exit /b -1 -) - -copy /y "%DESTDIR%\..\msvcr110.dll" "%DESTDIR%" -if %ERRORLEVEL% neq 0 ( - echo Error copying msvcr110.dll %FLAVOR% to %DESTDIR% - exit /b -1 -) - -echo Success building httpsys.node %FLAVOR% for node.js %2 v%3 +@echo off +set SELF=%~dp0 +if "%1" equ "" ( + echo Usage: build.bat debug^|release "{version} {version}" ... + echo e.g. build.bat release "10.13.0" + exit /b -1 +) + +SET FLAVOR=%1 +shift +if "%FLAVOR%" equ "" set FLAVOR=release +for %%i in (node.exe) do set NODEEXE=%%~$PATH:i +if not exist "%NODEEXE%" ( + echo Cannot find node.exe + popd + exit /b -1 +) +for %%i in ("%NODEEXE%") do set NODEDIR=%%~dpi +SET DESTDIRROOT=%SELF%\..\lib\native\win32 +set VERSIONS= +:harvestVersions +if "%1" neq "" ( + set VERSIONS=%VERSIONS% %1 + shift + goto :harvestVersions +) +if "%VERSIONS%" equ "" set VERSIONS=10.13.0 +pushd %SELF%\.. +for %%V in (%VERSIONS%) do call :build ia32 x86 %%V +for %%V in (%VERSIONS%) do call :build x64 x64 %%V +popd + +exit /b 0 + +:build + +set DESTDIR=%DESTDIRROOT%\%1\%3 +if exist "%DESTDIR%\node.exe" goto gyp +if not exist "%DESTDIR%\NUL" mkdir "%DESTDIR%" +echo Downloading node.exe %2 %3... +node %SELF%\download.js %2 %3 "%DESTDIR%" +if %ERRORLEVEL% neq 0 ( + echo Cannot download node.exe %2 v%3 + exit /b -1 +) + +:gyp + +echo Building httpsys.node %FLAVOR% for node.js %2 v%3 +set NODEEXE=%DESTDIR%\node.exe +set GYP=%APPDATA%\npm\node_modules\node-gyp\bin\node-gyp.js +if not exist "%GYP%" ( + echo Cannot find node-gyp at %GYP%. Make sure to install with npm install node-gyp -g + exit /b -1 +) + +"%NODEEXE%" "%GYP%" configure build --msvs_version=2015 -%FLAVOR% +if %ERRORLEVEL% neq 0 ( + echo Error building httpsys.node %FLAVOR% for node.js %2 v%3 + exit /b -1 +) + +echo %DESTDIR% +copy /y .\build\%FLAVOR%\httpsys.node "%DESTDIR%" +if %ERRORLEVEL% neq 0 ( + echo Error copying httpsys.node %FLAVOR% for node.js %2 v%3 + exit /b -1 +) + +copy /y "%DESTDIR%\..\msvcr110.dll" "%DESTDIR%" +if %ERRORLEVEL% neq 0 ( + echo Error copying msvcr110.dll %FLAVOR% to %DESTDIR% + exit /b -1 +) + +echo Success building httpsys.node %FLAVOR% for node.js %2 v%3 diff --git a/tools/buildall.bat b/tools/buildall.bat index 512e2ea..3a72e8b 100644 --- a/tools/buildall.bat +++ b/tools/buildall.bat @@ -1,2 +1,2 @@ -@echo off -%~dp0\build.bat release 0.6.20 0.8.22 0.10.15 +@echo off +%~dp0\build.bat release 8.9.4 9.9.0 10.13.0 diff --git a/tools/builddev.bat b/tools/builddev.bat index ad26153..0d67506 100644 --- a/tools/builddev.bat +++ b/tools/builddev.bat @@ -1,4 +1,4 @@ -@echo off -pushd %~dp0\.. -call node-gyp configure build --msvs_version=2012 -debug +@echo off +pushd %~dp0\.. +call node-gyp configure build --msvs_version=2015 -debug popd \ No newline at end of file diff --git a/tools/download.js b/tools/download.js index c036d71..34f4214 100644 --- a/tools/download.js +++ b/tools/download.js @@ -1,13 +1,13 @@ -var http = require('http'); - -var url = 'http://nodejs.org/dist/v' + process.argv[3] + '/' - + (process.argv[2] === 'x86' ? '/node.exe' : '/x64/node.exe'); - -console.log(url); -http.get(url, function (res) { - if (res.statusCode !== 200) - throw new Error('HTTP response status code ' + res.statusCode); - - var stream = require('fs').createWriteStream(process.argv[4] + '/node.exe'); - res.pipe(stream); +var http = require('http'); + +var url = 'http://nodejs.org/dist/v' + process.argv[3] + '/' + + (process.argv[2] === 'x86' ? '/win-x86/node.exe' : '/win-x64/node.exe'); + +console.log(url); +http.get(url, function (res) { + if (res.statusCode !== 200) + throw new Error('HTTP response status code ' + res.statusCode); + + var stream = require('fs').createWriteStream(process.argv[4] + '/node.exe'); + res.pipe(stream); }); \ No newline at end of file diff --git a/tools/install.bat b/tools/install.bat index c7e6045..45fed49 100644 --- a/tools/install.bat +++ b/tools/install.bat @@ -1,4 +1,4 @@ -@echo off -set LIBROOT=%~dp0\..\lib\native\win32 -for /F %%D in ('dir /b /ad "%LIBROOT%\ia32"') do copy /y "%LIBROOT%\ia32\msvcr110.dll" "%LIBROOT%\ia32\%%D" 2>&1 > nul +@echo off +set LIBROOT=%~dp0\..\lib\native\win32 +for /F %%D in ('dir /b /ad "%LIBROOT%\ia32"') do copy /y "%LIBROOT%\ia32\msvcr110.dll" "%LIBROOT%\ia32\%%D" 2>&1 > nul for /F %%D in ('dir /b /ad "%LIBROOT%\x64"') do copy /y "%LIBROOT%\x64\msvcr110.dll" "%LIBROOT%\x64\%%D" 2>&1 > nul \ No newline at end of file