Skip to content

Commit c4858c7

Browse files
committed
Initial working version
1 parent 6eb73d0 commit c4858c7

File tree

3 files changed

+1627
-0
lines changed

3 files changed

+1627
-0
lines changed

XMLHttpRequest.js

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
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+
14+
exports.XMLHttpRequest = function() {
15+
/**
16+
* Private variables
17+
*/
18+
var self = this;
19+
var http = require('http');
20+
21+
// Holds http.js objects
22+
var client;
23+
var request;
24+
var response;
25+
26+
// Request settings
27+
var settings = {};
28+
29+
// Set some default headers
30+
var defaultHeaders = {
31+
"User-Agent": "node.js",
32+
"Accept": "*/*",
33+
};
34+
35+
var headers = defaultHeaders;
36+
37+
/**
38+
* Constants
39+
*/
40+
this.UNSENT = 0;
41+
this.OPENED = 1;
42+
this.HEADERS_RECEIVED = 2;
43+
this.LOADING = 3;
44+
this.DONE = 4;
45+
46+
/**
47+
* Public vars
48+
*/
49+
// Current state
50+
this.readyState = this.UNSENT;
51+
52+
// default ready state change handler in case one is not set or is set late
53+
this.onreadystatechange = function() {};
54+
55+
// Result & response
56+
this.responseText = "";
57+
this.responseXML = "";
58+
this.status = null;
59+
this.statusText = null;
60+
61+
/**
62+
* Open the connection. Currently supports local server requests.
63+
*
64+
* @param string method Connection method (eg GET, POST)
65+
* @param string url URL for the connection.
66+
* @param boolean async Asynchronous connection. Default is true.
67+
* @param string user Username for basic authentication (optional)
68+
* @param string password Password for basic authentication (optional)
69+
*/
70+
this.open = function(method, url, async, user, password) {
71+
settings = {
72+
"method": method,
73+
"url": url,
74+
"async": async,
75+
"user": user,
76+
"password": password
77+
};
78+
79+
this.abort();
80+
81+
setState(this.OPENED);
82+
};
83+
84+
/**
85+
* Sets a header for the request.
86+
*
87+
* @param string header Header name
88+
* @param string value Header value
89+
*/
90+
this.setRequestHeader = function(header, value) {
91+
headers[header] = value;
92+
};
93+
94+
/**
95+
* Gets a header from the server response.
96+
*
97+
* @param string header Name of header to get.
98+
* @return string Text of the header or null if it doesn't exist.
99+
*/
100+
this.getResponseHeader = function(header) {
101+
if (this.readyState > this.OPENED && response.headers.header) {
102+
return header + ": " + response.headers.header;
103+
}
104+
105+
return null;
106+
};
107+
108+
/**
109+
* Gets all the response headers.
110+
*
111+
* @return string
112+
*/
113+
this.getAllResponseHeaders = function() {
114+
if (this.readyState < this.HEADERS_RECEIVED) {
115+
throw "INVALID_STATE_ERR: Headers have not been received.";
116+
}
117+
var result = "";
118+
119+
for (var i in response.headers) {
120+
result += i + ": " + response.headers[i] + "\r\n";
121+
}
122+
return result.substr(0, result.length - 2);
123+
};
124+
125+
/**
126+
* Sends the request to the server.
127+
*
128+
* @param string data Optional data to send as request body.
129+
*/
130+
this.send = function(data) {
131+
if (this.readyState != this.OPENED) {
132+
throw "INVALID_STATE_ERR: connection must be opened before send() is called";
133+
}
134+
135+
/**
136+
setState(this.OPENED);
137+
138+
* Figure out if a host and/or port were specified.
139+
* Regex borrowed from parseUri and modified. Needs additional optimization.
140+
* @see http://blog.stevenlevithan.com/archives/parseuri
141+
*/
142+
var loc = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?([^?#]*)/.exec(settings.url);
143+
144+
// Determine the server
145+
switch (loc[1]) {
146+
case 'http':
147+
var host = loc[6];
148+
break;
149+
150+
case undefined:
151+
case '':
152+
var host = "localhost";
153+
break;
154+
155+
case 'https':
156+
throw "SSL is not implemented.";
157+
break;
158+
159+
default:
160+
throw "Protocol not supported.";
161+
}
162+
163+
// Default to port 80. If accessing localhost on another port be sure to
164+
// use http://localhost:port/path
165+
var port = loc[7] ? loc[7] : 80;
166+
167+
// Set the URI, default to /
168+
var uri = loc[8] ? loc[8] : "/";
169+
170+
// Set the Host header or the server may reject the request
171+
headers["Host"] = host;
172+
173+
client = http.createClient(port, host);
174+
175+
client.addListener('error', function (error) { //Error checking
176+
self.status=503;
177+
self.statusText=error;
178+
self.responseText=error.stack;
179+
setState(self.DONE);
180+
//throw error;
181+
})
182+
183+
// Set content length header
184+
if (settings.method == "GET" || settings.method == "HEAD") {
185+
data = null;
186+
} else if (data) {
187+
headers["Content-Length"] = data.length;
188+
189+
if (!headers["Content-Type"]) {
190+
headers["Content-Type"] = "text/plain;charset=UTF-8";
191+
}
192+
}
193+
194+
// Use the correct request method
195+
switch (settings.method) {
196+
case 'GET':
197+
request = client.request("GET",uri, headers);
198+
break;
199+
200+
case 'POST':
201+
request = client.request("POST",uri, headers);
202+
break;
203+
204+
case 'HEAD':
205+
request = client.request("HEAD",uri, headers);
206+
break;
207+
208+
case 'PUT':
209+
request = client.request("PUT",uri, headers);
210+
break;
211+
212+
case 'DELETE':
213+
request = client.request("DELETE",uri, headers);
214+
break;
215+
216+
default:
217+
throw "Request method is unsupported.";
218+
}
219+
220+
// Send data to the server
221+
if (data) {
222+
request.write(data);
223+
}
224+
225+
request.addListener('response', function(resp) {
226+
response = resp;
227+
response.setEncoding("utf8");
228+
229+
setState(this.HEADERS_RECEIVED);
230+
231+
self.status = response.statusCode;
232+
233+
response.addListener("data", function(chunk) {
234+
// Make sure there's some data
235+
if (chunk) {
236+
self.responseText += chunk;
237+
}
238+
setState(self.LOADING);
239+
});
240+
241+
response.addListener("end", function() {
242+
setState(self.DONE);
243+
});
244+
});
245+
request.end();
246+
};
247+
248+
/**
249+
* Aborts a request.
250+
*/
251+
this.abort = function() {
252+
headers = defaultHeaders;
253+
this.readyState = this.UNSENT;
254+
this.responseText = "";
255+
this.responseXML = "";
256+
};
257+
258+
/**
259+
* Changes readyState and calls onreadystatechange.
260+
*
261+
* @param int state New state
262+
*/
263+
var setState = function(state) {
264+
self.readyState = state;
265+
self.onreadystatechange();
266+
}
267+
};

0 commit comments

Comments
 (0)