http-client.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /**
  2. * HTTP Client using the JSGI standard objects
  3. */
  4. ({define:typeof define!="undefined"?define:function(deps, factory){module.exports = factory.apply(this, deps.slice(0,2).map(require).concat(require))}}).
  5. define(["./promise", "./process", "require"],
  6. function(promise, process, require){
  7. var defer = promise.defer,
  8. when = promise.when,
  9. print = process.print,
  10. request;
  11. if(typeof XMLHttpRequest === "undefined"){
  12. request = require("./engines/node/http-client").request;
  13. }
  14. else{
  15. request = function(request){
  16. var
  17. scheme = request.scheme || "http",
  18. serverName = request.serverName || request.hostname || "localhost",
  19. serverPort = request.serverPort || request.port || 80,
  20. xhr = new XMLHttpRequest();
  21. xhr.open(request.method || "GET",
  22. request.url || // allow request.url to shortcut creating a URL from all the various parts
  23. (scheme + "://" + serverName + ":" + serverPort + request.pathInfo + (request.queryString ? '?' + request.queryString : '')), true);
  24. for(var i in request.headers){
  25. xhr.setRequestHeader(i, request.headers[i]);
  26. }
  27. var deferred = defer(),
  28. response,
  29. lastUpdate;
  30. xhr.onreadystatechange = function(){
  31. if(xhr.readyState == 4 || xhr.readyState == 3){
  32. if(!response){
  33. response = {
  34. body: [xhr.responseText],
  35. status: xhr.status,
  36. headers: {}
  37. };
  38. lastUpdate = xhr.responseText.length;
  39. var headers = xhr.getAllResponseHeaders();
  40. headers = headers.split(/\n/);
  41. for(var i = 0; i < headers.length; i++){
  42. var nameValue = headers[i].split(": ", 2);
  43. if(nameValue){
  44. var name = nameValue[0];
  45. response.headers[name.toLowerCase()] = xhr.getResponseHeader(name);
  46. }
  47. }
  48. }
  49. else{
  50. response.body = [xhr.responseText];
  51. lastUpdate = xhr.responseText.length;
  52. }
  53. if(xhr.readyState == 4){
  54. deferred.resolve(response);
  55. }
  56. else{
  57. deferred.progress(response);
  58. }
  59. }
  60. }
  61. xhr.send(request.body && request.body.toString());
  62. return deferred.promise;
  63. }
  64. }
  65. // for back-compat
  66. request.request = request;
  67. // FIXME this way too naive
  68. var isRedirect = request.isRedirect = function(response){
  69. return [301,302,303,307].indexOf(response.status) >= 0;
  70. }
  71. request.Redirect = function(nextApp, maxRedirects){
  72. maxRedirects = maxRedirects || 10;
  73. return function(request){
  74. var remaining = maxRedirects,
  75. deferred = defer();
  76. function next(){
  77. when(nextApp(request), function(response) {
  78. if(remaining--){
  79. // TODO cache safe redirects when cache is added
  80. if(isRedirect(response)){
  81. request.url = response.headers.location;
  82. next();
  83. }else{
  84. deferred.resolve(response);
  85. }
  86. }else{
  87. if(isRedirect(response)) print("Maximum redirects reached")
  88. deferred.resolve(response);
  89. }
  90. }, deferred.reject);
  91. }
  92. next();
  93. return deferred.promise;
  94. }
  95. }
  96. request.CookieJar = function(nextApp) {
  97. var domainToCookies = {};
  98. return function(req) {
  99. var
  100. querystring = require("./querystring"),
  101. parseUri = require("./util/uri").parseUri;
  102. if (req.url) {
  103. var url = parseUri(req.url);
  104. req.hostname = url.host;
  105. req.port = url.port;
  106. req.pathInfo = url.path;
  107. req.authority = url.authority;
  108. }
  109. if (req.hostname && domainToCookies[req.hostname]) {
  110. var cookieString = "";
  111. req.headers["Cookie"] = domainToCookies[req.hostname].map(function(cookie) {
  112. return querystring.toQueryString(cookie);
  113. }).join(',');
  114. }
  115. return when(nextApp(req), function(response) {
  116. var cookies;
  117. if (response.headers["set-cookie"]) {
  118. var path, domain = req.hostname + (req.port ? ":"+req.port : "");
  119. if (Array.isArray(response.headers["set-cookie"])) {
  120. cookies = [];
  121. response.headers["set-cookie"].forEach(function(cookie) {
  122. cookie = querystring.parseQuery(cookie, /[;,]/g);
  123. if (cookie.Version !== undefined) { delete cookie.Version; }
  124. if (cookie.Path !== undefined) { path = cookie.Path; delete cookie.Path; }
  125. if (cookie.HttpOnly !== undefined) { delete cookie.HttpOnly; }
  126. if (cookie.Domain !== undefined) { domain = cookie.Domain; delete cookie.Domain; }
  127. cookies.push(cookie);
  128. });
  129. } else {
  130. cookies = querystring.parseQuery(response.headers["set-cookie"], /[;,]/g);
  131. if (cookies.Version !== undefined) { delete cookies.Version; }
  132. if (cookies.Path !== undefined) { path = cookies.Path; delete cookies.Path; }
  133. if (cookies.HttpOnly !== undefined) { delete cookies.HttpOnly; }
  134. if (cookies.Domain !== undefined) { domain = cookies.Domain; delete cookies.Domain; }
  135. cookies = [ cookies ];
  136. }
  137. for (var k in cookies) {
  138. if (Array.isArray(cookies[k])) {
  139. cookies[k] = cookies[k][0];
  140. }
  141. }
  142. if (cookies) {
  143. domainToCookies[req.hostname] = cookies;
  144. }
  145. }
  146. return response;
  147. });
  148. };
  149. };
  150. // TODO request.Cache
  151. // TODO request.CookieJar
  152. request.Client = function(options) {
  153. if (!(this instanceof request.Client)) return new request.Client(options);
  154. options = options || {};
  155. for (var key in options) {
  156. this[key] = options[key];
  157. }
  158. this.request = request;
  159. // turn on redirects by default
  160. var redirects = "redirects" in this ? this.redirects : 20;
  161. if (redirects) {
  162. this.request = request.CookieJar(request.Redirect(this.request, typeof redirects === "number" && redirects));
  163. }
  164. var finalRequest = this.request;
  165. this.request = function(options) {
  166. if (typeof options === "string") options = {url: options};
  167. return finalRequest(options);
  168. }
  169. }
  170. return request;
  171. });