From 70bd1a1639c111523bf02dc5d161566c60e7e9d3 Mon Sep 17 00:00:00 2001 From: Derek Kastner Date: Thu, 22 Mar 2012 14:25:50 -0400 Subject: [PATCH 1/5] IE XDomainRequest support for IE CORS --- index.js | 9 ++++++--- lib/request.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index bca5a27..5d24b5e 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ http.request = function (params, cb) { if (!params.host) params.host = window.location.host.split(':')[0]; if (!params.port) params.port = window.location.port; - var req = new Request(new xhrHttp, params); + var req = new Request(new xhrHttp(params), params); if (cb) req.on('response', cb); return req; }; @@ -22,10 +22,13 @@ http.get = function (params, cb) { http.Agent = function () {}; http.Agent.defaultMaxSockets = 4; -var xhrHttp = (function () { +var xhrHttp = function (params) { if (typeof window === 'undefined') { throw new Error('no window object present'); } + else if (params.host != window.location.host.split(':')[0] && window.XDomainRequest) { + return window.XDomainRequest; + } else if (window.XMLHttpRequest) { return window.XMLHttpRequest; } @@ -56,4 +59,4 @@ var xhrHttp = (function () { else { throw new Error('ajax not supported in this browser'); } -})(); +}; diff --git a/lib/request.js b/lib/request.js index 19ecca3..f5ad1b7 100644 --- a/lib/request.js +++ b/lib/request.js @@ -32,9 +32,26 @@ var Request = module.exports = function (xhr, params) { self.emit('response', res); }); + + xhr.onprogress = function() { // IE XDR + xhr.readyState = 2; + res.getAllResponseHeaders = function() { + return 'Content-Type: ' + xhr.contentType; // This is the only header available + }; + res.handle(xhr); + } xhr.onreadystatechange = function () { res.handle(xhr); }; + xhr.onload = function() { // IE XDR + xhr.readyState = 4; + res.handle(xhr); + }; + xhr.onerror = function() { // IE XDR + xhr.readyState = 3; + xhr.error = 'Unknown error'; + res.handle(xhr); + }; }; Request.prototype = new EventEmitter; From db0f1b376e46f4908e42d262eee92e395a94804a Mon Sep 17 00:00:00 2001 From: Benjamin Goering Date: Wed, 2 Jul 2014 13:05:09 -0700 Subject: [PATCH 2/5] make tests pass --- index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 4d943b5..dc054d8 100644 --- a/index.js +++ b/index.js @@ -26,8 +26,7 @@ http.request = function (params, cb) { params.host = params.host.split(':')[0]; } if (!params.port) params.port = params.scheme == 'https' ? 443 : 80; - - var req = new Request(new xhrHttp(params), params); + var req = new Request(new (xhrHttp(params)), params); if (cb) req.on('response', cb); return req; }; @@ -79,7 +78,7 @@ var xhrHttp = function (params) { else { throw new Error('ajax not supported in this browser'); } -})(); +}; http.STATUS_CODES = { 100 : 'Continue', From 7439d1de3fe865c48d1fc7c4056891a3b6398a89 Mon Sep 17 00:00:00 2001 From: Benjamin Goering Date: Tue, 8 Jul 2014 00:57:48 -0700 Subject: [PATCH 3/5] For cross-origin requests in browsers where XHR does not support CORS, use XDomainRequest if it exists --- .gitignore | 1 + index.js | 14 ++++++++++-- lib/request.js | 59 +++++++++++++++++++++++++++++++++++-------------- lib/response.js | 25 ++++++++++++++++++--- package.json | 2 +- readme.markdown | 29 +++++++++++++++--------- 6 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/index.js b/index.js index dc054d8..1266d39 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ var http = module.exports; var EventEmitter = require('events').EventEmitter; var Request = require('./lib/request'); -var url = require('url') +var url = require('url'); http.request = function (params, cb) { if (typeof params === 'string') { @@ -45,7 +45,8 @@ var xhrHttp = function (params) { if (typeof window === 'undefined') { throw new Error('no window object present'); } - else if (params.host != window.location.host.split(':')[0] && window.XDomainRequest) { + else if (shouldXDR(params)) { + // NOTE: params.withCredentials will be ignored: http://bit.ly/ie9nocors return window.XDomainRequest; } else if (window.XMLHttpRequest) { @@ -138,3 +139,12 @@ http.STATUS_CODES = { 510 : 'Not Extended', // RFC 2774 511 : 'Network Authentication Required' // RFC 6585 }; + +// whether the request with params should use XDomainRequest +function shouldXDR(params) { + var crossOrigin = params.host != window.location.host.split(':')[0]; + var xhrExists = typeof XMLHttpRequest !== 'undefined'; + var xdrExists = typeof XDomainRequest !== 'undefined'; + var noCORS = xhrExists && ! ('withCredentials' in new XMLHttpRequest); + return crossOrigin && noCORS && xdrExists; +} diff --git a/lib/request.js b/lib/request.js index 220216e..e61fd3c 100644 --- a/lib/request.js +++ b/lib/request.js @@ -14,7 +14,7 @@ var Request = module.exports = function (xhr, params) { + (params.port ? ':' + params.port : '') + (params.path || '/') ; - + if (typeof params.withCredentials === 'undefined') { params.withCredentials = true; } @@ -53,34 +53,59 @@ var Request = module.exports = function (xhr, params) { self.emit('close'); }); - res.on('ready', function () { + res.once('ready', function () { self.emit('response', res); }); - - xhr.onprogress = function() { // IE XDR - xhr.readyState = 2; - res.getAllResponseHeaders = function() { - return 'Content-Type: ' + xhr.contentType; // This is the only header available - }; - res.handle(xhr); - } xhr.onreadystatechange = function () { // Fix for IE9 bug // SCRIPT575: Could not complete the operation due to error c00c023f // It happens when a request is aborted, calling the success callback anyway with readyState === 4 if (xhr.__aborted) return; + if (xhr.readyState === 4 && xhr.status === 0) { + // onerror will fire and its an error so there should be no res + return; + } res.handle(xhr); }; - xhr.onload = function() { // IE XDR - xhr.readyState = 4; - res.handle(xhr); - }; - xhr.onerror = function() { // IE XDR - xhr.readyState = 3; - xhr.error = 'Unknown error'; + + xhr.onerror = function() { + // http://www.w3.org/TR/XMLHttpRequest/#the-status-attribute + // there was an error making the request + // In node, this means the request should emit 'error' and no response + // should ever be created + if (xhr.status === 0) { + // error flag is set so no response should be gotten + requestError(new Error("XHR is done (state 4) but error flag is set (status 0)")); + return; + } + else if ( ! xhr.status) { + // If falsy but not 0, it's an IE XDR + requestError(new Error('XDomainRequest Error')); + return; + } + + xhr.error = xhr.error || 'Unknown error'; res.handle(xhr); + + function requestError(err) { + self.emit('error', err); + self.emit('close'); + } }; + + if (window.XDomainRequest && xhr instanceof window.XDomainRequest) { + // IE XDomainRequest has onload but not onreadystatechange + // Raised when the object has been completely received from the server. + // http://msdn.microsoft.com/en-us/library/ie/ms536942(v=vs.85).aspx + xhr.onload = function() { // IE XDR + res.handle(xhr); + }; + // IE sometimes fails if you don't specify every handler. + // https://github.com/matthewwithanm/httpplease.js/blob/861b26a9916543ff34963192d734da4c06603d0b/lib/index.js#L94 + xhr.onprogress = function () {}; + xhr.ontimeout = function () {}; + } }; inherits(Request, Stream); diff --git a/lib/response.js b/lib/response.js index f83d761..06d0983 100644 --- a/lib/response.js +++ b/lib/response.js @@ -87,10 +87,13 @@ Response.prototype.handle = function (res) { } } else if (res.readyState === 4) { - if (!this.statusCode) { - this.statusCode = res.status; - this.emit('ready'); + this.statusCode = res.status; + // statusCode 0 means the error flag is set on the request + // and thus there is no response to handle + if (this.statusCode === 0) { + return; } + this.emit('ready'); this._emitData(res); if (res.error) { @@ -100,6 +103,17 @@ Response.prototype.handle = function (res) { this.emit('close'); } + else if (isXDomainRequest(res)) { + // XDomainRequest wil only load if it got a 200 statusCode + // http://bugs.jquery.com/ticket/8283 + if (!this.statusCode) { + this.statusCode = 200; + } + this.emit('ready'); + this._emitData(res); + this.emit('end'); + this.emit('close'); + } }; Response.prototype._emitData = function (res) { @@ -118,3 +132,8 @@ Response.prototype._emitData = function (res) { var isArray = Array.isArray || function (xs) { return Object.prototype.toString.call(xs) === '[object Array]'; }; + +function isXDomainRequest(res) { + var XDomainRequest = window.XDomainRequest; + return XDomainRequest && res instanceof XDomainRequest; +} diff --git a/package.json b/package.json index 15490aa..1f1dab4 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "http-browserify", + "name": "http-browserify-xdr", "version": "1.4.1", "description": "http module compatability for browserify", "main": "index.js", diff --git a/readme.markdown b/readme.markdown index 1088261..3e5fcb3 100644 --- a/readme.markdown +++ b/readme.markdown @@ -1,19 +1,28 @@ -# http-browserify +# http-browserify-xdr The -[http](http://nodejs.org/docs/v0.4.10/api/all.html#hTTP) module from node.js, -but for browsers. +[http](http://nodejs.org/docs/v0.4.10/api/all.html#hTTP) module from browserify, +but for browsers that can only do cross-origin requests with XDomainRequest (IE8 & IE9). -When you `require('http')` in -[browserify](http://github.com/substack/node-browserify), -this module will be loaded. +The out-of-the-box [http-browserify] does not work for cross-origin requests with XDomainRequest, and [an open issue](https://github.com/substack/http-browserify/pull/3) has gone unresponded to for almost 3 years. + +Use this in your browserify project by adding the following to your package.json: +```json +"browser": { + "http": "http-browserify-xdr" +} +``` + +Note: XDomainRequests [cannot send cookies](http://bit.ly/ie9nocors), so 'withCredentials' options will be ignored. The way to do cross-origin requests withCredentials in these browsers is, well, you can't. You have to open an iframe serving a src on the origin you want to request, then postMessage into it, have it make the request, then postMessage the response out. See [sockjs-client](https://github.com/sockjs/sockjs-client#supported-transports-by-browser-html-served-from-http-or-https) for a referenc eimplementation of that. + +The following is the original http-browserify README because the API is the same. # example ``` js var http = require('http'); -http.get({ path : '/beep' }, function (res) { +http.get({ host: 'anotherorigin.com', path : '/beep' }, function (res) { var div = document.getElementById('result'); div.innerHTML += 'GET /beep
'; @@ -108,11 +117,11 @@ You can do: ````javascript var bundle = browserify({ - require : { http : 'http-browserify' } + require : { http : 'http-browserify-xdr' } }); ```` -in order to map "http-browserify" over `require('http')` in your browserified +in order to map "http-browserify-xdr" over `require('http')` in your browserified source. # install @@ -120,7 +129,7 @@ source. With [npm](https://npmjs.org) do: ``` -npm install http-browserify +npm install http-browserify-xdr ``` # license From 32d57a519ba8d58ee1a969e27ff2088fc71b6fa4 Mon Sep 17 00:00:00 2001 From: Benjamin Goering Date: Tue, 8 Jul 2014 00:59:49 -0700 Subject: [PATCH 4/5] Update readme.markdown --- readme.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.markdown b/readme.markdown index 3e5fcb3..417abf8 100644 --- a/readme.markdown +++ b/readme.markdown @@ -13,6 +13,8 @@ Use this in your browserify project by adding the following to your package.json } ``` +I intend to have version numbers here mirror http-browserify changes (>= 1.4.1) until @substack merges my pull request. + Note: XDomainRequests [cannot send cookies](http://bit.ly/ie9nocors), so 'withCredentials' options will be ignored. The way to do cross-origin requests withCredentials in these browsers is, well, you can't. You have to open an iframe serving a src on the origin you want to request, then postMessage into it, have it make the request, then postMessage the response out. See [sockjs-client](https://github.com/sockjs/sockjs-client#supported-transports-by-browser-html-served-from-http-or-https) for a referenc eimplementation of that. The following is the original http-browserify README because the API is the same. From 8c914fc0d16571935e367b82a06c6396f7a8be4f Mon Sep 17 00:00:00 2001 From: Benjamin Goering Date: Tue, 8 Jul 2014 01:01:02 -0700 Subject: [PATCH 5/5] Update readme.markdown --- readme.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.markdown b/readme.markdown index 417abf8..6ea6076 100644 --- a/readme.markdown +++ b/readme.markdown @@ -2,7 +2,7 @@ The [http](http://nodejs.org/docs/v0.4.10/api/all.html#hTTP) module from browserify, -but for browsers that can only do cross-origin requests with XDomainRequest (IE8 & IE9). +but supporting browsers that can only do cross-origin requests with XDomainRequest (IE8 & IE9). The out-of-the-box [http-browserify] does not work for cross-origin requests with XDomainRequest, and [an open issue](https://github.com/substack/http-browserify/pull/3) has gone unresponded to for almost 3 years.