diff options
author | 2022-08-22 00:03:34 -0700 | |
---|---|---|
committer | 2022-08-22 00:04:27 -0700 | |
commit | f5c6875da598cc10ad9a8b54cbb24a21c1418cad (patch) | |
tree | d0ca2a2caed6d45d9f4eac949ba4748a08e91746 | |
parent | eec11a66519bf8be0ac82c5e33382c0838180577 (diff) | |
download | bun-f5c6875da598cc10ad9a8b54cbb24a21c1418cad.tar.gz bun-f5c6875da598cc10ad9a8b54cbb24a21c1418cad.tar.zst bun-f5c6875da598cc10ad9a8b54cbb24a21c1418cad.zip |
38% faster `node:http`
Before:
```fish
❯ oha http://localhost:3000 -z 2s -c 20
Summary:
Success rate: 1.0000
Total: 2.0006 secs
Slowest: 0.0095 secs
Fastest: 0.0000 secs
Average: 0.0003 secs
Requests/sec: 69521.0420
```
After:
```
❯ oha http://localhost:3000 -z 2s -c 20
Summary:
Success rate: 1.0000
Total: 2.0005 secs
Slowest: 0.0063 secs
Fastest: 0.0000 secs
Average: 0.0002 secs
Requests/sec: 109119.8614
```
Code
```
const http = require("http");
const hostname = "127.0.0.1";
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World!");
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
```
-rw-r--r-- | src/bun.js/http.exports.js | 280 |
1 files changed, 203 insertions, 77 deletions
diff --git a/src/bun.js/http.exports.js b/src/bun.js/http.exports.js index b039a14c7..cbaf937b7 100644 --- a/src/bun.js/http.exports.js +++ b/src/bun.js/http.exports.js @@ -1,6 +1,5 @@ const { EventEmitter } = import.meta.require("node:events"); const { Readable, Writable } = import.meta.require("node:stream"); - export function createServer(options, callback) { return new Server(options, callback); } @@ -36,8 +35,8 @@ export class Server extends EventEmitter { listen(...args) { const server = this; const [options, listening_cb] = _normalizeArgs(args); - const res_class = this.#options.ServerResponse || ServerResponse; - const req_class = this.#options.IncomingMessage || IncomingMessage; + const ResponseClass = this.#options.ServerResponse || ServerResponse; + const RequestClass = this.#options.IncomingMessage || IncomingMessage; try { this.#server = Bun.serve({ @@ -45,14 +44,39 @@ export class Server extends EventEmitter { hostname: options.host, fetch(req) { - return new Promise((reply, reject) => { - const http_req = new req_class(req); - const http_res = new res_class({ reply, req: http_req }); - - http_req.once("error", (err) => reject(err)); - http_res.once("error", (err) => reject(err)); - - server.emit("request", http_req, http_res); + var pendingResponse; + var pendingError; + var rejectFunction, resolveFunction; + var reject = (err) => { + if (pendingError) return; + pendingError = err; + if (rejectFunction) rejectFunction(err); + }; + + var reply = function (resp) { + if (pendingResponse) return; + pendingResponse = resp; + if (resolveFunction) resolveFunction(resp); + }; + + const http_req = new RequestClass(req); + const http_res = new ResponseClass({ reply, req: http_req }); + + http_req.once("error", (err) => reject(err)); + http_res.once("error", (err) => reject(err)); + server.emit("request", http_req, http_res); + + if (pendingError) { + throw pendingError; + } + + if (pendingResponse) { + return pendingResponse; + } + + return new Promise((resolve, reject) => { + resolveFunction = resolve; + rejectFunction = reject; }); }, }); @@ -69,38 +93,72 @@ export class Server extends EventEmitter { export class IncomingMessage extends Readable { constructor(req) { - const rawHeaders = []; const method = req.method; - const headers = Object.create(null); - - for (const key of req.headers.keys()) { - const value = req.headers.get(key); - - headers[key] = value; - rawHeaders.push(key, value); - } super(); const url = new URL(req.url); - // TODO: reuse trailer object? - // TODO: get hostname and port from Bun.serve and calculate substring() offset - this._req = req; - this.method = method; - this.complete = false; - this._body_offset = 0; - this.headers = headers; - this._body = undefined; - this._socket = undefined; - this.rawHeaders = rawHeaders; - this.url = url.pathname + url.search; this._no_body = "GET" === method || "HEAD" === method || "TRACE" === method || "CONNECT" === method || - "OPTIONS" === method; + "OPTIONS" === method || + (parseInt(req.headers.get("Content-Length") || "") || 0) === 0; + + this._req = req; + this.method = method; + this.complete = !!this._no_body; + this._body_offset = 0; + + this._body = undefined; + this._socket = undefined; + + this.url = url.pathname; + this.#inputRequest = req; + } + #inputRequest; + #headers; + #rawHeaders; + _consuming = false; + _dumped = false; + + #constructHeaders() { + const rawHeaders = []; + const headers = Object.create(null); + var req = this.#inputRequest; + this.#inputRequest = undefined; + for (const [key, value] of req.headers.entries()) { + headers[key] = value; + rawHeaders.push(key, value); + } + this.#headers = headers; + this.#rawHeaders = rawHeaders; + } + + get headers() { + var _headers = this.#headers; + if (_headers) return _headers; + this.#constructHeaders(); + return this.#headers; + } + + set headers(val) { + this.#headers = val; + return true; + } + + get rawHeaders() { + var _rawHeaders = this.#rawHeaders; + if (_rawHeaders) return _rawHeaders; + this.#constructHeaders(); + return this.#rawHeaders; + } + + set rawHeaders(val) { + this.#rawHeaders = val; + return true; } _construct(callback) { @@ -134,6 +192,10 @@ export class IncomingMessage extends Readable { } get aborted() { + return false; + } + + abort() { throw new Error("not implemented"); } @@ -170,13 +232,14 @@ export class IncomingMessage extends Readable { } get socket() { - if (this._socket) return this._socket; + var _socket = this._socket; + if (_socket) return _socket; - this._socket = new EventEmitter(); - this.on("end", () => duplex.emit("end")); - this.on("close", () => duplex.emit("close")); + this._socket = _socket = new EventEmitter(); + this.on("end", () => _socket.emit("end")); + this.on("close", () => _socket.emit("close")); - return this._socket; + return _socket; } setTimeout(msecs, callback) { @@ -186,53 +249,107 @@ export class IncomingMessage extends Readable { export class ServerResponse extends Writable { constructor({ req, reply }) { - const headers = new Headers(); - const sink = new Bun.ArrayBufferSink(); - sink.start({ stream: false, asUint8Array: true }); - super(); this.req = req; - this._sink = sink; this._reply = reply; this.sendDate = true; this.statusCode = 200; - this._headers = headers; + this.#headers = undefined; this.headersSent = false; this.statusMessage = undefined; - } + this.#controller = undefined; + this.#firstWrite = undefined; + this._writableState.decodeStrings = false; + } + + req; + _reply; + sendDate; + statusCode; + #headers; + headersSent = false; + statusMessage; + #controller; + #firstWrite; + _sent100 = false; + _defaultKeepAlive = false; + _removedConnection = false; + _removedContLen = false; _write(chunk, encoding, callback) { - this.headersSent = true; - this._sink.write(chunk); + if (!this.#firstWrite && !this.headersSent) { + this.#firstWrite = chunk; + callback(); + return; + } - callback(); + this.#ensureReadableStreamController((controller) => { + controller.write(chunk); + callback(); + }); } _writev(chunks, callback) { - this.headersSent = true; - - for (const chunk of chunks) { - this._sink.write(chunk.chunk); + if (chunks.length === 1 && !this.headersSent && !this.#firstWrite) { + this.#firstWrite = chunks[0].chunk; + callback(); + return; } - callback(); + this.#ensureReadableStreamController((controller) => { + for (const chunk of chunks) { + controller.write(chunk.chunk); + } + + callback(); + }); } - _final(callback) { - callback(); + #ensureReadableStreamController(run) { + var thisController = this.#controller; + if (thisController) return run(thisController); this.headersSent = true; + var firstWrite = this.#firstWrite; + this.#firstWrite = undefined; + this._reply( + new Response( + new ReadableStream({ + type: "direct", + pull: (controller) => { + this.#controller = controller; + if (firstWrite) controller.write(firstWrite); + firstWrite = undefined; + run(controller); + }, + }), + { + headers: this.#headers, + status: this.statusCode, + statusText: this.statusMessage ?? STATUS_CODES[this.statusCode], + } + ) + ); + } - if (this.sendDate && !this._headers.has("date")) { - this._headers.set("date", new Date().toUTCString()); + _final(callback) { + if (!this.headersSent) { + var data = this.#firstWrite || ""; + this.#firstWrite = undefined; + this._reply( + new Response(data, { + headers: this.#headers, + status: this.statusCode, + statusText: this.statusMessage ?? STATUS_CODES[this.statusCode], + }) + ); + callback && callback(); + return; } - this._reply( - new Response(this._sink.end(), { - headers: this._headers, - status: this.statusCode, - statusText: this.statusMessage ?? STATUS_CODES[this.statusCode], - }) - ); + this.#ensureReadableStreamController((controller) => { + controller.close(); + callback(); + }); } get socket() { @@ -294,23 +411,29 @@ export class ServerResponse extends Writable { flushHeaders() {} removeHeader(name) { - this._headers.delete(name); + var headers = (this.#headers ||= new Headers()); + headers.delete(name); } getHeader(name) { - return this._headers.get(name); + var headers = (this.#headers ||= new Headers()); + return headers.get(name); } hasHeader(name) { - return this._headers.has(name); + var headers = (this.#headers ||= new Headers()); + return headers.has(name); } getHeaderNames() { - return Array.from(this._headers.keys()); + var headers = (this.#headers ||= new Headers()); + return Array.from(headers.keys()); } setHeader(name, value) { - this._headers.set(name, value); + var headers = (this.#headers ||= new Headers()); + + headers.set(name, value); return this; } @@ -322,13 +445,8 @@ export class ServerResponse extends Writable { } getHeaders() { - const headers = Object.create(null); - - for (const key of this._headers.keys()) { - headers[key] = this._headers.get(key); - } - - return headers; + if (!this.#headers) return {}; + return Object.fromEntries(this.#headers.entries()); } } @@ -529,8 +647,7 @@ function _writeHead(statusCode, reason, obj, response) { } } } - -export default { +var defaultObject = { Server, METHODS, STATUS_CODES, @@ -538,3 +655,12 @@ export default { ServerResponse, IncomingMessage, }; + +var wrapper = + (0, + function () { + return defaultObject; + }); + +wrapper[Symbol.for("CommonJS")] = true; +export default wrapper; |