Use parseUri() for req.uri. Update docs.
This commit is contained in:
Родитель
9c70bf356b
Коммит
edc38b4134
337
src/http.js
337
src/http.js
|
@ -38,6 +38,56 @@ node.http.STATUS_CODES = { 100 : 'Continue'
|
|||
, 505 : 'HTTP Version not supported'
|
||||
};
|
||||
|
||||
/*
|
||||
parseUri 1.2.1
|
||||
(c) 2007 Steven Levithan <stevenlevithan.com>
|
||||
MIT License
|
||||
*/
|
||||
|
||||
function parseUri (str) {
|
||||
var o = parseUri.options,
|
||||
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
|
||||
uri = {},
|
||||
i = 14;
|
||||
|
||||
while (i--) uri[o.key[i]] = m[i] || "";
|
||||
|
||||
uri[o.q.name] = {};
|
||||
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
|
||||
if ($1) uri[o.q.name][$1] = $2;
|
||||
});
|
||||
uri.toString = function () { return str; };
|
||||
|
||||
return uri;
|
||||
};
|
||||
|
||||
parseUri.options = {
|
||||
strictMode: false,
|
||||
key: [ "source"
|
||||
, "protocol"
|
||||
, "authority"
|
||||
, "userInfo"
|
||||
, "user"
|
||||
, "password"
|
||||
, "host"
|
||||
, "port"
|
||||
, "relative"
|
||||
, "path"
|
||||
, "directory"
|
||||
, "file"
|
||||
, "query"
|
||||
, "anchor"
|
||||
],
|
||||
q: {
|
||||
name: "queryKey",
|
||||
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
|
||||
},
|
||||
parser: {
|
||||
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
|
||||
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
||||
}
|
||||
};
|
||||
|
||||
var connection_expression = /Connection/i;
|
||||
var transfer_encoding_expression = /Transfer-Encoding/i;
|
||||
var close_expression = /close/i;
|
||||
|
@ -51,6 +101,148 @@ function toRaw(string) {
|
|||
return a;
|
||||
}
|
||||
|
||||
node.http.ServerResponse = function (connection, responses) {
|
||||
responses.push(this);
|
||||
this.connection = connection;
|
||||
var output = [];
|
||||
|
||||
// The send method appends data onto the output array. The deal is,
|
||||
// the data is either an array of integer, representing binary or it
|
||||
// is a string in which case it's UTF8 encoded.
|
||||
// Two things to considered:
|
||||
// - we should be able to send mixed encodings.
|
||||
// - we don't want to call connection.send("smallstring") because that
|
||||
// is wasteful. *I think* its rather faster to concat inside of JS
|
||||
// Thus I attempt to concat as much as possible.
|
||||
function send (data) {
|
||||
if (connection.readyState === "closed" || connection.readyState === "readOnly")
|
||||
{
|
||||
responses = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.length == 0) {
|
||||
output.push(data);
|
||||
return;
|
||||
}
|
||||
|
||||
var li = output.length-1;
|
||||
|
||||
if (data.constructor == String && output[li].constructor == String) {
|
||||
output[li] += data;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.constructor == Array && output[li].constructor == Array) {
|
||||
output[li] = output[li].concat(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the string is small enough, just convert it to binary
|
||||
if (data.constructor == String
|
||||
&& data.length < 128
|
||||
&& output[li].constructor == Array)
|
||||
{
|
||||
output[li] = output[li].concat(toRaw(data));
|
||||
return;
|
||||
}
|
||||
|
||||
output.push(data);
|
||||
};
|
||||
|
||||
this.flush = function () {
|
||||
if (responses.length > 0 && responses[0] === this)
|
||||
while (output.length > 0)
|
||||
connection.send(output.shift());
|
||||
};
|
||||
|
||||
var chunked_encoding = false;
|
||||
var connection_close = false;
|
||||
|
||||
this.sendHeader = function (status_code, headers) {
|
||||
var sent_connection_header = false;
|
||||
var sent_transfer_encoding_header = false;
|
||||
var sent_content_length_header = false;
|
||||
|
||||
var reason = node.http.STATUS_CODES[status_code] || "unknown";
|
||||
var header = "HTTP/1.1 "
|
||||
+ status_code.toString()
|
||||
+ " "
|
||||
+ reason
|
||||
+ "\r\n"
|
||||
;
|
||||
|
||||
for (var i = 0; i < headers.length; i++) {
|
||||
var field = headers[i][0];
|
||||
var value = headers[i][1];
|
||||
|
||||
header += field + ": " + value + "\r\n";
|
||||
|
||||
if (connection_expression.exec(field)) {
|
||||
sent_connection_header = true;
|
||||
if (close_expression.exec(value))
|
||||
connection_close = true;
|
||||
} else if (transfer_encoding_expression.exec(field)) {
|
||||
sent_transfer_encoding_header = true;
|
||||
if (chunk_expression.exec(value))
|
||||
chunked_encoding = true;
|
||||
} else if (content_length_expression.exec(field)) {
|
||||
sent_content_length_header = true;
|
||||
}
|
||||
}
|
||||
|
||||
// keep-alive logic
|
||||
if (sent_connection_header == false) {
|
||||
if (this.should_keep_alive) {
|
||||
header += "Connection: keep-alive\r\n";
|
||||
} else {
|
||||
connection_close = true;
|
||||
header += "Connection: close\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (sent_content_length_header == false && sent_transfer_encoding_header == false) {
|
||||
header += "Transfer-Encoding: chunked\r\n";
|
||||
chunked_encoding = true;
|
||||
}
|
||||
|
||||
header += "\r\n";
|
||||
|
||||
send(header);
|
||||
};
|
||||
|
||||
this.sendBody = function (chunk) {
|
||||
if (chunked_encoding) {
|
||||
send(chunk.length.toString(16));
|
||||
send("\r\n");
|
||||
send(chunk);
|
||||
send("\r\n");
|
||||
} else {
|
||||
send(chunk);
|
||||
}
|
||||
|
||||
this.flush();
|
||||
};
|
||||
|
||||
this.finished = false;
|
||||
this.finish = function () {
|
||||
if (chunked_encoding)
|
||||
send("0\r\n\r\n"); // last chunk
|
||||
|
||||
this.finished = true;
|
||||
|
||||
while (responses.length > 0 && responses[0].finished) {
|
||||
var res = responses[0];
|
||||
res.flush();
|
||||
responses.shift();
|
||||
}
|
||||
|
||||
if (responses.length == 0 && connection_close) {
|
||||
connection.fullClose();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* This is a wrapper around the LowLevelServer interface. It provides
|
||||
* connection handling, overflow checking, and some data buffering.
|
||||
*/
|
||||
|
@ -58,148 +250,6 @@ node.http.Server = function (RequestHandler, options) {
|
|||
if (!(this instanceof node.http.Server))
|
||||
throw Error("Constructor called as a function");
|
||||
|
||||
function Response (connection, responses) {
|
||||
responses.push(this);
|
||||
this.connection = connection;
|
||||
var output = [];
|
||||
|
||||
// The send method appends data onto the output array. The deal is,
|
||||
// the data is either an array of integer, representing binary or it
|
||||
// is a string in which case it's UTF8 encoded.
|
||||
// Two things to considered:
|
||||
// - we should be able to send mixed encodings.
|
||||
// - we don't want to call connection.send("smallstring") because that
|
||||
// is wasteful. *I think* its rather faster to concat inside of JS
|
||||
// Thus I attempt to concat as much as possible.
|
||||
function send (data) {
|
||||
if (connection.readyState === "closed" || connection.readyState === "readOnly")
|
||||
{
|
||||
responses = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.length == 0) {
|
||||
output.push(data);
|
||||
return;
|
||||
}
|
||||
|
||||
var li = output.length-1;
|
||||
|
||||
if (data.constructor == String && output[li].constructor == String) {
|
||||
output[li] += data;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.constructor == Array && output[li].constructor == Array) {
|
||||
output[li] = output[li].concat(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the string is small enough, just convert it to binary
|
||||
if (data.constructor == String
|
||||
&& data.length < 128
|
||||
&& output[li].constructor == Array)
|
||||
{
|
||||
output[li] = output[li].concat(toRaw(data));
|
||||
return;
|
||||
}
|
||||
|
||||
output.push(data);
|
||||
};
|
||||
|
||||
this.flush = function () {
|
||||
if (responses.length > 0 && responses[0] === this)
|
||||
while (output.length > 0)
|
||||
connection.send(output.shift());
|
||||
};
|
||||
|
||||
var chunked_encoding = false;
|
||||
var connection_close = false;
|
||||
|
||||
this.sendHeader = function (status_code, headers) {
|
||||
var sent_connection_header = false;
|
||||
var sent_transfer_encoding_header = false;
|
||||
var sent_content_length_header = false;
|
||||
|
||||
var reason = node.http.STATUS_CODES[status_code] || "unknown";
|
||||
var header = "HTTP/1.1 "
|
||||
+ status_code.toString()
|
||||
+ " "
|
||||
+ reason
|
||||
+ "\r\n"
|
||||
;
|
||||
|
||||
for (var i = 0; i < headers.length; i++) {
|
||||
var field = headers[i][0];
|
||||
var value = headers[i][1];
|
||||
|
||||
header += field + ": " + value + "\r\n";
|
||||
|
||||
if (connection_expression.exec(field)) {
|
||||
sent_connection_header = true;
|
||||
if (close_expression.exec(value))
|
||||
connection_close = true;
|
||||
} else if (transfer_encoding_expression.exec(field)) {
|
||||
sent_transfer_encoding_header = true;
|
||||
if (chunk_expression.exec(value))
|
||||
chunked_encoding = true;
|
||||
} else if (content_length_expression.exec(field)) {
|
||||
sent_content_length_header = true;
|
||||
}
|
||||
}
|
||||
|
||||
// keep-alive logic
|
||||
if (sent_connection_header == false) {
|
||||
if (this.should_keep_alive) {
|
||||
header += "Connection: keep-alive\r\n";
|
||||
} else {
|
||||
connection_close = true;
|
||||
header += "Connection: close\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (sent_content_length_header == false && sent_transfer_encoding_header == false) {
|
||||
header += "Transfer-Encoding: chunked\r\n";
|
||||
chunked_encoding = true;
|
||||
}
|
||||
|
||||
header += "\r\n";
|
||||
|
||||
send(header);
|
||||
};
|
||||
|
||||
this.sendBody = function (chunk) {
|
||||
if (chunked_encoding) {
|
||||
send(chunk.length.toString(16));
|
||||
send("\r\n");
|
||||
send(chunk);
|
||||
send("\r\n");
|
||||
} else {
|
||||
send(chunk);
|
||||
}
|
||||
|
||||
this.flush();
|
||||
};
|
||||
|
||||
this.finished = false;
|
||||
this.finish = function () {
|
||||
if (chunked_encoding)
|
||||
send("0\r\n\r\n"); // last chunk
|
||||
|
||||
this.finished = true;
|
||||
|
||||
while (responses.length > 0 && responses[0].finished) {
|
||||
var res = responses[0];
|
||||
res.flush();
|
||||
responses.shift();
|
||||
}
|
||||
|
||||
if (responses.length == 0 && connection_close) {
|
||||
connection.fullClose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function ConnectionHandler (connection) {
|
||||
// An array of responses for each connection. In pipelined connections
|
||||
// we need to keep track of the order they were sent.
|
||||
|
@ -214,7 +264,7 @@ node.http.Server = function (RequestHandler, options) {
|
|||
, onBody : null // by user
|
||||
, onBodyComplete : null // by user
|
||||
}
|
||||
var res = new Response(connection, responses);
|
||||
var res = new node.http.ServerResponse(connection, responses);
|
||||
|
||||
this.onURI = function (data) {
|
||||
req.uri += data;
|
||||
|
@ -246,6 +296,7 @@ node.http.Server = function (RequestHandler, options) {
|
|||
this.onHeadersComplete = function () {
|
||||
req.http_version = this.http_version;
|
||||
req.method = this.method;
|
||||
req.uri = parseUri(req.uri);
|
||||
|
||||
res.should_keep_alive = this.should_keep_alive;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ new node.http.Server(function (req, res) {
|
|||
var arg = commands[2];
|
||||
var status = 200;
|
||||
|
||||
//p(req.headers);
|
||||
p(req.headers);
|
||||
|
||||
if (command == "bytes") {
|
||||
var n = parseInt(arg, 10)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
new node.http.Server(function (req, res) {
|
||||
setTimeout(function () {
|
||||
res.sendHeader(200, [["Content-Type", "text/plain"]]);
|
||||
res.sendBody("Hello World");
|
||||
res.sendBody(JSON.stringify(req.uri));
|
||||
res.finish();
|
||||
}, 1000);
|
||||
}, 1);
|
||||
}).listen(8000, "localhost");
|
||||
|
|
|
@ -68,7 +68,7 @@ a:hover { text-decoration: underline; }
|
|||
}
|
||||
|
||||
</style>
|
||||
<script type="text/javascript" src="sh_main.min.js"></script>
|
||||
<script type="text/javascript" src="sh_main.js"></script>
|
||||
<script type="text/javascript" src="sh_javascript.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="sh_vim-dark.css">
|
||||
|
||||
|
@ -84,10 +84,12 @@ a:hover { text-decoration: underline; }
|
|||
<ol>
|
||||
<li><a href="#timers">Timers</a>
|
||||
<li><a href="#files">File System</a>
|
||||
<li><a href="#tcp">TCP</a>
|
||||
<li><a href="#http">HTTP</a>
|
||||
<li><a href="#tcp">tcp</a>
|
||||
<li><a href="#http">http</a>
|
||||
<ol>
|
||||
<li><a href="#http_server">Server</a>
|
||||
<li><a href="#http_server_request">ServerRequest</a>
|
||||
<li><a href="#http_server_response">ServerResponse</a>
|
||||
<li><a href="#http_client">Client</a>
|
||||
</ol>
|
||||
<li><a href="#modules">Modules</a>
|
||||
|
@ -99,11 +101,12 @@ a:hover { text-decoration: underline; }
|
|||
|
||||
<h1><a href="http://tinyclouds.org/node">Node</a></h1>
|
||||
|
||||
<p id="introduction"> Node is a purely asynchronous I/O framework for <a
|
||||
<p id="introduction"> Purely asynchronous I/O for <a
|
||||
href="http://code.google.com/p/v8/">V8 javascript</a>.
|
||||
|
||||
<p>This is an example of a web server written with Node which listens on
|
||||
port 8000 and responds with "Hello World" after waiting two seconds:
|
||||
<p>This is an example of a web server written with Node which responds with
|
||||
"Hello World" after waiting two seconds:
|
||||
|
||||
<pre class="sh_javascript">new node.http.Server(function (req, res) {
|
||||
setTimeout(function () {
|
||||
res.sendHeader(200, [["Content-Type", "text/plain"]]);
|
||||
|
@ -111,7 +114,20 @@ port 8000 and responds with "Hello World" after waiting two seconds:
|
|||
res.finish();
|
||||
}, 2000);
|
||||
}).listen(8000);
|
||||
</pre>
|
||||
puts("Server running at http://127.0.0.1:8000/");</pre>
|
||||
|
||||
<p> Execution does not block on <code
|
||||
class="sh_javascript">setTimeout()</code>
|
||||
nor
|
||||
<code
|
||||
class="sh_javascript">listen(8000)</code>.
|
||||
File I/O is also preformed without blocking.
|
||||
In fact, not a single function in Node blocks execution.
|
||||
There isn't even a call to run the event loop.
|
||||
|
||||
<p> Programmers using this environment will find it difficult to design
|
||||
their systems ineffiencely. It's impossible to make a database call from a
|
||||
web server and block other requests.
|
||||
|
||||
|
||||
<p> Check out <a href="#api">the API documentation</a> for more examples.
|
||||
|
@ -224,26 +240,35 @@ See <a
|
|||
|
||||
<h3 id="files">File System</h3>
|
||||
|
||||
<h3 id="tcp">TCP</h3>
|
||||
<h3 id="tcp"><code>node.tcp</code></h3>
|
||||
|
||||
<h3 id="http">HTTP (<code class="sh_javascript">node.http</code>)</h3>
|
||||
<h3 id="http"><code>node.http</code></h3>
|
||||
|
||||
<p> Node provides a web server and client interface. The interface is rather
|
||||
low-level but complete. For example, it does not parse
|
||||
<code class="sh_javascript">application/x-www-form-urlencoded</code> message bodies. The interface
|
||||
does abstract the Transfer-Encoding (i.e. chuncked or identity), Message
|
||||
boundarys, and Keep-Alive connections.
|
||||
boundaries, and Keep-Alive connections.
|
||||
|
||||
<h4 id="http_server"><code class="sh_javascript">node.http.Server</code></h4>
|
||||
|
||||
<h4 id="http_server">HTTP Server (<code class="sh_javascript">node.http.Server</code>)</h4>
|
||||
<dl>
|
||||
<dt><code class="sh_javascript">new Server(request_handler, options)</code></dt>
|
||||
<dt><code class="sh_javascript">var server = new node.http.Server(request_handler, options);</code></dt>
|
||||
<dd>
|
||||
<p>Creates a new web server. The <code class="sh_javascript">options</code> argument accepts
|
||||
the same values as the options argument for
|
||||
<code class="sh_javascript">node.tcp.Server</code> does. The options argument is optional.
|
||||
<p>Creates a new web server.
|
||||
|
||||
<p>
|
||||
The <code>options</code> argument is optional.
|
||||
The <code
|
||||
class="sh_javascript">options</code> argument accepts the same values
|
||||
as the options argument for <code
|
||||
class="sh_javascript">node.tcp.Server</code> does.
|
||||
|
||||
<p>The <code class="sh_javascript">request_handler</code> is a
|
||||
callback which is made on each request with a
|
||||
<code>ServerRequest</code> and
|
||||
<code>ServerResponse</code> arguments.
|
||||
|
||||
<p>The <code class="sh_javascript">request_handler</code> is a function which is called on
|
||||
each request with a <code class="sh_javascript">Message</code> object argument.
|
||||
</dd>
|
||||
|
||||
<dt><code class="sh_javascript">server.listen(port, hostname)</code>
|
||||
|
@ -261,26 +286,37 @@ boundarys, and Keep-Alive connections.
|
|||
</dl>
|
||||
|
||||
|
||||
<h4>HTTP Request Message (<code class="sh_javascript">node.http.Message</code>)</h4>
|
||||
<h4 id="http_server_request"><code class="sh_javascript">node.http.ServerRequest</code></h4>
|
||||
|
||||
<p> This object is only created internally—not by the user. It is passed
|
||||
as an argument to the <code class="sh_javascript">request_handler</code> callback in a web server.
|
||||
<p> This object is created internally by a HTTP server—not by the user.
|
||||
It is passed as the first argument to the <code
|
||||
class="sh_javascript">request_handler</code> callback.
|
||||
|
||||
<p> This object, unlike in other HTTP APIs, is used as an interface for both
|
||||
the request and response. Members and callbacks reference request data, like
|
||||
<code class="sh_javascript">msg.method</code> and <code class="sh_javascript">msg.onBody</code>. The methods are for
|
||||
sending a response to this message. Like <code class="sh_javascript">msg.sendHeader()</code> and
|
||||
<code class="sh_javascript">msg.sendBody()</code>.
|
||||
<dl>
|
||||
<dt><code class="sh_javascript">msg.method</code>
|
||||
<dt><code class="sh_javascript">req.method</code>
|
||||
<dd>The request method as a string. Read only. Example: <code class="sh_javascript">"GET"</code>,
|
||||
<code class="sh_javascript">"DELETE"</code>.</dd>
|
||||
|
||||
<dt><code class="sh_javascript">msg.uri</code>
|
||||
<dd>The request URI as a string. Read only.
|
||||
Example: <code class="sh_javascript">"/index.html?hello=world"</code>.</dd>
|
||||
<dt><code class="sh_javascript">req.uri</code>
|
||||
<dd> URI object. Has many fields.
|
||||
<dt><code>req.uri.toString()</code>
|
||||
<dd> The original URI found in the status line.
|
||||
<dt><code>req.uri.anchor</code>
|
||||
<dt><code>req.uri.query</code>
|
||||
<dt><code>req.uri.file</code>
|
||||
<dt><code>req.uri.directory</code>
|
||||
<dt><code>req.uri.path</code>
|
||||
<dt><code>req.uri.relative</code>
|
||||
<dt><code>req.uri.port</code>
|
||||
<dt><code>req.uri.host</code>
|
||||
<dt><code>req.uri.password</code>
|
||||
<dt><code>req.uri.user</code>
|
||||
<dt><code>req.uri.authority</code>
|
||||
<dt><code>req.uri.protocol</code>
|
||||
<dt><code>req.uri.source</code>
|
||||
<dt><code>req.uri.queryKey</code>
|
||||
|
||||
<dt><code class="sh_javascript">msg.headers</code>
|
||||
<dt><code class="sh_javascript">req.headers</code>
|
||||
<dd>The request headers expressed as an array of 2-element arrays. Read only.
|
||||
Example:
|
||||
<pre class="sh_javascript">
|
||||
|
@ -291,40 +327,41 @@ sending a response to this message. Like <code class="sh_javascript">msg.sendHea
|
|||
]
|
||||
</pre>
|
||||
|
||||
<dt><code class="sh_javascript">msg.http_version</code></dt>
|
||||
<dt><code class="sh_javascript">req.http_version</code></dt>
|
||||
<dd>The HTTP protocol version as a string. Read only. Examples: <code class="sh_javascript">"1.1"</code>,
|
||||
<code class="sh_javascript">"1.0"</code>
|
||||
|
||||
<dt><code class="sh_javascript">msg.connection</code></dt>
|
||||
<dd> A reference to the <code class="sh_javascript">node.tcp.Connection</code> object. Read
|
||||
only. Note that multiple messages can be sent on a single connection.
|
||||
</dd>
|
||||
|
||||
<dt><code class="sh_javascript">msg.onBody</code></dt>
|
||||
<dt><code class="sh_javascript">req.onBody</code></dt>
|
||||
<dd>Callback. Should be set by the user to be informed of when a piece
|
||||
of the message body is received. Example:
|
||||
<pre class="sh_javascript">
|
||||
msg.onBody = function (chunk) {
|
||||
req.onBody = function (chunk) {
|
||||
puts("part of the body: " + chunk);
|
||||
}
|
||||
};
|
||||
</pre>
|
||||
A chunk of the body is given as the single argument. The transfer-encoding
|
||||
has been removed.
|
||||
<p>The body chunk is either a String in the case of utf8 encoding or an
|
||||
array of numbers in the case of raw encoding.
|
||||
|
||||
<dt><code class="sh_javascript">msg.onBodyComplete</code></dt>
|
||||
<p>The body chunk is either a String in the case of UTF-8 encoding or an
|
||||
array of numbers in the case of raw encoding. The body encoding is set with
|
||||
<code class="sh_javascript">req.setBodyEncoding()</code>.
|
||||
|
||||
<dt><code class="sh_javascript">req.onBodyComplete</code></dt>
|
||||
<dd>Callback. Made exactly once for each message. No arguments. After
|
||||
<code class="sh_javascript">onBodyComplete</code> is executed <code class="sh_javascript">onBody</code> will no longer be called.
|
||||
</dd>
|
||||
|
||||
<dt><code class="sh_javascript">msg.setBodyEncoding(encoding)</code></dt>
|
||||
<dt><code class="sh_javascript">req.setBodyEncoding(encoding)</code></dt>
|
||||
<dd>
|
||||
Set the encoding for the request body. Either <code class="sh_javascript">"utf8"</code> or
|
||||
<code class="sh_javascript">"raw"</code>. Defaults to raw.
|
||||
<big>TODO</big>
|
||||
</dl>
|
||||
|
||||
<dt><code class="sh_javascript">msg.sendHeader(status_code, headers)</code></dt>
|
||||
<h4 id="http_server_response"><code class="sh_javascript">node.http.ServerResponse</code></h4>
|
||||
|
||||
<dl>
|
||||
<dt><code class="sh_javascript">res.sendHeader(status_code, headers)</code></dt>
|
||||
<dd>
|
||||
Sends a response header to the request. The status code is a 3-digit
|
||||
HTTP status code, like <code class="sh_javascript">404</code>. The second argument,
|
||||
|
@ -334,28 +371,26 @@ msg.onBody = function (chunk) {
|
|||
<p>Example:
|
||||
<pre class="sh_javascript">
|
||||
var body = "hello world";
|
||||
msg.sendHeader( 200
|
||||
, [ ["Content-Length", body.length]
|
||||
, ["Content-Type", "text/plain"]
|
||||
]
|
||||
);
|
||||
res.sendHeader(200, [ ["Content-Length", body.length]
|
||||
, ["Content-Type", "text/plain"]
|
||||
]);
|
||||
</pre>
|
||||
This method must only be called once on a message and it must be called
|
||||
before <code class="sh_javascript">msg.finish()</code> is called.
|
||||
before <code class="sh_javascript">res.finish()</code> is called.
|
||||
</dd>
|
||||
|
||||
<dt><code class="sh_javascript">msg.sendBody(chunk)</code></dt>
|
||||
<dt><code class="sh_javascript">res.sendBody(chunk)</code></dt>
|
||||
<dd>
|
||||
This method must be called after <code class="sh_javascript">sendHeader</code> was called. It
|
||||
sends a chunk of the response body. This method may be called multiple
|
||||
times to provide successive parts of the body.
|
||||
</dd>
|
||||
|
||||
<dt><code class="sh_javascript">msg.finish()</code></dt>
|
||||
<dt><code class="sh_javascript">res.finish()</code></dt>
|
||||
<dd>
|
||||
This method signals that all of the response headers and body has been
|
||||
sent; that server should consider this message complete.
|
||||
The method, <code class="sh_javascript">msg.finish()</code>, MUST be called on each response.
|
||||
The method, <code class="sh_javascript">res.finish()</code>, MUST be called on each response.
|
||||
|
||||
</dl>
|
||||
|
||||
|
|
|
@ -0,0 +1,545 @@
|
|||
/*
|
||||
SHJS - Syntax Highlighting in JavaScript
|
||||
Copyright (C) 2007, 2008 gnombat@users.sourceforge.net
|
||||
License: http://shjs.sourceforge.net/doc/gplv3.html
|
||||
*/
|
||||
|
||||
if (! this.sh_languages) {
|
||||
this.sh_languages = {};
|
||||
}
|
||||
var sh_requests = {};
|
||||
|
||||
function sh_isEmailAddress(url) {
|
||||
if (/^mailto:/.test(url)) {
|
||||
return false;
|
||||
}
|
||||
return url.indexOf('@') !== -1;
|
||||
}
|
||||
|
||||
function sh_setHref(tags, numTags, inputString) {
|
||||
var url = inputString.substring(tags[numTags - 2].pos, tags[numTags - 1].pos);
|
||||
if (url.length >= 2 && url.charAt(0) === '<' && url.charAt(url.length - 1) === '>') {
|
||||
url = url.substr(1, url.length - 2);
|
||||
}
|
||||
if (sh_isEmailAddress(url)) {
|
||||
url = 'mailto:' + url;
|
||||
}
|
||||
tags[numTags - 2].node.href = url;
|
||||
}
|
||||
|
||||
/*
|
||||
Konqueror has a bug where the regular expression /$/g will not match at the end
|
||||
of a line more than once:
|
||||
|
||||
var regex = /$/g;
|
||||
var match;
|
||||
|
||||
var line = '1234567890';
|
||||
regex.lastIndex = 10;
|
||||
match = regex.exec(line);
|
||||
|
||||
var line2 = 'abcde';
|
||||
regex.lastIndex = 5;
|
||||
match = regex.exec(line2); // fails
|
||||
*/
|
||||
function sh_konquerorExec(s) {
|
||||
var result = [''];
|
||||
result.index = s.length;
|
||||
result.input = s;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Highlights all elements containing source code in a text string. The return
|
||||
value is an array of objects, each representing an HTML start or end tag. Each
|
||||
object has a property named pos, which is an integer representing the text
|
||||
offset of the tag. Every start tag also has a property named node, which is the
|
||||
DOM element started by the tag. End tags do not have this property.
|
||||
@param inputString a text string
|
||||
@param language a language definition object
|
||||
@return an array of tag objects
|
||||
*/
|
||||
function sh_highlightString(inputString, language) {
|
||||
if (/Konqueror/.test(navigator.userAgent)) {
|
||||
if (! language.konquered) {
|
||||
for (var s = 0; s < language.length; s++) {
|
||||
for (var p = 0; p < language[s].length; p++) {
|
||||
var r = language[s][p][0];
|
||||
if (r.source === '$') {
|
||||
r.exec = sh_konquerorExec;
|
||||
}
|
||||
}
|
||||
}
|
||||
language.konquered = true;
|
||||
}
|
||||
}
|
||||
|
||||
var a = document.createElement('a');
|
||||
var span = document.createElement('span');
|
||||
|
||||
// the result
|
||||
var tags = [];
|
||||
var numTags = 0;
|
||||
|
||||
// each element is a pattern object from language
|
||||
var patternStack = [];
|
||||
|
||||
// the current position within inputString
|
||||
var pos = 0;
|
||||
|
||||
// the name of the current style, or null if there is no current style
|
||||
var currentStyle = null;
|
||||
|
||||
var output = function(s, style) {
|
||||
var length = s.length;
|
||||
// this is more than just an optimization - we don't want to output empty <span></span> elements
|
||||
if (length === 0) {
|
||||
return;
|
||||
}
|
||||
if (! style) {
|
||||
var stackLength = patternStack.length;
|
||||
if (stackLength !== 0) {
|
||||
var pattern = patternStack[stackLength - 1];
|
||||
// check whether this is a state or an environment
|
||||
if (! pattern[3]) {
|
||||
// it's not a state - it's an environment; use the style for this environment
|
||||
style = pattern[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentStyle !== style) {
|
||||
if (currentStyle) {
|
||||
tags[numTags++] = {pos: pos};
|
||||
if (currentStyle === 'sh_url') {
|
||||
sh_setHref(tags, numTags, inputString);
|
||||
}
|
||||
}
|
||||
if (style) {
|
||||
var clone;
|
||||
if (style === 'sh_url') {
|
||||
clone = a.cloneNode(false);
|
||||
}
|
||||
else {
|
||||
clone = span.cloneNode(false);
|
||||
}
|
||||
clone.className = style;
|
||||
tags[numTags++] = {node: clone, pos: pos};
|
||||
}
|
||||
}
|
||||
pos += length;
|
||||
currentStyle = style;
|
||||
};
|
||||
|
||||
var endOfLinePattern = /\r\n|\r|\n/g;
|
||||
endOfLinePattern.lastIndex = 0;
|
||||
var inputStringLength = inputString.length;
|
||||
while (pos < inputStringLength) {
|
||||
var start = pos;
|
||||
var end;
|
||||
var startOfNextLine;
|
||||
var endOfLineMatch = endOfLinePattern.exec(inputString);
|
||||
if (endOfLineMatch === null) {
|
||||
end = inputStringLength;
|
||||
startOfNextLine = inputStringLength;
|
||||
}
|
||||
else {
|
||||
end = endOfLineMatch.index;
|
||||
startOfNextLine = endOfLinePattern.lastIndex;
|
||||
}
|
||||
|
||||
var line = inputString.substring(start, end);
|
||||
|
||||
var matchCache = [];
|
||||
for (;;) {
|
||||
var posWithinLine = pos - start;
|
||||
|
||||
var stateIndex;
|
||||
var stackLength = patternStack.length;
|
||||
if (stackLength === 0) {
|
||||
stateIndex = 0;
|
||||
}
|
||||
else {
|
||||
// get the next state
|
||||
stateIndex = patternStack[stackLength - 1][2];
|
||||
}
|
||||
|
||||
var state = language[stateIndex];
|
||||
var numPatterns = state.length;
|
||||
var mc = matchCache[stateIndex];
|
||||
if (! mc) {
|
||||
mc = matchCache[stateIndex] = [];
|
||||
}
|
||||
var bestMatch = null;
|
||||
var bestPatternIndex = -1;
|
||||
for (var i = 0; i < numPatterns; i++) {
|
||||
var match;
|
||||
if (i < mc.length && (mc[i] === null || posWithinLine <= mc[i].index)) {
|
||||
match = mc[i];
|
||||
}
|
||||
else {
|
||||
var regex = state[i][0];
|
||||
regex.lastIndex = posWithinLine;
|
||||
match = regex.exec(line);
|
||||
mc[i] = match;
|
||||
}
|
||||
if (match !== null && (bestMatch === null || match.index < bestMatch.index)) {
|
||||
bestMatch = match;
|
||||
bestPatternIndex = i;
|
||||
if (match.index === posWithinLine) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch === null) {
|
||||
output(line.substring(posWithinLine), null);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// got a match
|
||||
if (bestMatch.index > posWithinLine) {
|
||||
output(line.substring(posWithinLine, bestMatch.index), null);
|
||||
}
|
||||
|
||||
var pattern = state[bestPatternIndex];
|
||||
|
||||
var newStyle = pattern[1];
|
||||
var matchedString;
|
||||
if (newStyle instanceof Array) {
|
||||
for (var subexpression = 0; subexpression < newStyle.length; subexpression++) {
|
||||
matchedString = bestMatch[subexpression + 1];
|
||||
output(matchedString, newStyle[subexpression]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
matchedString = bestMatch[0];
|
||||
output(matchedString, newStyle);
|
||||
}
|
||||
|
||||
switch (pattern[2]) {
|
||||
case -1:
|
||||
// do nothing
|
||||
break;
|
||||
case -2:
|
||||
// exit
|
||||
patternStack.pop();
|
||||
break;
|
||||
case -3:
|
||||
// exitall
|
||||
patternStack.length = 0;
|
||||
break;
|
||||
default:
|
||||
// this was the start of a delimited pattern or a state/environment
|
||||
patternStack.push(pattern);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// end of the line
|
||||
if (currentStyle) {
|
||||
tags[numTags++] = {pos: pos};
|
||||
if (currentStyle === 'sh_url') {
|
||||
sh_setHref(tags, numTags, inputString);
|
||||
}
|
||||
currentStyle = null;
|
||||
}
|
||||
pos = startOfNextLine;
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DOM-dependent functions
|
||||
|
||||
function sh_getClasses(element) {
|
||||
var result = [];
|
||||
var htmlClass = element.className;
|
||||
if (htmlClass && htmlClass.length > 0) {
|
||||
var htmlClasses = htmlClass.split(' ');
|
||||
for (var i = 0; i < htmlClasses.length; i++) {
|
||||
if (htmlClasses[i].length > 0) {
|
||||
result.push(htmlClasses[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function sh_addClass(element, name) {
|
||||
var htmlClasses = sh_getClasses(element);
|
||||
for (var i = 0; i < htmlClasses.length; i++) {
|
||||
if (name.toLowerCase() === htmlClasses[i].toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
htmlClasses.push(name);
|
||||
element.className = htmlClasses.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
Extracts the tags from an HTML DOM NodeList.
|
||||
@param nodeList a DOM NodeList
|
||||
@param result an object with text, tags and pos properties
|
||||
*/
|
||||
function sh_extractTagsFromNodeList(nodeList, result) {
|
||||
var length = nodeList.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var node = nodeList.item(i);
|
||||
switch (node.nodeType) {
|
||||
case 1:
|
||||
if (node.nodeName.toLowerCase() === 'br') {
|
||||
var terminator;
|
||||
if (/MSIE/.test(navigator.userAgent)) {
|
||||
terminator = '\r';
|
||||
}
|
||||
else {
|
||||
terminator = '\n';
|
||||
}
|
||||
result.text.push(terminator);
|
||||
result.pos++;
|
||||
}
|
||||
else {
|
||||
result.tags.push({node: node.cloneNode(false), pos: result.pos});
|
||||
sh_extractTagsFromNodeList(node.childNodes, result);
|
||||
result.tags.push({pos: result.pos});
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
result.text.push(node.data);
|
||||
result.pos += node.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Extracts the tags from the text of an HTML element. The extracted tags will be
|
||||
returned as an array of tag objects. See sh_highlightString for the format of
|
||||
the tag objects.
|
||||
@param element a DOM element
|
||||
@param tags an empty array; the extracted tag objects will be returned in it
|
||||
@return the text of the element
|
||||
@see sh_highlightString
|
||||
*/
|
||||
function sh_extractTags(element, tags) {
|
||||
var result = {};
|
||||
result.text = [];
|
||||
result.tags = tags;
|
||||
result.pos = 0;
|
||||
sh_extractTagsFromNodeList(element.childNodes, result);
|
||||
return result.text.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
Merges the original tags from an element with the tags produced by highlighting.
|
||||
@param originalTags an array containing the original tags
|
||||
@param highlightTags an array containing the highlighting tags - these must not overlap
|
||||
@result an array containing the merged tags
|
||||
*/
|
||||
function sh_mergeTags(originalTags, highlightTags) {
|
||||
var numOriginalTags = originalTags.length;
|
||||
if (numOriginalTags === 0) {
|
||||
return highlightTags;
|
||||
}
|
||||
|
||||
var numHighlightTags = highlightTags.length;
|
||||
if (numHighlightTags === 0) {
|
||||
return originalTags;
|
||||
}
|
||||
|
||||
var result = [];
|
||||
var originalIndex = 0;
|
||||
var highlightIndex = 0;
|
||||
|
||||
while (originalIndex < numOriginalTags && highlightIndex < numHighlightTags) {
|
||||
var originalTag = originalTags[originalIndex];
|
||||
var highlightTag = highlightTags[highlightIndex];
|
||||
|
||||
if (originalTag.pos <= highlightTag.pos) {
|
||||
result.push(originalTag);
|
||||
originalIndex++;
|
||||
}
|
||||
else {
|
||||
result.push(highlightTag);
|
||||
if (highlightTags[highlightIndex + 1].pos <= originalTag.pos) {
|
||||
highlightIndex++;
|
||||
result.push(highlightTags[highlightIndex]);
|
||||
highlightIndex++;
|
||||
}
|
||||
else {
|
||||
// new end tag
|
||||
result.push({pos: originalTag.pos});
|
||||
|
||||
// new start tag
|
||||
highlightTags[highlightIndex] = {node: highlightTag.node.cloneNode(false), pos: originalTag.pos};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (originalIndex < numOriginalTags) {
|
||||
result.push(originalTags[originalIndex]);
|
||||
originalIndex++;
|
||||
}
|
||||
|
||||
while (highlightIndex < numHighlightTags) {
|
||||
result.push(highlightTags[highlightIndex]);
|
||||
highlightIndex++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts tags into text.
|
||||
@param tags an array of tag objects
|
||||
@param text a string representing the text
|
||||
@return a DOM DocumentFragment representing the resulting HTML
|
||||
*/
|
||||
function sh_insertTags(tags, text) {
|
||||
var doc = document;
|
||||
|
||||
var result = document.createDocumentFragment();
|
||||
var tagIndex = 0;
|
||||
var numTags = tags.length;
|
||||
var textPos = 0;
|
||||
var textLength = text.length;
|
||||
var currentNode = result;
|
||||
|
||||
// output one tag or text node every iteration
|
||||
while (textPos < textLength || tagIndex < numTags) {
|
||||
var tag;
|
||||
var tagPos;
|
||||
if (tagIndex < numTags) {
|
||||
tag = tags[tagIndex];
|
||||
tagPos = tag.pos;
|
||||
}
|
||||
else {
|
||||
tagPos = textLength;
|
||||
}
|
||||
|
||||
if (tagPos <= textPos) {
|
||||
// output the tag
|
||||
if (tag.node) {
|
||||
// start tag
|
||||
var newNode = tag.node;
|
||||
currentNode.appendChild(newNode);
|
||||
currentNode = newNode;
|
||||
}
|
||||
else {
|
||||
// end tag
|
||||
currentNode = currentNode.parentNode;
|
||||
}
|
||||
tagIndex++;
|
||||
}
|
||||
else {
|
||||
// output text
|
||||
currentNode.appendChild(doc.createTextNode(text.substring(textPos, tagPos)));
|
||||
textPos = tagPos;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Highlights an element containing source code. Upon completion of this function,
|
||||
the element will have been placed in the "sh_sourceCode" class.
|
||||
@param element a DOM <pre> element containing the source code to be highlighted
|
||||
@param language a language definition object
|
||||
*/
|
||||
function sh_highlightElement(element, language) {
|
||||
sh_addClass(element, 'sh_sourceCode');
|
||||
var originalTags = [];
|
||||
var inputString = sh_extractTags(element, originalTags);
|
||||
var highlightTags = sh_highlightString(inputString, language);
|
||||
var tags = sh_mergeTags(originalTags, highlightTags);
|
||||
var documentFragment = sh_insertTags(tags, inputString);
|
||||
while (element.hasChildNodes()) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
element.appendChild(documentFragment);
|
||||
}
|
||||
|
||||
function sh_getXMLHttpRequest() {
|
||||
if (window.ActiveXObject) {
|
||||
return new ActiveXObject('Msxml2.XMLHTTP');
|
||||
}
|
||||
else if (window.XMLHttpRequest) {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
throw 'No XMLHttpRequest implementation available';
|
||||
}
|
||||
|
||||
function sh_load(language, element, prefix, suffix) {
|
||||
if (language in sh_requests) {
|
||||
sh_requests[language].push(element);
|
||||
return;
|
||||
}
|
||||
sh_requests[language] = [element];
|
||||
var request = sh_getXMLHttpRequest();
|
||||
var url = prefix + 'sh_' + language + suffix;
|
||||
request.open('GET', url, true);
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState === 4) {
|
||||
try {
|
||||
if (! request.status || request.status === 200) {
|
||||
eval(request.responseText);
|
||||
var elements = sh_requests[language];
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
sh_highlightElement(elements[i], sh_languages[language]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw 'HTTP error: status ' + request.status;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
request = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
request.send(null);
|
||||
}
|
||||
|
||||
/**
|
||||
Highlights all elements containing source code on the current page. Elements
|
||||
containing source code must be "pre" elements with a "class" attribute of
|
||||
"sh_LANGUAGE", where LANGUAGE is a valid language identifier; e.g., "sh_java"
|
||||
identifies the element as containing "java" language source code.
|
||||
*/
|
||||
function highlight(prefix, suffix, tag) {
|
||||
var nodeList = document.getElementsByTagName(tag);
|
||||
for (var i = 0; i < nodeList.length; i++) {
|
||||
var element = nodeList.item(i);
|
||||
var htmlClasses = sh_getClasses(element);
|
||||
for (var j = 0; j < htmlClasses.length; j++) {
|
||||
var htmlClass = htmlClasses[j].toLowerCase();
|
||||
if (htmlClass === 'sh_sourcecode') {
|
||||
continue;
|
||||
}
|
||||
if (htmlClass.substr(0, 3) === 'sh_') {
|
||||
var language = htmlClass.substring(3);
|
||||
if (language in sh_languages) {
|
||||
sh_highlightElement(element, sh_languages[language]);
|
||||
}
|
||||
else if (typeof(prefix) === 'string' && typeof(suffix) === 'string') {
|
||||
sh_load(language, element, prefix, suffix);
|
||||
}
|
||||
else {
|
||||
throw 'Found <' + tag + '> element with class="' + htmlClass + '", but no such language exists';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function sh_highlightDocument(prefix, suffix) {
|
||||
highlight(prefix, suffix, 'code');
|
||||
highlight(prefix, suffix, 'pre');
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
.sh_sourceCode .sh_symbol , pre.sh_sourceCode .sh_cbracket {
|
||||
.sh_sourceCode .sh_symbol , .sh_sourceCode .sh_cbracket {
|
||||
color: #bbd;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
.sh_sourceCode .sh_string, pre.sh_sourceCode .sh_regexp, pre.sh_sourceCode .sh_number
|
||||
.sh_sourceCode .sh_string, .sh_sourceCode .sh_regexp, .sh_sourceCode .sh_number
|
||||
{
|
||||
color: #daa;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче