The IO family of modules provide a simple API for requesting resources over HTTP and HTTPS.
Getting Started
To include the source files for IO Utility and its dependencies, first load the YUI seed file if you haven't already loaded it.
<script src="http://yui.yahooapis.com/3.8.0/build/yui/yui-min.js"></script>
Next, create a new YUI instance for your application and populate it with the
modules you need by specifying them as arguments to the YUI().use()
method.
YUI will automatically load any dependencies required by the modules you
specify.
<script> // Create a new YUI instance and populate it with the required modules. YUI().use('io', function (Y) { // IO Utility is available and ready for use. Add implementation // code here. }); </script>
For more information on creating YUI instances and on the
use()
method, see the
documentation for the YUI Global Object.
A Simple Transaction
The io()
method on the YUI instance is the static method for making an HTTP request. This method accepts two arguments: the uri and the configuration object.
- uri: This parameter specifies the URI for the transaction
- configuration: This parameter is an object of keys and values of configurations specific to the transaction. Please see: The Configuration Object for more information on all available configuration properties.
io()
returns an object with the following members:
- id: This is the unique identifier of the transaction.
- abort(): Aborts the specific transaction.
- isInProgress(): Returns a boolean value indicating whether the transaction has completed.
This is an example GET transaction, handling the response at the earliest opportunity.
// Create a YUI instance using io-base module. YUI().use("io-base", function(Y) { var uri = "get.php?foo=bar"; // Define a function to handle the response data. function complete(id, o, args) { var id = id; // Transaction ID. var data = o.responseText; // Response data. var args = args[1]; // 'ipsum'. }; // Subscribe to event "io:complete", and pass an array // as an argument to the event handler "complete", since // "complete" is global. At this point in the transaction // lifecycle, success or failure is not yet known. Y.on('io:complete', complete, Y, ['lorem', 'ipsum']); // Make an HTTP request to 'get.php'. // NOTE: This transaction does not use a configuration object. var request = Y.io(uri); });
Using IO
The IO modules
IO's core and supplementary features are split into several modules, to allow greater flexibility in selecting the desired features.
Module | Description |
---|---|
io-base |
This is the base module, containing the basic functionality needed for making HTTP requests. |
io-xdr |
This module extends io-base, to add cross-domain request capabilities using alternate HTTP transports. |
io-form |
This module extends io-base, to add the ability to serialize HTML form data for POST transactions. |
io-upload-iframe |
This module extends io-base, to allow file uploads with an HTML form, using an iframe as the transaction transport. |
io-queue |
This module extends io-base, to provide a basic queue interface for io. |
io-nodejs |
Conditional module that provides a transport for Node.js. |
The Configuration Object
This object is the second argument of io()
. Its properties define transaction parameters and transaction response handling. The configuration object and all configuration properties are optional. The following table describes all configuration properties used by IO.
Property | Description |
---|---|
method (string) | Defines the HTTP method for the transaction. If this property is undefined or omitted, the default value is GET. |
data (string) | Data to be sent with the transaction, defined as a string of key-value pairs(e.g., "foo=bar&baz=boo" .) Data can also be defined as a single-level object(e.g., { 'foo':'bar', 'baz':'boo' }), which is then serialized into a key-value string. To use this capability, the sub-module querystring-stringify-simple , an optional dependency for io-base, must be declared in Y.use() . |
headers (object) | Specific HTTP headers and values to be sent with the transaction (e.g., { 'Content-Type': 'application/xml; charset=utf-8' } ). |
on (object) | This object can be used to register transaction event handlers for the set of supported io events, listed below. These events fire in addition to the global io events. All events handlers are optional.
|
context (object) | Defines the execution context of the event handler functions for the transaction. If undefined, a default value of Y(the YUI instance) is used. |
form (object) | This object instructs io to use the labels and values of the specified HTML form as data.
|
xdr (object) | Defines the transport to be used for cross-domain requests (e.g., { use:'flash' } ). The transaction will use the specified transport instead of the default transport, when specified in the transaction's configuration object.
|
sync (boolean) | When set to true , the transaction will be processed synchronous, and will halt all code execution until the transaction is complete. |
arguments (object) | Object, array, string, or number passed to all registered, transaction event handlers. This value is available as the second argument in the "start" and "end" event handlers; and, it is the third argument in the "complete", "success", and "failure" event handlers. |
timeout | This value, defined as milliseconds, is a time threshold for the transaction (e.g., { timeout: 2000 } ). When this limit is reached, and the transaction's Complete event has not yet fired, the transaction will be aborted. |
This is an example of a configuration object, with a set of properties defined.
/* * This is an example configuration object with all properties defined. * * method: This transaction will use HTTP POST. * data: "user=yahoo" is the POST data. * headers: Object of HTTP request headers for this transaction. The * first header defines "Content-Type" and the second is a * custom header. * on: Object of defined event handlers for "start", "complete", * and "end". These handlers are methods of an object * named "Dispatch". * context: Event handlers will execute in the proper object context, * so usage 'this' will reference Dispatch. * form: Object specifying the HTML form to be serialized into a key-value * string and sent as data; and, informing io to include disabled * HTML form fields as part of the data. If input type of "file" * is present, setting the upload property to "true" will create an * alternate transport, to submit the HTML form with the * selected files. * xdr: Instructs io to use the defined transport, in this case Flash, * to make a cross-domain request for this transaction. * arguments: Object of data, passed as an argument to the event * handlers. */ var cfg = { method: 'POST', data: 'user=yahoo', headers: { 'Content-Type': 'application/json', }, on: { start: Dispatch.start, complete: Dispatch.complete, end: Dispatch.end }, context: Dispatch, form: { id: formObject, useDisabled: true, upload: true }, xdr: { use: 'flash', dataType: 'xml' }, arguments: { start: 'foo', complete: 'bar', end: 'baz' } };
The Response Object
When a transaction, using the XHR object as a transport, is complete, the response data are passed as an object to the event handler.
Field | Description |
---|---|
status | The HTTP status code of the transaction. |
statusText | The HTTP status message. |
getResponseHeader(headername) | Returns the string value of the specified header label. |
getAllResponseHeaders() | All response HTTP headers are available as a string. Each key-value pair is delimited by "\n". |
responseText | The response data as a decoded string. |
responseXML | The response data as an XML document. |
NOTE: Transactions involving file upload or cross-domain requests, using alternate transports, will only populate the responseText
or responseXML
field. The HTTP status and response headers are either inaccessible or unavailable to these alternate transports.
Events
IO events provide access to state and data during a transaction's lifecycle. There are two aspects to io events: global and transaction. Global events are always fired by io for all transactions, and these events are accessible to all event subscribers. Transaction events are created and fired if they have handlers defined in the configuration object. Global events are identified by the io:eventname
pattern.
The following table describes the available io events and provides examples of how to subscribe to them globally:
Event | Description |
---|---|
io:start |
Fires when a request is made to a resource. The event handler's arguments signature is: function onStart(transactionid, arguments) { // transactionid : The transaction's ID. // arguments: Array ['foo','bar']. } // Subscribe to "io.start". Y.on('io:start', onStart, Y, { start: ['foo','bar']; ); |
io:complete |
Fires after "io:start", when the transaction is complete and response data are available. This is the earliest opportunity to access any response data. The event handler's arguments signature is: function onComplete(transactionid, response, arguments) { // transactionid : The transaction's ID. // response: The response object. // arguments: Object containing an array { complete: ['foo', 'bar'] }. } // Subscribe to "io.complete". Y.on('io:complete', onComplete, Y, { complete: ['foo', 'bar'] } ); |
io:success |
Fires after the "complete" event, when the response HTTP status resolves to 2xx. The event handler's arguments signature is: function onSuccess(transactionid, response, arguments) { // transactionid : The transaction's ID. // response: The response object. // arguments: Boolean value "true". } // Subscribe to "io.success". Y.on('io:success', onSuccess, Y, true); |
io:failure |
Fires after the "complete" event, when the response HTTP status resolves to 4xx. 5xx, undefined, or a non-standard HTTP status. This event also includes 'abort' and 'timeout' conditions. The event handler's arguments signature is: function onFailure(transactionid, response, arguments) { // transactionid : The transaction's ID. // response: The response object. Only status and // statusText are populated when the // transaction is terminated due to abort // or timeout. The status will read // 0, and statusText will return "timeout" // or "abort" depending on the mode of // termination. // arguments: String "Transaction Failed". } // Subscribe to "io.failure". Y.on('io:failure', onFailure, Y, 'Transaction Failed'); |
io:end |
Fires at the conclusion of a transaction, after "success" or "failure" has been determined.. The event handler's arguments signature is: function onEnd(transactionid, arguments) { // transactionid : The transaction's ID. // arguments: Number 0. } // Subscribe to "io.end". Y.on('io:end', onEnd, Y, 0); |
io:xdrReady | Fires when the flash XDR transport is ready for use. This event only fires once, when the transport initialization is complete. |
The following example demonstrates an IO transaction with an event handler subscribed to "io:complete".
// Create a YUI instance using io module. YUI().use("io-base", function(Y) { /* * Create a function as the event handler for the event "io:complete". * * The function will receive the following arguments: * - The ID of the transaction * - Object containing the response data. * - Argument one defined when subscribing to the event(e.g., "foo"). * - Argument two defined when subscribing to the event(e.g., "bar"). */ function onComplete(transactionId, responseObject, arg1, arg2) { /* * The argument 'responseObject' is the response object. Its * properties are: * - status * - statusText * - getResponseHeader(headerName) * - getAllResponseHeaders * - responseText * - responseXML * * NOTE: In an XDR transaction, only the responseText or the responseXML property is defined. */ }; /* * Subscribe to the event "io:complete", using Y.on. * * - 'io:complete' : Subscribe to this io event. * - onComplete : The event handler to be subscribed to 'io:complete'. * - Y : The execution context of the event handler, in this case, the YUI sandbox. * since the doComplete is defined as a global function. * - 'foo' : The first argument received by the event handler. * - 'bar' : The second argument received by the event handler. * Additional arguments can be defined, as desired. */ Y.on('io:complete', onComplete, Y, "foo", "bar"); // Starts the transaction. var request = Y.io(uri); });
Synchronous Transactions
For same-domain requests, YUI io can be instructed to send a synchronous request, which will halt all script execution until the transaction is complete. When the transaction is complete, the response data are directly accessible through the object returned by Y.io(), and the data are also accessible through all io events. When making synchronous requests, abort() and isInProgress() are not available.
// Create a YUI instance using the io-base module. YUI().use("io-base", function(Y) { var cfg, request; // Create a configuration object for the synchronous transaction. cfg = { sync: true, arguments: { 'foo' : 'bar' } }; /* * var request will contain the following fields, when the * transaction is complete: * - id * - status * - statusText * - getResponseHeader() * - getAllResponseHeaders() * - responseText * - responseXML * - arguments */ request = Y.io(uri, cfg); });
Cross-Domain Transactions
By default, io
uses the XMLHttpRequest object as the transport for HTTP transactions. It can also be configured to use an alternate transport to make cross-domain, HTTP transactions. Currently, io can make use of Flash as an alternate transport. To prepare io for Flash-based, cross-domain transactions, the transport io.swf
must be deployed and accessible to YUI io. (The file "io.swf" can be found in YUI io's build directory in the YUI3 download at: http://yuilibrary.com/downloads/.) For each transaction, the configuration object's xdr
object must be defined as { use: 'flash' }
so io will use the designated transport instead of using the default XMLHttpRequest transport.
As io.swf
is written in ActionScript 3, Flash Player 9 or better is required (version 9.0.124 or better is recommended). Additionally, a cross-domain policy file must be deployed at the resource to grant the client access to the remote domain. A cross-domain request will not be successful without this policy file hosted at the resource. The following example file grants permissive access to the host from all requests, but the host will only accept custom HTTP headers originating from yahoo.com
.
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <allow-access-from domain="*"/> <allow-http-request-headers-from domain="*.yahoo.com" headers="*"/> </cross-domain-policy>
For more information on cross-domain policy file specifications, see the following articles at Adobe Developer Connection.
The following example demonstrates a cross-domain transaction, starting with the initialization of the XDR transport and subscribing to three global io events.
// Create a YUI instance using the io cross-domain submodule YUI().use("io-xdr", function(Y) { // Create a configuration object with the src property defined, // src: The path to "io.swf" relative to the HTML file. var xdrCfg = { src:'io.swf' }; // Initialize the cross-domain transport Y.io.transport(xdrCfg); // Define the configurations to be used for each transaciton.. var cfg = { xdr: { use: 'flash'}, // Instruct io to use the flash XDR transport. data: 'foo=bar&baz=boo', // Key-value string of data. timeout: 3000, // Abort the transaction, if it is still pending, after 3000ms. // An object passed, as an argument, to the event handlers. arguments: { start: 'foo', complete: 'bar', end: 'baz' } }; /* * GlobalEventHandler is an example object that encapsulates * event handlers for "io:start", "io:complete", and "io:end". * * start( ) // Event handler for "io:start" * success( ) // Event handler for "io:complete". * end( ) // Event handler for "io:end". */ var GlobalEventHandler = { start: function(id, args) { var args = args.start // 'foo' }, success: function(id, o, args) { var args = args.complete; // 'bar' var data = o.responseText; var xml = o.responseXML; }, end: function(id, args) { var args = args.end // args = 'baz' } }; function callIo() { //example URI. var uri = "http://pipes.yahooapis.com/", // Start the transaction request = Y.io(uri, cfg); } // Subscribe GlobalEventHandler.start to event "io:start". Y.on('io:start', GlobalEventHandler.start, Y); // Subscribe GlobalEventHandler.complete to event "io:complete". Y.on('io:success', GlobalEventHandler.complete, Y); // Subscribe GlobalEventHandler to event "io:end". Y.on('io:end', GlobalEventHandler.end, Y); // Once the Flash transport is initialized and ready for use, // it will fire the "io:xdrReady" event. Subscribe to it, // to automatically call function "callIo" when the transport // is ready.. Y.on('io:xdrReady', callIo, Y); });
Note: Cross-domain transactions do not fire the global io:complete
event and the transaction-specific complete
event, when using the IE XDomainRequest or the Flash transport. All other events in the transaction lifecycle are fired.
A subset of A-grade browsers are capable of making cross-domain requests, using XMLHttpRequest, requiring specific access control headers be served from the resource. To use this feature, the xdr configuration must be defined with: { use: 'native' }
. IO will try to resolve the request using the native transport, and it will fall back to the Flash transport if the initial attempt throws an exception due to the browser lacking native support.
NOTE: For native cross-domain requests to work, the resource must respond with the "Access-Control-Allow-Origin" header with a value permitting the client to make the request. In the absence of this HTTP response header, the transaction will always fail. Please see the following articles for more information on this topic.
- Mozilla Developer Center: HTTP Access Control article.
- MSDN: Cross-Domain Security article.
- W3C: Access Control Working Draft,
Serializing HTML Form as Data
IO can serialize HTML form fields into a string of UTF-8 encoded, name-value pairs. If the transaction is HTTP GET, the data are appended to the URI as a querystring. If the transaction if HTTP POST, the data will be the POST message.
// Create a YUI instance using the io-form sub-module. YUI().use("io-form", function(Y) { // Create a configuration object for the file upload transaction. // The form configuration should include two defined properties: // id: This can be the ID or an object reference to the HTML form. // useDisabled: Set this property to "true" to include disabled // HTML form fields, as part of the data. By // default, disabled fields are excluded from the // serialization. // The HTML form data are sent as a UTF-8 encoded key-value string. var cfg = { method: 'POST', form: { id: formObject, useDisabled: true } }; // Define a function to handle the response data. function complete(id, o, args) { var id = id; // Transaction ID. var data = o.responseText; // Response data. var args = args[1]; // 'ipsum'. }; // Subscribe to event "io:complete", and pass an array // as an argument to the event handler "complete". Y.on('io:complete', complete, Y, { 'foo':'bar' }); // Start the transaction. var request = Y.io(uri, cfg); });
Uploading Files in an HTML Form
The default XHR transport, used in IO, cannot upload HTML form data that include elements of type="file". In this situation, IO will use an alternate transport -- an iframe -- to facilitate the transaction. The response data must be one of the following content types: "text/html", "text/plain", "text/xml". The following example shows how to configure a transaction involving file upload:
/* * This example demonstrates how to configure io to upload files * from an HTML form. This example uses the global events: * "io:start" and "io:complete" to handle the transaction and * response. Transaction events can be defined and fired, as well, * in the configuration object; but, they are not used in this * example. */ // Create a YUI instance using the io-upload-iframe sub-module. YUI().use("io-upload-iframe", function(Y) { // Create a configuration object for the file upload transaction. // The form configuration should include two defined properties: // id: This can be the ID or an object reference to the HTML form // containing the input type="file" elements. // upload: Set this property to "true" to indicate this is a file // upload transaction. var cfg = { method: 'POST', form: { id: formObject, upload: true } }; // Define a function to handle the start of a transaction function start(id, args) { var id = id; // Transaction ID. var args = args.foo; // 'bar' } // Define a function to handle the response data. function complete(id, o, args) { var id = id; // Transaction ID. var data = o.responseText; // Response data. var args = args[1]; // 'ipsum'. }; // Subscribe to event "io:start", and pass an object // as an argument to the event handler "start". Y.on('io:start', start, Y, { 'foo':'bar' }); // Subscribe to event "io:complete", and pass an array // as an argument to the event handler "complete". Y.on('io:complete', complete, Y, ['lorem', 'ipsum']); // Start the transaction. var request = Y.io(uri, cfg); });
When performing a file upload, a subset of global and transaction events will be fired. Specifically, these are:
- Start
- Complete
- End
Success and Failure events are not processed and fired because the iframe transport does not provide access to the HTTP status and response headers, to reliably determine those conditions.
Setting HTTP Headers
IO can be configure to send default, user-defined HTTP Headers for all transactions, in addition to any headers defined in the configuration object. Headers can be set or removed as needed. The following example shows how to set and how to delete default headers in IO:
YUI().use("io-base", function(Y) { // Set a new default HTTP header. Y.io.header('Content-Type', 'application/json'); // To remove an existing header, use the same method, but omit the value. Y.io.header('Content-Type'); });
Custom HTTP headers may or may not be sent in cross-domain requests. This is may be due to limitations of the transport, or specific "Access-Control" headers requirement.
Queue
IO's queue module provides FIFO transaction response while keeping each transaction asynchronous and non-blocking. Specifically, transactions are handled -- by global or transaction event handlers -- in the order they are sent, regardless of actual server response order. Transactions can be promoted to the front of the queue, or they can be purged from the queue, as well.
Field | Description |
---|---|
queue(uri, configuration) | Method signature is identical to io, but returns the id of the transaction. |
queue.start() | Activates the queue, and begins processing transactions in the queue. This is the default state of the queue. |
queue.stop() | Deactivates the queue. Transactions sent to queue() will be stored until the queue is re-started. | queue.promote(id) | Moves the specified transaction stored in the queue to the head of the queue. |
queue.remove(id) | Deletes the specified transaction stored in the queue. |
// Create a YUI instance using the io queue sub-module. YUI().use("io-queue", function(Y) { // Stop the queue so transactions can be stored. Y.io.queue.stop(); // Send four transactions into the queue. Each response will arrive // in synchronous order. var task0 = Y.io.queue(uri); var task1 = Y.io.queue(uri); var task2 = Y.io.queue(uri); var task3 = Y.io.queue(uri); // Promote task2 to the top of the queue. Y.io.queue.promote(task2); // Remove task3 from the queue. Y.io.queue.remove(task3); // Re-start the queue. // Transactions are sent in the following order: task2, task0, task 1. // Transaction callbacks, if provided, will be processed in the same // sequence: task2, task0, task1, regardless of actual response order. Y.io.queue.start(); });
Instantiating IO
As of 3.4.0, IO is instantiatiable. An IO instance avails its public and private fields, allowing for customizations as needed.
// Create a new instance of IO. var io = new Y.IO(); // Send a request using the new Y.IO instance. // This is analogous to the static method // Y.io() io.send(uri, configuration);
In addition to being instantiable, IO is now an EventTarget, and IO's global events can be configured at instantiation time.
// This simple example creates a new instance of IO and passes // Custom Event configurations that instructs IO to emit // Event Facades for all its events, and allow the events to // bubble to other registered event targets, if any. var io = new Y.IO({ emitFacade: true, // Event handlers will receive an Event Facade. bubbles: true, // Events will bubble to registered event targets. });
If IO is configured to emit Event Facades, each event handler will receive the Event Facade as the argument.
// This is the event handler using Event Facades. var configuration = { on: { complete: function(o) { /* * o is the event facade, and contains the following fields: * - o.id is the transaction id. * - o.data is the XMLHttpRequest (or other transport) object. * - o.arguments is the user-defined arguments, if any. * - o.cfg is the configuration object used for this transaction. * * These fields are in addition to the Event Facade's fields. */ } } }; // For comparison, this is the regular event handler, when // not emitting Event Facades as described in the previous // sections on "The Response Object" and "Events." var configuration = { on: { complete: function(id, xhr, arguments) { // id is the transaction id. // xhr is the XMLHttpRequest object. // arguments is the user-defined arguments, if any. } } };
Using IO in Node.js
YUI uses Mikeal Roger's Request library under the hood to provide our IO transport layer in Node.js.
The io-base
module works out of the box and mimic's it's browser counterpart as much as it can.
Note: You can not use the io
module on the server, the io
module
contains the io-form
and the io-upload-frame
modules which both rely on a working DOM
to be available. The io-base
module, however, has no requirement on a DOM.
Simple Example
var Y = require('yui/io-base'); Y.io('https://github.com/api/v2/json/user/show/yui', { on: { complete: function(id, e) { var json = JSON.parse(e.responseText); console.log(json); } } });
Since the request
module is bundled with YUI, we expose that inside YUI so you can also use it.
We alias request on the IO
object as Y.IO.request
, so now you can use it like this:
fs.createReadStream('file.json').pipe(Y.IO.request.put('http://mysite.com/obj.json')); Y.IO.request.get('http://google.com/img.png').pipe(Y.IO.request.put('http://mysite.com/img.png'));
In future versions of YUI, we will support file uploads via our File API that will use this under the hood as well.
See the YUI on Node.js example for IO for more information about
using IO
on Node.js.
Security Bulletin
A security vulnerability exists in the XDR transport io.swf
when using the io-xdr
sub-module to make cross-domain requests. This vulnerability allows third-party sites to load io.swf
from a remote domain and issue HTTP requests with the SWF's domain credentials. Please examine the following use cases, and, if applicable to you, please follow the recommended actions to close this exploit.
- You currently host
io.swf
from YUI 3.1.0, 3.1.1, or 3.2.0pr1, and your application uses the io-xdr sub-module to make cross-domain requests. Solution: replace the version ofio.swf
withio.swf
from YUI 3.1.2. - Your application uses the
io-xdr
sub-module from version YUI 3.1.0, 3.1.1, and you explicitly loadio.swf
fromhttp://yui.yahooapis.com/version/build/io.swf
(whereversion
matches the affected YUI versions). Solution: modify your application'scrossdomain.xml
so thatallow-access-from domain=
does not allow access from yui.yahooapis.com. Download YUI 3.1.2 and deployio.swf
on your application's domain instead of loading it from yui.yahooapis.com. - Your application uses the
io-xdr
sub-module from version YUI 3.1.0, 3.1.1, and you explicitly loadio.swf
from a disparate domain, and you have a crossdomain policy file allowing access from the SWF's domain. Solution: modify your application's crossdomain.xml so thatallow-access-from domain=
does not allow access from the domain servingio.swf
. Download YUI 3.1.2 and deployio.swf
on your application's domain instead of loading it from a remote domain. - If you use
io.swf
from YUI 3.0.0 you are not affected by this vulnerability.
Beginning with YUI 3.1.2, io.swf
will no longer be accessible from yui.yahooapis.com. You will be required to host and serve io.swf
, if you wish to employ it as an XDR transport.
Known Issues
- Multiple HTML Submit buttons, in an HTML form, are not supported at this time.