Skip to content
This repository has been archived by the owner on Jul 3, 2020. It is now read-only.

Node http module - WIP #103

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion js/__loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@
stream: './modules/stream.js',
inherits: './modules/inherits.js',
sys: 'util/util.js',
util: 'util/util.js'
util: 'util/util.js',
http: './modules/http.js'
/* eslint-enable camelcase */
}, runtimePackagePath);

Expand Down
2 changes: 1 addition & 1 deletion js/modules/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function lookup(hostname, opts, cb) {
if (!opts.all && i === 0) {
var addr = res.address.join('.');
if (cb) cb(null, addr, 4);
break;
return;
} else {
switch (res.record) {
case 'A':
Expand Down
244 changes: 244 additions & 0 deletions js/modules/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
'use strict';
//
// this entire file feels extremly hackish, but whatever
//

const EventEmitter = require('events');
const stream = require('stream');
const eshttp = require('eshttp');
const net = require('net');
const url = require('url');
const dns = require('dns');

const or = (...objs) => {
for (let obj of objs) if (obj !== undefined && obj !== null) return obj;
}

class ClientRequest extends stream.Writable {
constructor() {
super();
this._body = '';
this._aborted = false;
this._method = '';
this._path = '';
this._headers = {};
this._handle = null;
this._interalListeners = [];
this._resolved = false;
}
_emitInterals() {
this._resolved = true;
let listener = null;
while (listener = this._interalListeners.shift()) listener();
}
_write(chunk, encoding, callback) {
const cb = () => {
this._body += chunk;
callback();
}
if (this._resolved) {
cb();
} else {
this._interalListeners.push(cb);
}
}
end(data, encoding, callback) {
const cb = () => {
super.end(data, encoding, callback);
this._handle.request(new eshttp.HttpRequest(this._method, this._path, this._headers), (err, response) => {
if (this._aborted) return;
if (err) return this.emit('error', err);
this.emit('response', new IncomingMessage(false, response));
});
this._handle.close();
}
if (this._resolved) {
cb();
} else {
this._interalListeners.push(cb);
}
}
abort() {
const cb = () => this._handle.close();
if (this._resolved) {
cb();
} else {
this._interalListeners.push(cb);
}
}
flushHeaders() {
// to be implemented
}
setNoDelay(noDelay) {
// to be implemented
}
setSocketKeepAlive(enable, initialDelay) {
// to be implemented
}
setTimeout(timeout, callback) {
// to be implemented
}
}

class Server extends net.Server {
constructor() {
super();
this._handle2 = new eshttp.HttpServer();
this.on('connection', (socket) => {
socket.on('data', (data) => this._handle2._dataHandler(socket._handle, data));
socket.on('end', () => this._handle2._endHandler(socket._handle));
socket.on('close', () => this._handle2._closeHandler(socket._handle));
socket.on('error', () => null);
this._handle2._connectionHandler(socket._handle);
});
this._handle2._handle = this._handle;
this._handle2.onrequest = (req) => this.emit('request', new IncomingMessage(true, req), new ServerResponse(req));
}
}

class ServerResponse extends stream.Writable {
constructor(request) {
super();
this._handle = new eshttp.HttpResponse(200, {}, '');
this._reqHandle = request;
this.finished = false;
this.sendDate = true; // doesn't matter what it's set to, eshttp always appends the date header
this._sent = false;
}
_write(chunk, encoding, cb) {
this._handle._body += String(chunk);
if (!this._sent) {
this._sent = true;
this.writeHead(this.statusCode, this.statusMessage);
}
cb(null);
}
end(data, encoding, cb) {
super.end(data, encoding, cb);
this._reqHandle.respondWith(this._handle);
this.finished = true;
}
addTrailers(headers) {
if (this._handle._parser) for (let key of Object.keys(headers)) this._handle._parser._addTrailer(key, headers[key]);
}
getHeader(name) {
return this._handle._headers.get(name);
}
get headersSent() {
return this._sent;
}
removeHeader(name) {
this._handle._headers.delete(name);
}
setHeader(name, val) {
this._handle._headers.set(name, val);
}
get statusCode() {
return this._handle.statusCode;
}
get statusMessage() {
return this._handle.statusMessage;
}
set statusCode(code) {
this._handle._code = code;
}
set statusMessage(msg) {
if (this._handle._parser) this._handle._parser._phrase = statusMessage || this.statusMessage;
}
writeHead(statusCode, statusMessage, headers) {
this._handle._code = statusCode || this.statusCode;
if (this._handle._parser) this._handle._parser._phrase = statusMessage || this.statusMessage;
if (headers) for (let key of Objects.keys(headers)) this._handle._headers.set(key, headers[key]);
}
}

class IncomingMessage extends stream.Readable {
constructor(server, reqOrRes) {
super();
this._handle = reqOrRes;
this._ms = null;
this._ontimeout = () => null;
this._server = server;
if (!this._server) {
this._handle.ondata = (data) => this.push(new Buffer(data));
this._handle.onend = () => this.push(null);
}
}
_read(size) {
// just like net, we can't force a read. do nothing.
}
get headers() {
const headers = {};
for (let header of this._handle._headers) headers[header[0]] = header[1];
return headers;
}
get httpVersion() {
return this._handle.httpVersion;
}
get method() {
return (this._server) ? this._handle.method : undefined;
}
get rawHeaders() {
const headers = [];
for (let header of this._handle._headers) headers.push(header[0], header[1]);
return headers;
}
get rawTrailers() {
return []; // for now
}
setTimeout(ms, cb) {
this._ms = ms;
this._ontimeout = cb;
}
get statusCode() {
return (!this._server) ? this._handle.statusCode : undefined;
}
get statusMessage() {
return (!this._server) ? this._handle.statusMessage : undefined;
}
get socket() {
return null; // for now
}
get trailers() {
return (!this._server) ? this._handle.trailers : undefined;
}
get url() {
return (this._server) ? this._handle.path : undefined;
}
}

exports.ClientRequest = ClientRequest;
exports.Server = Server;
exports.ServerResponse = ServerResponse;
exports.IncomingMessage = IncomingMessage;
exports.createServer = (cb) => {
const server = new Server();
if (cb) server.on('request', cb);
return server;
}
exports.request = (opt, cb) => {
if (typeof opt === 'string') opt = url.parse(opt);
let protocol = or(opt.protocol, 'http:');
if (protocol !== 'http:') throw new Error(`Protocol "${protcol}" not supported. Expected "http:"`);
let ip = or(opt.hostname, opt.host, 'localhost');
let port = or(opt.port, 80);
let req = new ClientRequest();
req._headers = or(opt.headers, {});
req._method = or(opt.method, 'GET');
req._path = or(opt.path, '/');
let onresolved = () => {
req._handle = new eshttp.HttpClient(ip, port);
req._emitInterals();
}
if (net.isIP(ip)) {
onresolved();
} else {
dns.lookup(opt.host, (err, address) => {
if (err) return req.emit('error', err);
ip = address;
onresolved();
});
}
if (cb) req.on('response', cb);
return req;
}
7 changes: 4 additions & 3 deletions js/modules/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function rmFromArrayByVal(array, val) {
}

class Server extends EventEmitter {
constructor(runtimeServer) {
constructor(opts, connectionListener, runtimeServer) {
super();
this._handle = runtimeServer || new runtime.net.TCPServerSocket();
this._connections = [];
Expand All @@ -39,6 +39,7 @@ class Server extends EventEmitter {
this.close();
}
this._handle.onlisten = () => this.emit('listening');
if (connectionListener) this.on('connection', connectionListener);
}
address() {
return {
Expand All @@ -48,7 +49,7 @@ class Server extends EventEmitter {
}
}
close(cb) {
this.once('close', cb);
if (cb) this.once('close', cb);
this._handle.close();
}
get connections() {
Expand Down Expand Up @@ -76,7 +77,7 @@ class Server extends EventEmitter {
port = null;
}
options.port = port;
this.once('listening', callback);
if (callback) this.once('listening', callback);
this._handle.listen(options.port);
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"typeutils": "^1.0.1",
"u8-view": "^1.0.0",
"url": "^0.11.0",
"util": "^0.10.3"
"util": "^0.10.3",
"eshttp": "^0.5.0"
}
}