/* * Oni Apollo 'http' module * Functions for performing HTTP requests and working with URLs * * Part of the Oni Apollo Standard Module Library * Version: '0.13.2' * http://onilabs.com/apollo * * (c) 2010-2011 Oni Labs, http://onilabs.com * * This file is licensed under the terms of the MIT License: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ /** @module http @summary Functions for performing HTTP requests and working with URLs. */ var sys = require('sjs:apollo-sys'); //---------------------------------------------------------------------- // url functions /** @function constructQueryString @summary Build a URL query string. @param {QUERYHASHARR} [hashes] Object(s) with key/value pairs. See below for full syntax. @return {String} @desc ###Notes: *hashes* can be a simple object with key/values or an arbitrarily nested array of (arrays of) key/value objects. Instead of passing an array, the individual array components can also be passed as individual parameters to [::constructURL]. E.g. the following two calls are equivalent: http.constructQueryString({a:1}, {b:1}); // is equivalent to http.constructQueryString([{a:1}, {b:1}]); If the value in a key/value pair is an array [a,b,c], then a key=value query will be encoded for each of the array elements. ###Examples: http.constructQueryString({a:1}, {b:1}); // -> "a=1&b=1" http.constructQueryString({a:1,b:"foo&bar"}); // -> "a=1&b=foo%26bar" http.constructQueryString([[null,[{a:1,b:['x','y']},{c:3}],[[]]]]); // -> "a=1&b=x&b=y&c=3"` Full syntax for *hashes*: QUERYHASHARR : arbitraily nested array of [ QUERYHASH* ] QUERYHASH : { QUERY* } | undefined QUERY : SIMPLE_QUERY | MULTI_QUERY SIMPLE_QUERY : "field" : "value" MULTI_QUERY : "field" : [ "value1", ... ] */ exports.constructQueryString = sys.constructQueryString; /** @function constructURL @summary Build a URL string. @param {URLSPEC} [urlspec] Base string and optional path strings and query hashes. See below for full syntax. @return {String} @desc ###Notes: *urlspec* can be a simple string or an (arbitrarily nested) array composed of one base strings, and optionally a number of path strings and/or a number of QUERYHASH objects (as accepted by [::constructQueryString]). Instead of passing an array, the individual array components can also be passed as individual parameters to [::constructURL]. E.g. the following two calls are equivalent: http.constructURL("foo", "bar", "baz"); // is equivalent to http.constructURL(["foo", "bar", "baz"]); Base & path strings will be concatenated in such a way that there is exactly one '/' character between each component. The base string may contain a '?' character. In this case no path strings should be given, but query hashes can be specified and will be appended correctly (using '&' instead of '?'). ###Examples: http.constructURL("foo.txt"); // -> "foo.txt" http.constructURL("foo", "bar", "foo.txt"); // -> "foo/bar/foo.txt" http.constructURL("foo/", "/bar/"); // -> "foo/bar/" http.constructURL("foo?a=b"); // -> "foo?a=b" http.constructURL("foo?a=b", {b:1}); // -> "foo?a=b&b=1" http.constructURL("foo?a=b", {b:[1,2]}); // -> "foo?a=b&b=1&b=2" http.constructURL("foo?a=b", [{b:[1,2]}]); // -> "foo?a=b&b=1&b=2" http.constructURL(["http://foo", {bar:"x", zz:"w"}, {foo:[1,2,3]}]); // -> "http://foo?bar=x&zz=w&foo=1&foo=2&foo=3" http.constructURL([["http://foo", {bar:"x", zz:"w"}], [{foo:[1,2,3]}]]); // -> "http://foo?bar=x&zz=w&foo=1&foo=2&foo=3" Full syntax for *urlspec*: URLSPEC : arbitrarily nested array [ BASESTR , PATHSTR* , QUERYHASH* ] BASESTR : string with url base (e.g. "http://onilabs.com/foo") PATHSTR : string with directory component QUERYHASH : { QUERY* } | undefined QUERY : SIMPLE_QUERY | MULTI_QUERY SIMPLE_QUERY : "field" : "value" MULTI_QUERY : "field" : [ "value1", ... ] */ exports.constructURL = sys.constructURL; /** @function parseURL @summary Parses the given URL into components. @param {String} [url] URL to parse. @return {Object} Parsed URL as described at (using 'strict' mode). @desc Uses the parseuri function from . */ exports.parseURL = sys.parseURL; /** @function isSameOrigin @summary Checks if the given URLs have matching authority parts. @param {String} [url1] First URL. @param {String} [url2] Second URL. @desc If either URL is missing an authority part (i.e. it is a relative URL), the function returns true as well. */ exports.isSameOrigin = sys.isSameOrigin; /** @function canonicalizeURL @summary Convert relative to absolute URLs and collapse '.' and '..' path components. @param {String} [url] URL to canonicalize. @param {optional String} [base] URL which will be taken as a base if *url* is relative. @return {String} Canonicalized URL. @desc ###Examples: http.canonicalizeURL("/foo/bar.txt", "http://a.b/c/d/baz.txt"); // --> "http://a.b/foo/bar.txt" http.canonicalizeURL("foo/bar.txt", "http://a.b/c/d/baz.txt"); // --> "http://a.b/c/d/foo/bar.txt" http.canonicalizeURL("././foo/./bar.txt", "http://a.b/c/d/"); // --> "http://a.b/c/d/foo/bar.txt" http.canonicalizeURL(".././foo/../bar.txt", "http://a.b/c/d/"); // --> "http://a.b/c/bar.txt" */ exports.canonicalizeURL = sys.canonicalizeURL; //---------------------------------------------------------------------- exports.xhr = function() { throw "http.xhr() is obsolete. Please use http.request()"; }; exports.xml = function() { throw "http.xml() is obsolete."; }; /** @function getXDomainCaps @summary Returns the cross-domain capabilities of the host environment ('CORS'|'none'|'any') @return {String} @desc See also [::request]. */ exports.getXDomainCaps = sys.getXDomainCaps; /** @function request @summary Performs an [XMLHttpRequest](https://developer.mozilla.org/en/XMLHttpRequest)-like HTTP request. @param {URLSPEC} [url] Request URL (in the same format as accepted by [::constructURL]) @param {optional Object} [settings] Hash of settings (or array of hashes) @return {String} @setting {String} [method="GET"] Request method. @setting {QUERYHASHARR} [query] Additional query hash(es) to append to url. Accepts same format as [::constructQueryString]. @setting {String} [body] Request body. @setting {Object} [headers] Hash of additional request headers. @setting {String} [username] Username for authentication. @setting {String} [password] Password for authentication. @setting {String} [mime] Override mime type. @setting {Boolean} [throwing=true] Throw exception on error. @desc ### Limitations: This method exposes similar functionality to that provided by browsers' [XMLHttpRequest](https://developer.mozilla.org/en/XMLHttpRequest), and as such has a number of limitations: * It is only safe for transferring textual data (UTF8-encoded by default). * Redirects will automatically be followed, and there is no way to discover the value of the final URL in a redirection chain. * Cross-origin restrictions might apply; see below. ### Cross-site requests: The success of cross-domain requests depends on the cross-domain capabilities of the host environment, see [::getXDomainCaps]. If this function returns "CORS" then success of cross-domain requests depends on whether the server allows the access (see ). In the xbrowser host environment, the standard XMLHttpRequest can handle cross-domain requests on compatible browsers (any recent Chrome, Safari, Firefox). On IE8+, [::request] will automatically fall back to using MS's XDomainRequest object for cross-site requests. In the nodejs host environment, we can always perform cross-domain requests ### Request failure: If the request is unsuccessful, and the call is configured to throw exceptions (setting {"throwing":true}; the default), an exception will be thrown which has a 'status' member set to the request status. If the call is configured to not throw, an empty string will be returned. ### Example: try { alert(http.request("foo.txt")); } catch (e) { alert("Error! Status="+e.status); } */ exports.request = sys.request; /** @function get @summary Perform a HTTP GET request and return the response text. @param {URLSPEC} [url] Request URL (in the same format as accepted by [::constructURL]) @param {optional Object} [settings] Hash of settings (or array of hashes) as accepted by [::request]. @return {String} @shortcut request @desc An alias for `http.request(url,settings)`. ### Example: console.log( require("apollo:http").get("data.txt") ); ### Example: timeout var http = require("apollo:http"); waitfor { var data = http.get("data.txt"); } or { hold(1000); } if (!data) { throw "Server too slow..."; } */ exports.get = exports.request; /** @function post @summary Perform a HTTP POST request and return the response text. @param {URLSPEC} [url] Request URL (in the same format as accepted by [::constructURL]) @param {String|null} [body] Request body. @param {optional Object} [settings] Hash of settings (or array of hashes) as accepted by [::request]. @return {String} @shortcut request @desc ### Example: var http = require("apollo:http"); var response = http.post("/service", "some raw data"); console.log("server replied:", response); ### Example: posting data in the url, not the body var http = require("apollo:http"); var rv = http.post("/service", null, { query: { name: "ford", lastname: "prefect" } }); // sends an HTTP POST to /service // with payload: name=ford&lastname=prefect */ exports.post = function(url, body, settings) { return sys.request(url, [{method:"POST", body:body}, settings]); }; /** @function json @summary Perform a HTTP GET request and parse the response text as a JSON object. @param {URLSPEC} [url] Request URL (in the same format as accepted by [::constructURL]) @param {optional Object} [settings] Hash of settings (or array of hashes) as accepted by [::request]. @shortcut get @return {Object} @desc ### Example: var http = require("apollo:http"); var animals = http.json("/animals.php?type=cats").animals; for (var i = 0, cat; cat = animals[i]; i++) { console.log(cat.name); } */ // helper taken from jquery function parseJSON(data) { if (typeof data !== "string" || !data) { return null; } // Make sure leading/trailing whitespace is removed (IE can't handle it) data = data.replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g, ""); // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:[\"\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") .replace(/\"[^\"\\\n\r]*\"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { // Try to use the native JSON parser first var global = sys.getGlobal(); return global.JSON && global.JSON.parse ? global.JSON.parse(data) : (new Function("return " + data))(); } else throw "Invalid JSON"; }; exports.json = function(/*url, settings*/) { return parseJSON(exports.get.apply(this, arguments)); }; //---------------------------------------------------------------------- // jsonp /** @function jsonp @summary Perform a cross-domain capable JSONP-style request. @param {URLSPEC} [url] Request URL (in the same format as accepted by [::constructURL]) @param {optional Object} [settings] Hash of settings (or array of hashes) @return {Object} @setting {QUERYHASHARR} [query] Additional query hash(es) to append to url. Accepts same format as [::constructQueryString]. @setting {String} [cbfield="callback"] Name of JSONP callback field in query string. @setting {String} [forcecb] Force the name of the callback to the given string. @desc ### Example: var http = require("apollo:http"); var url = "http://api.flickr.com/services/feeds/photos_public.gne?" + "tags=cat&tagmode=any&format=json"; var data = http.jsonp(url, {cbfield:"jsoncallback"}); for (var i = 0, item; item = data.items[i]; i++) { c.log("src=", item.media.m); }; */ exports.jsonp = sys.jsonp;