XMLHttpRequest.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /**
  2. * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
  3. *
  4. * This can be used with JS designed for browsers to improve reuse of code and
  5. * allow the use of existing libraries.
  6. *
  7. * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.
  8. *
  9. * @todo SSL Support
  10. * @author Dan DeFelippi <dan@driverdan.com>
  11. * @license MIT
  12. */
  13. var Url = require("url")
  14. ,sys = require("util");
  15. exports.XMLHttpRequest = function() {
  16. /**
  17. * Private variables
  18. */
  19. var self = this;
  20. var http = require('http');
  21. var https = require('https');
  22. // Holds http.js objects
  23. var client;
  24. var request;
  25. var response;
  26. // Request settings
  27. var settings = {};
  28. // Set some default headers
  29. var defaultHeaders = {
  30. "User-Agent": "node.js",
  31. "Accept": "*/*",
  32. };
  33. var headers = defaultHeaders;
  34. /**
  35. * Constants
  36. */
  37. this.UNSENT = 0;
  38. this.OPENED = 1;
  39. this.HEADERS_RECEIVED = 2;
  40. this.LOADING = 3;
  41. this.DONE = 4;
  42. /**
  43. * Public vars
  44. */
  45. // Current state
  46. this.readyState = this.UNSENT;
  47. // default ready state change handler in case one is not set or is set late
  48. this.onreadystatechange = function() {};
  49. // Result & response
  50. this.responseText = "";
  51. this.responseXML = "";
  52. this.status = null;
  53. this.statusText = null;
  54. /**
  55. * Open the connection. Currently supports local server requests.
  56. *
  57. * @param string method Connection method (eg GET, POST)
  58. * @param string url URL for the connection.
  59. * @param boolean async Asynchronous connection. Default is true.
  60. * @param string user Username for basic authentication (optional)
  61. * @param string password Password for basic authentication (optional)
  62. */
  63. this.open = function(method, url, async, user, password) {
  64. settings = {
  65. "method": method,
  66. "url": url,
  67. "async": async || null,
  68. "user": user || null,
  69. "password": password || null
  70. };
  71. this.abort();
  72. setState(this.OPENED);
  73. };
  74. /**
  75. * Sets a header for the request.
  76. *
  77. * @param string header Header name
  78. * @param string value Header value
  79. */
  80. this.setRequestHeader = function(header, value) {
  81. headers[header] = value;
  82. };
  83. /**
  84. * Gets a header from the server response.
  85. *
  86. * @param string header Name of header to get.
  87. * @return string Text of the header or null if it doesn't exist.
  88. */
  89. this.getResponseHeader = function(header) {
  90. if (this.readyState > this.OPENED && response.headers[header]) {
  91. return header + ": " + response.headers[header];
  92. }
  93. return null;
  94. };
  95. /**
  96. * Gets all the response headers.
  97. *
  98. * @return string
  99. */
  100. this.getAllResponseHeaders = function() {
  101. if (this.readyState < this.HEADERS_RECEIVED) {
  102. throw "INVALID_STATE_ERR: Headers have not been received.";
  103. }
  104. var result = "";
  105. for (var i in response.headers) {
  106. result += i + ": " + response.headers[i] + "\r\n";
  107. }
  108. return result.substr(0, result.length - 2);
  109. };
  110. /**
  111. * Sends the request to the server.
  112. *
  113. * @param string data Optional data to send as request body.
  114. */
  115. this.send = function(data) {
  116. if (this.readyState != this.OPENED) {
  117. throw "INVALID_STATE_ERR: connection must be opened before send() is called";
  118. }
  119. var ssl = false;
  120. var url = Url.parse(settings.url);
  121. // Determine the server
  122. switch (url.protocol) {
  123. case 'https:':
  124. ssl = true;
  125. // SSL & non-SSL both need host, no break here.
  126. case 'http:':
  127. var host = url.hostname;
  128. break;
  129. case undefined:
  130. case '':
  131. var host = "localhost";
  132. break;
  133. default:
  134. throw "Protocol not supported.";
  135. }
  136. // Default to port 80. If accessing localhost on another port be sure
  137. // to use http://localhost:port/path
  138. var port = url.port || (ssl ? 443 : 80);
  139. // Add query string if one is used
  140. var uri = url.pathname + (url.search ? url.search : '');
  141. // Set the Host header or the server may reject the request
  142. this.setRequestHeader("Host", host);
  143. // Set content length header
  144. if (settings.method == "GET" || settings.method == "HEAD") {
  145. data = null;
  146. } else if (data) {
  147. this.setRequestHeader("Content-Length", Buffer.byteLength(data));
  148. if (!headers["Content-Type"]) {
  149. this.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
  150. }
  151. }
  152. // Use the proper protocol
  153. var doRequest = ssl ? https.request : http.request;
  154. var options = {
  155. host: host,
  156. port: port,
  157. path: uri,
  158. method: settings.method,
  159. headers: headers,
  160. agent: false
  161. };
  162. var req = doRequest(options, function(res) {
  163. response = res;
  164. response.setEncoding("utf8");
  165. setState(self.HEADERS_RECEIVED);
  166. self.status = response.statusCode;
  167. response.on('data', function(chunk) {
  168. // Make sure there's some data
  169. if (chunk) {
  170. self.responseText += chunk;
  171. }
  172. setState(self.LOADING);
  173. });
  174. response.on('end', function() {
  175. setState(self.DONE);
  176. });
  177. response.on('error', function() {
  178. self.handleError(error);
  179. });
  180. }).on('error', function(error) {
  181. self.handleError(error);
  182. });
  183. req.setHeader("Connection", "Close");
  184. // Node 0.4 and later won't accept empty data. Make sure it's needed.
  185. if (data) {
  186. req.write(data);
  187. }
  188. req.end();
  189. };
  190. this.handleError = function(error) {
  191. this.status = 503;
  192. this.statusText = error;
  193. this.responseText = error.stack;
  194. setState(this.DONE);
  195. };
  196. /**
  197. * Aborts a request.
  198. */
  199. this.abort = function() {
  200. headers = defaultHeaders;
  201. this.readyState = this.UNSENT;
  202. this.responseText = "";
  203. this.responseXML = "";
  204. };
  205. /**
  206. * Changes readyState and calls onreadystatechange.
  207. *
  208. * @param int state New state
  209. */
  210. var setState = function(state) {
  211. self.readyState = state;
  212. self.onreadystatechange();
  213. }
  214. };