diff --git a/dist-tools/test/helpers.coffee b/dist-tools/test/helpers.coffee index 486103f4b4..1b7ec20814 100644 --- a/dist-tools/test/helpers.coffee +++ b/dist-tools/test/helpers.coffee @@ -3,7 +3,7 @@ fs = require('fs') evalCode = (code, preamble) -> eval """ (function() { - var window = GLOBAL; + var window = global; #{preamble}; return #{code}; })(); diff --git a/lib/event_listeners.js b/lib/event_listeners.js index 8e1cdefd1c..0cd1563b33 100644 --- a/lib/event_listeners.js +++ b/lib/event_listeners.js @@ -206,8 +206,11 @@ AWS.EventListeners = { function callback(httpResp) { resp.httpResponse.stream = httpResp; - httpResp.on('headers', function onHeaders(statusCode, headers) { - resp.request.emit('httpHeaders', [statusCode, headers, resp]); + httpResp.on('headers', function onHeaders(statusCode, headers, statusMessage) { + resp.request.emit( + 'httpHeaders', + [statusCode, headers, resp, statusMessage] + ); if (!resp.httpResponse.streaming) { if (AWS.HttpClient.streamsApiVersion === 2) { // streams2 API check @@ -277,8 +280,9 @@ AWS.EventListeners = { }); add('HTTP_HEADERS', 'httpHeaders', - function HTTP_HEADERS(statusCode, headers, resp) { + function HTTP_HEADERS(statusCode, headers, resp, statusMessage) { resp.httpResponse.statusCode = statusCode; + resp.httpResponse.statusMessage = statusMessage; resp.httpResponse.headers = headers; resp.httpResponse.body = new AWS.util.Buffer(''); resp.httpResponse.buffers = []; diff --git a/lib/http/node.js b/lib/http/node.js index 644f993e5a..fd4198839e 100644 --- a/lib/http/node.js +++ b/lib/http/node.js @@ -44,7 +44,12 @@ AWS.NodeHttpClient = AWS.util.inherit({ if (cbAlreadyCalled) return; cbAlreadyCalled = true; callback(httpResp); - httpResp.emit('headers', httpResp.statusCode, httpResp.headers); + httpResp.emit( + 'headers', + httpResp.statusCode, + httpResp.headers, + httpResp.statusMessage + ); }); httpRequest.stream = stream; // attach stream to httpRequest diff --git a/lib/http/xhr.js b/lib/http/xhr.js index c7ef1dec71..9878e2f709 100644 --- a/lib/http/xhr.js +++ b/lib/http/xhr.js @@ -28,7 +28,12 @@ AWS.XHRClient = AWS.util.inherit({ try { xhr.responseType = 'arraybuffer'; } catch (e) {} emitter.statusCode = xhr.status; emitter.headers = self.parseHeaders(xhr.getAllResponseHeaders()); - emitter.emit('headers', emitter.statusCode, emitter.headers); + emitter.emit( + 'headers', + emitter.statusCode, + emitter.headers, + xhr.statusText + ); headersEmitted = true; } if (this.readyState === this.DONE) { diff --git a/lib/http_response.d.ts b/lib/http_response.d.ts index d7a4148ae8..a1346f3645 100644 --- a/lib/http_response.d.ts +++ b/lib/http_response.d.ts @@ -22,8 +22,12 @@ export class HttpResponse { * The HTTP status code of the response (e.g., 200, 404). */ statusCode: number; + /** + * The HTTP status message of the response (e.g., 'Bad Request', 'Not Found') + */ + statusMessage: string; /** * Whether this response is being streamed at a low-level. */ streaming: boolean; -} \ No newline at end of file +} diff --git a/lib/protocol/json.js b/lib/protocol/json.js index b9c9d8c713..72c0dd56f2 100644 --- a/lib/protocol/json.js +++ b/lib/protocol/json.js @@ -26,14 +26,19 @@ function extractError(resp) { } if (httpResponse.body.length > 0) { - var e = JSON.parse(httpResponse.body.toString()); - if (e.__type || e.code) { - error.code = (e.__type || e.code).split('#').pop(); - } - if (error.code === 'RequestEntityTooLarge') { - error.message = 'Request body must be less than 1 MB'; - } else { - error.message = (e.message || e.Message || null); + try { + var e = JSON.parse(httpResponse.body.toString()); + if (e.__type || e.code) { + error.code = (e.__type || e.code).split('#').pop(); + } + if (error.code === 'RequestEntityTooLarge') { + error.message = 'Request body must be less than 1 MB'; + } else { + error.message = (e.message || e.Message || null); + } + } catch (e) { + error.statusCode = httpResponse.statusCode; + error.message = httpResponse.statusMessage; } } else { error.statusCode = httpResponse.statusCode; diff --git a/lib/protocol/query.js b/lib/protocol/query.js index 2e1adcbdb6..27e11ce122 100644 --- a/lib/protocol/query.js +++ b/lib/protocol/query.js @@ -30,7 +30,14 @@ function extractError(resp) { Message: 'Unknown operation ' + resp.request.operation }; } else { - data = new AWS.XML.Parser().parse(body); + try { + data = new AWS.XML.Parser().parse(body); + } catch (e) { + data = { + Code: resp.httpResponse.statusCode, + Message: resp.httpResponse.statusMessage + }; + } } if (data.requestId && !resp.requestId) resp.requestId = data.requestId; diff --git a/lib/protocol/rest_xml.js b/lib/protocol/rest_xml.js index ebfd1b6536..a0e2aeafa7 100644 --- a/lib/protocol/rest_xml.js +++ b/lib/protocol/rest_xml.js @@ -37,7 +37,16 @@ function buildRequest(req) { function extractError(resp) { Rest.extractError(resp); - var data = new AWS.XML.Parser().parse(resp.httpResponse.body.toString()); + var data; + try { + data = new AWS.XML.Parser().parse(resp.httpResponse.body.toString()); + } catch (e) { + data = { + Code: resp.httpResponse.statusCode, + Message: resp.httpResponse.statusMessage + }; + } + if (data.Errors) data = data.Errors; if (data.Error) data = data.Error; if (data.Code) { diff --git a/lib/request.d.ts b/lib/request.d.ts index 1fc2bfad62..ea3ded7264 100644 --- a/lib/request.d.ts +++ b/lib/request.d.ts @@ -122,7 +122,7 @@ export class Request { * @param {string} event - httpHeaders: triggered when headers are sent by the remote server. * @param {function} listener - Callback to run when the headers are sent by the remote server. */ - on(event: "httpHeaders", listener: (statusCode: number, headers: {[key: string]: string}, response: Response) => void): Request; + on(event: "httpHeaders", listener: (statusCode: number, headers: {[key: string]: string}, response: Response, statusMessage: string) => void): Request; /** * Adds a listener that is triggered when data is sent by the remote server. * @@ -176,4 +176,4 @@ export class Request { export interface Progress { loaded: number; total: number; -} \ No newline at end of file +} diff --git a/lib/request.js b/lib/request.js index 0bc1bd753b..6b58fdec19 100644 --- a/lib/request.js +++ b/lib/request.js @@ -246,11 +246,13 @@ fsm.setupStates(); * * @!group HTTP Events * - * @!event httpHeaders(statusCode, headers, response) + * @!event httpHeaders(statusCode, headers, response, statusMessage) * Triggered when headers are sent by the remote server * @param statusCode [Integer] the HTTP response code * @param headers [map] the response headers * @param (see AWS.Request~send) + * @param statusMessage [String] A status message corresponding to the HTTP + * response code * @context (see AWS.Request~send) * * @!event httpData(chunk, response) diff --git a/test/helpers.coffee b/test/helpers.coffee index 978fa2e0d2..196cf8f7f9 100644 --- a/test/helpers.coffee +++ b/test/helpers.coffee @@ -1,18 +1,18 @@ AWS = null -global = null +topLevelScope = null ignoreRequire = require if typeof window == 'undefined' AWS = ignoreRequire('../lib/aws') - global = GLOBAL + topLevelScope = global else AWS = window.AWS - global = window + topLevelScope = window -if global.jasmine - global.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000 +if topLevelScope.jasmine + topLevelScope.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000 _it = it -global.it = (label, fn) -> +topLevelScope.it = (label, fn) -> if label.match(/\(no phantomjs\)/) and navigator and navigator.userAgent.match(/phantomjs/i) return _it(label, fn) @@ -76,10 +76,10 @@ _spyOn = (obj, methodName) -> # Disable setTimeout for tests, but keep original in case test needs to use it # Warning: this might cause unpredictable results # TODO: refactor this out. -global.setTimeoutOrig = global.setTimeout -global.setTimeout = (fn) -> fn() +topLevelScope.setTimeoutOrig = topLevelScope.setTimeout +topLevelScope.setTimeout = (fn) -> fn() -global.expect = require('chai').expect +topLevelScope.expect = require('chai').expect matchXML = (xml1, xml2) -> results = [] diff --git a/test/protocol/json.spec.coffee b/test/protocol/json.spec.coffee index 91f5b931d0..2b568ef6f2 100644 --- a/test/protocol/json.spec.coffee +++ b/test/protocol/json.spec.coffee @@ -79,6 +79,7 @@ describe 'AWS.Protocol.Json', -> describe 'extractError', -> extractError = (body) -> response.httpResponse.statusCode = 500 + response.httpResponse.statusMessage = 'Internal Server Error' response.httpResponse.body = new Buffer(body) svc.extractError(response) @@ -102,6 +103,14 @@ describe 'AWS.Protocol.Json', -> expect(response.error.message).to.equal('500') expect(response.data).to.equal(null) + it 'returns the status code when the body is not valid JSON', -> + extractError 'Http/1.1 Service Unavailable ' + expect(response.error ).to.be.instanceOf(Error) + expect(response.error.code).to.equal('UnknownError') + expect(response.error.statusCode).to.equal(500) + expect(response.error.message).to.equal('Internal Server Error') + expect(response.data).to.equal(null) + it 'returns UnknownError when the error type is not set', -> extractError '{"message":"Error Message" }' expect(response.error ).to.be.instanceOf(Error) @@ -171,4 +180,4 @@ describe 'AWS.Protocol.Json', -> extractData '{"i":1, "b": null}' expect(response.error).to.equal(null) expect(response.data.i).to.equal(1) - expect(response.data.b).to.equal(null) \ No newline at end of file + expect(response.data.b).to.equal(null) diff --git a/test/protocol/query.spec.coffee b/test/protocol/query.spec.coffee index 3b02dd3198..d31aa94a4c 100644 --- a/test/protocol/query.spec.coffee +++ b/test/protocol/query.spec.coffee @@ -99,6 +99,7 @@ describe 'AWS.Protocol.Query', -> """ response.httpResponse.statusCode = 400 + response.httpResponse.statusMessage = 'Bad Request' response.httpResponse.body = new Buffer(body) svc.extractError(response) @@ -122,6 +123,12 @@ describe 'AWS.Protocol.Query', -> expect(response.error.message).to.equal(null) expect(response.data).to.equal(null) + it 'returns an empty error when the body cannot be parsed', -> + extractError JSON.stringify({"foo":"bar","fizz":["buzz", "pop"]}) + expect(response.error.code).to.equal(400) + expect(response.error.message).to.equal('Bad Request') + expect(response.data).to.equal(null) + it 'extracts error when inside ', -> extractError """ diff --git a/test/protocol/rest_xml.spec.coffee b/test/protocol/rest_xml.spec.coffee index 9310c8a487..7f1044c228 100644 --- a/test/protocol/rest_xml.spec.coffee +++ b/test/protocol/rest_xml.spec.coffee @@ -217,6 +217,7 @@ describe 'AWS.Protocol.RestXml', -> """ response.httpResponse.statusCode = 400 + response.httpResponse.statusMessage = 'Bad Request' response.httpResponse.body = new Buffer(body) svc.extractError(response) @@ -234,6 +235,13 @@ describe 'AWS.Protocol.RestXml', -> expect(response.error.message).to.equal(null) expect(response.data).to.equal(null) + it 'returns an empty error when the body cannot be parsed', -> + extractError JSON.stringify({"foo":"bar","fizz":["buzz", "pop"]}) + expect(response.error ).to.be.instanceOf(Error) + expect(response.error.code).to.equal(400) + expect(response.error.message).to.equal('Bad Request') + expect(response.data).to.equal(null) + it 'extracts error when inside ', -> extractError """ diff --git a/test/util.spec.coffee b/test/util.spec.coffee index 011499dbb6..62d6c2e6b4 100644 --- a/test/util.spec.coffee +++ b/test/util.spec.coffee @@ -86,7 +86,7 @@ describe 'AWS.util.date', -> describe 'getDate', -> it 'should return current date by default', -> now = {} - obj = if AWS.util.isNode() then GLOBAL else window + obj = if AWS.util.isNode() then global else window helpers.spyOn(obj, 'Date').andCallFake -> now expect(util.getDate()).to.equal(now) @@ -95,7 +95,7 @@ describe 'AWS.util.date', -> beforeEach -> [date, mocked, config] = [Date, false, AWS.config] - obj = if AWS.util.isNode() then GLOBAL else window + obj = if AWS.util.isNode() then global else window helpers.spyOn(obj, 'Date').andCallFake (t) -> if mocked new date(t)