Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #7295 (closed enhancement: fixed)

Opened 3 years ago

Last modified 3 years ago

[PATCH] lots of JSON goodness

Reported by: mislav Assigned to: tobie
Priority: normal Milestone: 1.x
Component: Prototype Version:
Severity: normal Keywords: fixedinbranch 1.6
Cc:

Description

Read the discussion on Spinoffs

The bottom line is that HTTP headers could be limited by size, and that we should use response body to transport large JSON load. JSON would be identified by "application/json" MIME-type.

If JSON is detected as the response type, then X-JSON header is ignored and the json parameter is populated by evaling the response body. Otherwise, everything else should stay the same.

Attachments

json_full.diff (7.2 kB) - added by Tobie on 01/25/07 03:48:18.
! tests contain ref to some php files which aren't included.
json_full.2.diff (7.5 kB) - added by Tobie on 01/26/07 06:25:10.
! tests contain ref to some php files which aren't included.
json.diff (11.1 kB) - added by Tobie on 02/12/07 05:20:24.
full cross-browser support through cloning the transport object when necessary
ajax_response_and_json.diff (15.7 kB) - added by Tobie on 02/21/07 05:01:39.
features a new Ajax.Response object

Change History

01/22/07 12:36:32 changed by mislav

  • status changed from new to assigned.

01/23/07 09:29:33 changed by mislav

Colin Mollenhour suggested that JSON from the response body should not override the message from X-JSON header for consistency. His suggestion is to write to the json property of the transport (xhr) object that is being passed to the callbacks as the first argument.

myCallback = function(transport, json) {
  json           // -> from X-JSON header
  transport.json // -> from response body
}

While the consistency is good, I can't help to think something is bad with this approach. I'm particularly worried about mixing the two up, which could happen even to experienced scripters. Also, I hope Safari or other browsers don't mind writing to the xhr instance

01/24/07 05:50:06 changed by Tobie

The only thing that bothers me about this solution (auto-eval depending on the mime-type) is that it prevents you from handling security issues (through the use of parseJSON), and implies evaluating the content of the response body twice if you want to do some filtering on the returned string before evaluating it (a common use is to convert dates to Date objects).

01/24/07 06:04:13 changed by Tobie

Come to think about it, shouldn't we just add a String.prototype.evalJSON and/or a String.prototype.parseJSON (if license issues permit it - it only says 'open-source')?

Seriously, isn't that enough:

myCallback = function(transport) {
  var data = transport.responseText.evalJSON();
}

evalJSON could just be something like this:

String.prototype.evalJSON = function() {
  return eval('(' + this + ')');
};

01/25/07 02:56:23 changed by Tobie

Note: John Wang posted an article on his blog a while ago about failing JSON requests in IE6 for large data sets. Comments confirm this and indicate similar issues with Firefox 1.5.0.7.

I've emailed Douglas Crockford and he confirmed that there is no license whatsoever attached to his JSON parser.

I've toyed with his implementation a little bit and I don't think we need something so powerfull. Here's what I suggest (it just adds an optional regex to check the string for valid JSON).

  String.prototype.evalJSON = function(sanitize){
    try {
      if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(this)))
        return eval('(' + this + ')');
    } catch (e) { return null }
  }

sanitize is an optional param which uses a regex to check and reject malicious attempts. I've left it off by default for speed considerations (You'll be able to see the benchmarks in the attached tests) but that decision is questionable.

I've added automatic eval for 'application/json' mime-type and added boolean evalJSON (if headers aren't set) and sanitizeJSON (for security freaks) options. It needs testing, and I don't really see how we can simulate HTTP headers in the testing environment we are using.

I'm having some weird issues, so I'll post this patch tomorrow.

01/25/07 03:46:45 changed by Tobie

  • summary changed from JSON could be received from response body to [PATCH] lots of JSON goodness.

OK, There you go.

There's some issues in IE which I haven't fixed yet (Not sure if the code is the issue or the testing).

The tests currently contain references to some php files to create the HTTP headers, The files are not included in the diff. (If anybody has a bright idea on how we could test this in a more agnostic way, please speak up!)

I've put the Ajax and String unit tests online: Ajax tests and string tests.

01/25/07 03:48:18 changed by Tobie

  • attachment json_full.diff added.

! tests contain ref to some php files which aren't included.

(follow-up: ↓ 8 ) 01/25/07 10:16:15 changed by mislav

About testing: I have 2 things in plan to enhance Ajax testing. First is the XHR mock object which is a highly-reflective class that simulates XHR; the second is teaching Webrick some new tricks. With these two we should be able to test absolutely every aspect of Ajax, we should be even able to fix latency issues!

I don't like the silent reject of malicious attempts. Couldn't an error be thrown or at least something? Also, the method returns 3 types of data: an object, null and undefined. I suggest only object or undefined.

(in reply to: ↑ 7 ) 01/25/07 18:00:54 changed by Tobie

I don't like the silent reject of malicious attempts. Couldn't an error be thrown or at least something? Also, the method returns 3 types of data: an object, null and undefined. I suggest only object or undefined.

good points.

I'm just unsure how to do that and keep evalJSON as a method of String without using a second try... catch block inside Ajax. I would like to keep evalJSON as a method of String though, because I think that's where it belongs.

Regarding what it should return, you are right, that's an omission, sorry. I'm wandering whether it should return null or undefined though. The previous implementation returned null so I was trying to keep it consistent.

01/26/07 06:25:10 changed by Tobie

  • attachment json_full.2.diff added.

! tests contain ref to some php files which aren't included.

01/26/07 06:30:46 changed by Tobie

OK, updated String.prototype.evalJSON to actually spit out some errors when the string is either badly formated or doesn't pass sanitization. Ajax now handles those exceptions through the onException callback.

All the tests now pass correctly in IE6, Firefox, Safari and Opera.

There's still that testing issue though...

01/29/07 21:11:18 changed by mislav

  • owner changed from mislav to tobie.
  • status changed from assigned to new.

02/11/07 19:59:38 changed by Tobie

OK... well, there's the news... and they're not good ones.

IE's transport object does not allow new properties to be set to it... nor does it allow existing properties to be modified.

There's an easy work-around (cloning the transport object if the response is JSON and the transport is not an instance of XMLHttpRequest), but I'm worried about the possible side-effects.

Any input on that particular issue would be welcomed.

02/12/07 05:19:22 changed by Tobie

  • priority changed from low to normal.

Hi again, here's a cross-browser diff that adds the above mentioned hack (cloning transport) to deal with IE6 (& IE7) 'features'.

Again, I'm a little uneasy with this proposal - so your input is welcome.

Is there anything we might want/need to access from the response object which cloning would make impossible?

02/12/07 05:20:24 changed by Tobie

  • attachment json.diff added.

full cross-browser support through cloning the transport object when necessary

02/12/07 13:05:40 changed by mislav

  • keywords changed from ajax json to ajax json discuss.

I feel uneasy thinking how the transport object gets cloned every time. I think we should keep the functionality of the native XHR instance intact and keep the extra functionality layer (properties, methods) on the Ajax.Request class.

(follow-up: ↓ 15 ) 02/13/07 05:21:18 changed by Tobie

Mislav,

The transport object only gets cloned if all the following conditions are met:

  • request state == 'Complete' (no cloning otherwise)
  • the browser is either IE6 or in IE7 with the XMLHttpRequest option turned off in browser preferences.
  • the server is sending JSON data (in which case cloning is certainly not going to be the performance bottleneck, eval is).

However, I'm not a 100% satisfied with this solution.

I think it's an alright fix for now, but we should seriously start thinking about developing a custom cross-browser response object (a suggestion Sam made to me a while ago).

I'm going to start looking more seriously into this asap.

(in reply to: ↑ 14 ) 02/20/07 08:06:25 changed by colinm

I too don't like having to clone the transport object in those certain cases even though I think that could still be a viable solution. However, I think I have a better solution which just dawned on me.. Why not eval the X-JSON header, then, if Content-type is 'application/json', eval it as well and Object.extend the X-JSON eval'ed response! This is more similar to your original suggestion, only rather than replacing it you would extend it.

This solution has the following advantages:

  • Backwards compatibility with X-JSON response being the second argument to onXXX
  • Won't break cases where bind was used to add additional arguments to onXXX
  • Doesn't require writing to the transport object (or cloning)
  • Keeps the API simple by using one object argument to handle both cases
  • Users can migrate their code to Content-type without changing the JS code
  • No data is lost unless properties in the Content-type object override properties in the X-JSON object. This is the only danger, but I think it is acceptable.

Colin

02/20/07 08:15:25 changed by Tobie

Hi Colin,

Thanks for the suggestion.

I'm in the middle of creating an Ajax.Response object which will - I hope - solve these issues and bring along much other niceties.

I hope to have it available soon.

02/21/07 05:01:39 changed by Tobie

  • attachment ajax_response_and_json.diff added.

features a new Ajax.Response object

06/14/07 00:21:37 changed by tobie

(In [7018]) * Add Ajax.Response object which supports the following methods: responseJSON, headerJSON, getHeader, getAllHeaders and handles browser discrepancies in the other response methods. Add sanitizeJSON, evalJS and evalJSON to Ajax.Request. References #8122, #8006, #7295. * Add an isRunningFromRake property to unit tests. * Add support for Opera browser in jstest.rb. * know issues: file: protocol doesn't work at all in IE7 (#8259), solving (#4267) is nightmarish.

06/14/07 00:53:38 changed by Tobie

  • keywords changed from ajax json discuss to fixedinbranch 1.6.

08/04/07 04:40:25 changed by sam

  • status changed from new to closed.
  • resolution set to fixed.

(In [7265]) prototype: Merge -r7016:HEAD from ../branches/ajax. Add Ajax.Response object which supports the following methods: responseJSON, headerJSON, getHeader, getAllHeaders and handles browser discrepancies in the other response methods. Add sanitizeJSON, evalJS and evalJSON to Ajax.Request. Closes #8122, #8006, #7295.