Skip to content

Commit

Permalink
Merge pull request #331 from nicksay/response-type-json
Browse files Browse the repository at this point in the history
Allow advanced users to enforce JSON response types in XHRs.
  • Loading branch information
nicksay committed Apr 16, 2015
2 parents 18f5fd0 + 027fc2c commit b6d08d5
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 15 deletions.
64 changes: 49 additions & 15 deletions src/client/nav/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ spf.nav.request.send = function(url, opt_options) {
onDone: handleComplete,
onTimeout: handleComplete
};
// As an advanced option, allow XHR requests to enforce JSON responses.
// This can make response parsing more efficient by reducing contention on
// the main thread (especially for very large responses), but as a
// side-effect, it removes the ability to parse chunked multipart responses
// on-the-fly.
if (spf.config.get('advanced-response-type-json')) {
xhrOpts.responseType = 'json';
}
var xhr;
if (options.method == 'POST') {
xhr = spf.net.xhr.post(requestUrl, options.postData, xhrOpts);
Expand Down Expand Up @@ -293,8 +301,13 @@ spf.nav.request.handleChunkFromXHR_ = function(url, options, chunking,
*/
spf.nav.request.handleCompleteFromXHR_ = function(url, options, timing,
chunking, xhr) {
spf.debug.debug('nav.request.handleCompleteFromXHR_ ',
url, {'extra': chunking.extra, 'complete': xhr.responseText});
if (xhr.responseType == 'json') {
spf.debug.debug('nav.request.handleCompleteFromXHR_ ', url, xhr.response);
} else {
spf.debug.debug('nav.request.handleCompleteFromXHR_ ', url,
{'extra': chunking.extra, 'complete': xhr.responseText});
}

// Record the timing information from the XHR.
if (xhr['timing']) {
for (var t in xhr['timing']) {
Expand Down Expand Up @@ -347,22 +360,43 @@ spf.nav.request.handleCompleteFromXHR_ = function(url, options, timing,
xhr, '', true);
}
}
// Always attempt a full parse with error handling.
// A multipart response parsed on-the-fly via chunking may be invalid JSON if
// the response is truncated early. (If truncated just after a token,
// the chunking.extra value will be empty and no additional chunk parsing
// will be done, but the overall response will stil be invalid.)

var parts;
try {
var parsed = spf.nav.response.parse(xhr.responseText);
parts = parsed.parts;
} catch (err) {
spf.debug.debug(' JSON parse failed');
if (options.onError) {
options.onError(url, err);
if (xhr.responseType == 'json') {
// If using the JSON `responseType`, parsing is complete and no chunking
// has been handled on-the-fly.
if (!xhr.response) {
spf.debug.debug(' JSON parse failed');
if (options.onError) {
options.onError(url, new Error('JSON response parsing failed'));
}
return;
}
parts = (typeof xhr.response.length == 'number') ?
xhr.response :
[xhr.response];
if (spf.config.get('experimental-parse-extract')) {
parts = spf.nav.response.extract(parts);
}
} else {
// Otherwise, parsing may need to be done. Always attempt a full parse with
// error handling. A multipart response parsed on-the-fly via chunking may
// be invalid JSON if the response is truncated early. (If truncated just
// after a token, the chunking.extra value will be empty and no additional
// chunk parsing will be done, but the overall response will stil be
// invalid.)
try {
var parsed = spf.nav.response.parse(xhr.responseText);
parts = parsed.parts;
} catch (err) {
spf.debug.debug(' JSON parse failed');
if (options.onError) {
options.onError(url, err);
}
return;
}
return;
}

if (options.onPart && parts.length > 1) {
// Only execute callbacks for parts that have not already been processed.
// In case there is an edge case where some parts were parsed on-the-fly
Expand Down
13 changes: 13 additions & 0 deletions src/client/net/xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ goog.require('spf');
* have been received.
* - onTimeout: optional callback to execute if the XHR times out. Only called
* if a timeout is configured.
* - responseType: type to create from the XHR response.
* - timeoutMs: number of milliseconds after which the request will be timed
* out by the client. Default is to allow the browser to handle timeouts.
*
Expand All @@ -36,6 +37,7 @@ goog.require('spf');
* onDone: (function(XMLHttpRequest)|undefined),
* onHeaders: (function(XMLHttpRequest)|undefined),
* onTimeout: (function(XMLHttpRequest)|undefined),
* responseType: (string|undefined),
* timeoutMs: (number|undefined)
* }}
*/
Expand Down Expand Up @@ -141,6 +143,14 @@ spf.net.xhr.send = function(method, url, data, opt_options) {
}
};

// If requested, attempt to use the JSON `responseType` to optimize parsing.
// If the browser supports `responseType` but not the `"json"` type, then
// the property will remain unset.
// NOTE: This removes the ability to handle chunked responses on the fly.
if ('responseType' in xhr && options.responseType == 'json') {
xhr.responseType = 'json';
}

// For POST, default to `Content-Type: application/x-www-form-urlencoded`
// unless a custom header was given.
var addContentTypeFormUrlEncoded = (method == 'POST');
Expand Down Expand Up @@ -183,6 +193,9 @@ spf.net.xhr.send = function(method, url, data, opt_options) {
* @private
*/
spf.net.xhr.isChunked_ = function(xhr) {
if (xhr.responseType == 'json') {
return false;
}
// Determine whether to process chunks as they arrive.
// This is only possible with chunked transfer encoding.
// Note: handle Transfer-Encoding header values like:
Expand Down

0 comments on commit b6d08d5

Please sign in to comment.