-
Notifications
You must be signed in to change notification settings - Fork 18
IO thoughts
This WIKI entry describes desired capabilities of a IO library to be used with #space / atlas. It is kept high-level on purpose as not to prescribe any particular implementation. Thanks to this approach we can evaluate existing IO implementations and 3rd party libraries to decide which one fits the bill.
The "ideal" library should be:
- Easy to use, modern API - this is subjective to a certain degree but at least typical cases should be obvious to write and shouldn't require excessive amounts of code.
- Feature rich / extensible - contain features needed to develop typical SPAs.
- Secure and security-friendly - the library itself should have built-in mechanism sanitizing typical vectors of attack as well as provide facilities to help developers write secure code for their applications
- Testable - that is, code written with the use of such library should be easy to mock and unit-test
- Lightweight - small code size and no 3rd party dependencies (polyffils are acceptable)
- Stable and of good quality - code for a library should be proven to work across the range of supported browsers
The subsequent sections go into more details of each criterion and describe ideal APIs.
Firstly, the proposed library should be (very) easy to use for a typical use-cases. To discuss an ideal API let's consider 2 common use-cases:
- sending a request without body-content (ex. GET, HEAD)
- sending a request with body-content (ex. POST, PUT)
The simplest GET call shouldn't require more than one line of code, ex.: io.get('http://site.com/folder')
. The returned value should be a promise that:
- resolves to an object representing a response (having fields like response status, header and the response content, etc.)
- is rejected with an error if an issued request fails.
Full example:
io.get('http://site.com/folder').then(function(response){
console.log(response.content);
});
A simple POST request should be a one-liner, too. Ex.:
io.post('http://site.com/folder', {foo: 'bar'}).then(function(response){
console.log(response.content);
});
Please notice that the proposed library is "smart enough" to properly convert a JavaScript object ({foo: 'bar'}
) into its string representation to be sent over HTTP. The most typical case is to convert a string to JSON but this should be configurable. The point here is that a user of a library shouldn't need to do low-level data-conversion and headers setting for typical scenarios (while still being free to fully control ongoing request when needed).
The incoming JSON response should be also automatically converted to a corresponding JavaScript object (depending on the response headers). Of course this mechanism should be configurable.
As with body content, the library should be able to serialize request parameters, provided as JavaScript objects, to their URL representation. For example, give this invocation:
io.get('http://site.com/folder', {params: {foo: 1, bar: false, baz: 'some string'}).then(function(response){
console.log(response.content);
});
we should issue a request targeting the following URL (notice all the boilerplate string gluing and params encoding):
http://site.com/folder?foo=1&bar=false&baz=some%20string
There are many features that could be considered for a IO library. As an example jQuery's $.ajax API has over 30 (!) different toggles in their settings section... Obviously the goal here is not to add every possible feature that the most popular libraries got but rather to make sure that we support features that are useful for typical web applications. The list of the features to support would be:
- configurable data conversion (both incoming and ongoing)
- request parameters serializers
- promise-based "around" interceptors - this IMO is the must as the number of use-cases that can be covered with interceptors without any need for the build in-library functionality. I wouldn't be surprised if 1/2 of all the jQuery settings could be covered by a dedicated interceptors (caching is a good example here).
There are several known attacks exploiting various vulnerabilities in the way JSON responses are parsed by browsers. The most "scary" and easy to understand is this one: http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/. Fortunately this affects only (very) old browsers but apparently GMail was a victim of this attack.
While the above attack is no longer exploitable there are other vectors of attacks that are still available: http://www.thespanner.co.uk/2011/05/30/json-hijacking/. To prevent those types of attacks a solution is to use a prefix to the JSON response but such prefix needs to be stripped by the IO library.
The other type of attack that can be prevent with a little help from the the IO library is the CSRF. In this case the IO library needs to read a special token set by a server in a cookie and put it in a request header. Since JavaScript can only read cookies from the same domain a server can be assured that a request with the embedded token is coming from the JavaScript code served from the same domain and not from the attackers site.
The above are obviously just the most prominent examples of the well-known attacks and their mitigation strategy. The point is that a IO library plays an important role in mitigating some of the attacks and there should be strong evidence that a selected IO library takes security-related topics seriously.
The proposed library should make it easy to mock XHR responses so people can write unit tests for classes relaying on IO. A developer writing a unit tests should be able to mock all the aspects of the XHR response (data, response code, headers).
The exact API for mocking to be worked out / evaluated.
Given a better and better support of XMLHttpRequest
in the modern browsers there is no reason for the selected library to be bulky. It is hard to set definitive size limits but probably anything larger than 500 LOC should be suspicious.
When it comes to cross-browser compatibly and modern APIs support (ex.: ES6 promises) it is preferable to select a library that would rely on polyfills so the overall library size gets "naturally reduced" over time.
It should go without saying that a selected library should have extensive test coverge. In case of selecting 3rd-party implementations we should evaluate number of issues open for a given library.
The selected library needs to support all the browsers supported by #space.
There are several items where I'm not clear on the recommendation(s) or even if a give feature should be supported at all:
- JSONP support - probably useful, need to watch out for potential security issues.
- CORS support - IE<10 is known to have half-broken CORS support. There are various work-arounds but those might substantially increase library's size. Maybe it should be offered as an extension?
- file:// protocol support - many browsers are blocking XHR calls for the file:// protocol anyway so probably not worth spending too much time on it.
- synchronous requests support - it is rather rare use case but not hard to implement, so why not.
- client-side cache - I would probably see it as a separate module where a cache could be plugged with a dedicated interceptor
- traditional forms and FormData
- file upload with progress notifications?
- AngularJS: https://docs.angularjs.org/api/ng/service/$http
- Dojo: http://livedocs.dojotoolkit.org/dojo/request/xhr
- jQuery: http://api.jquery.com/jquery.ajax/
- q-io: https://github.com/kriskowal/q-io#http
- WinJS.xhr: http://msdn.microsoft.com/en-us/library/windows/apps/br229787.aspx
The libraries below got APIs that are the closest one to the desired API outlined in this article, although none of them got "ideal" API:
- http://dojotoolkit.org/reference-guide/1.10/dojo/request/xhr.html#dojo-request-xhr
- https://github.com/nathanboktae/q-xhr
- https://github.com/alexyoung/turing.js/blob/master/turing.net.js
Just for the reference, here is the minimal promise-based wrapper over xhr: https://gist.github.com/matthewp/3099268