Skip to content

Commit

Permalink
Add http middleware and websocket upgrade on existing listener
Browse files Browse the repository at this point in the history
  • Loading branch information
wilberforce committed Dec 21, 2021
1 parent 2f93df2 commit 7b60140
Show file tree
Hide file tree
Showing 8 changed files with 547 additions and 10 deletions.
29 changes: 19 additions & 10 deletions modules/network/http/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class Request {
}

close() {
this.socket?.close();
if( 16 & this.flags == 0) this.socket?.close();
delete this.socket;
delete this.buffers;
delete this.callback;
Expand Down Expand Up @@ -452,7 +452,7 @@ function done(error = false, data) {
state:
0 - connecting
1 - receieving request status
1 - receiving request status
2 - receiving request headers
Expand Down Expand Up @@ -661,8 +661,10 @@ function server(message, value, etc) {
const status = response?.status ?? 200;
const message = response?.reason?.toString() ?? reason(status);
const parts = ["HTTP/1.1 ", status.toString(), " ", message, "\r\n",
"connection: ", "close\r\n"];

"connection: ", "close\r\n"];
if ( status === 101 ) {
this.flags = 16;
}
if (response) {
let byteLength;

Expand All @@ -682,11 +684,13 @@ function server(message, value, etc) {
this.flags = 4;
}
else {
this.flags = 1;
let count = 0;
if (this.body)
count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell
parts.push("content-length: ", count.toString(), "\r\n");
if ( this.flags !== 16 ) {
this.flags = 1;
let count = 0;
if (this.body)
count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell
parts.push("content-length: ", count.toString(), "\r\n");
}
}
}
else
Expand All @@ -702,6 +706,11 @@ function server(message, value, etc) {
if (count > (socket.write() - ((2 & this.flags) ? 8 : 0)))
return;
}

if ( this.flags === 16 ) {
this.state = 10;
return;
}
}
if (8 === this.state) {
let body = this.body;
Expand Down Expand Up @@ -741,7 +750,7 @@ function server(message, value, etc) {
}
finally {
this.server.connections.splice(this.server.connections.indexOf(this), 1);
this.close();
if ( this.flags !== 16 ) this.close();
}
}
}
Expand Down
75 changes: 75 additions & 0 deletions modules/network/http/middleware/hotspot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016-2021 Moddable Tech, Inc.
* Copyright (c) Wilberforce
*
* This file is part of the Moddable SDK.
*
* This work is licensed under the
* Creative Commons Attribution 4.0 International License.
* To view a copy of this license, visit
* <http://creativecommons.org/licenses/by/4.0>.
* or send a letter to Creative Commons, PO Box 1866,
* Mountain View, CA 94042, USA.
*
*/

import { Middleware, Server } from "middleware/server";
import Net from "net"

const hotspot = new Map;

// iOS 8/9
hotspot.set("/library/test/success.html",{status: 302,body: "Success"});
hotspot.set("/hotspot-detect.html",{status: 302,body: "Success"});

// Windows
hotspot.set("/ncsi.txt",{status: 302,body: "Microsoft NCSI"});
hotspot.set("/connecttest.txt",{status: 302,body: "Microsoft Connect Test"});
hotspot.set("/redirect",{status: 302,body: ""}); // Win 10

// Android
hotspot.set("/mobile/status.php", {status:302}); // Android 8.0 (Samsung s9+)
hotspot.set("/generate_204", {status:302}); // Android actual redirect
hotspot.set("/gen_204", {status:204}); // Android 9.0

export class MiddlewareHotspot extends Middleware {
constructor() {
super();
}
handler(req, message, value, etc) {
switch (message) {
case Server.status:

req.redirect=hotspot.get(value); // value is path
if ( req.redirect) return; // Hotspot url match
delete req.redirect;
return this.next?.handler(req, message, value, etc);
case Server.header: {
if ( "host" === value ) {
req.host=etc;
trace(`MiddlewareHotspot: http://${req.host}${req.path}\n`);
}
return this.next?.handler(req, message, value, etc);
}
case Server.prepareResponse:

if( req.redirect) {
let apIP=Net.get("IP", "ap");
let redirect={
headers: [ "Content-type", "text/plain", "Location",`http://${apIP}`],
...req.redirect
};
trace(`Hotspot match: http://${req.host}${req.path}\n`);
trace(JSON.stringify(redirect),'\n');

return redirect;
}
}
return this.next?.handler(req, message, value, etc);
}
}
Object.freeze(hotspot);

/* TO DO
add dns constructor flag. then becomes self contained.
*/
23 changes: 23 additions & 0 deletions modules/network/http/middleware/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"modules": {
"*": [
"$(MODULES)/files/zip/*",
"$(MODULES)/network/http/*",
"$(MODULES)/data/base64/*",
"$(MODULES)/data/hex/*",
"$(MODULES)/data/logical/*"

],
"dns/server": "$(MODULES)/network/dns/dnsserver",
"websocket/websocket": "$(MODULES)/network/websocket/websocket"
},
"preload": [
"http",
"dns/server",
"websocket/websocket",
"base64",
"hex",
"logical",
"websocket"
]
}
40 changes: 40 additions & 0 deletions modules/network/http/middleware/rewritespa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2016-2021 Moddable Tech, Inc.
* Copyright (c) Wilberforce
*
* This file is part of the Moddable SDK.
*
* This work is licensed under the
* Creative Commons Attribution 4.0 International License.
* To view a copy of this license, visit
* <http://creativecommons.org/licenses/by/4.0>.
* or send a letter to Creative Commons, PO Box 1866,
* Mountain View, CA 94042, USA.
*
*/

import { Middleware, Server } from "middleware/server";

// Single Page application. Map page requests back to /

export class MiddlewareRewriteSPA extends Middleware {
#routes
constructor(routes) {
super();
this.#routes=routes
}

handler(req, message, value, etc) {

switch (message) {
case Server.status:
if ( this.#routes.includes(value) ) { // To do: possibly use Set?
trace(`Rewrite: ${value}\n`);
value='/';
trace(`Rewrite now: ${value}\n`);
}
break;
}
return this.next?.handler(req, message, value, etc);
}
}
75 changes: 75 additions & 0 deletions modules/network/http/middleware/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016-2021 Moddable Tech, Inc.
* Copyright (c) Wilberforce
*
* This file is part of the Moddable SDK.
*
* This work is licensed under the
* Creative Commons Attribution 4.0 International License.
* To view a copy of this license, visit
* <http://creativecommons.org/licenses/by/4.0>.
* or send a letter to Creative Commons, PO Box 1866,
* Mountain View, CA 94042, USA.
*
*/

import {Server} from "http"

class Middleware {
#next;
constructor() {
this.#next = null
}
get next() {
return this.#next;
}
set next(n) {
this.#next = n;
}
}

class WebServer extends Server {
#last;
#first;

constructor(options) {
super(options);

this.#first = null;
this.#last = null;
}

use( handler ) {
// chaining here, join new to previous
if( this.#first === null ) {
this.#first=this.#last=handler;
} else {
this.#last.next=handler;
this.#last=handler;
}
// Advise middleware of http parent - needed by websocket for connection management
if ( 'parent' in handler )
handler.parent=this;
}

callback(message, value, etc) {
switch (message) {
case Server.status:
this.path=value;
this.method = etc;
break;
case Server.header:
value=value.toLowerCase(); // Header field names are case-insensitive - force lower for easy compare
}
return this.server.#first?.handler(this, message, value, etc);
}

close() {
if ( this.connections )
super.close(); // Close any http connections
}
}

Object.freeze(WebServer.prototype);

export { WebServer as Server, Middleware };
101 changes: 101 additions & 0 deletions modules/network/http/middleware/staticzip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (c) 2016-2021 Moddable Tech, Inc.
* Copyright (c) Wilberforce
*
* This file is part of the Moddable SDK.
*
* This work is licensed under the
* Creative Commons Attribution 4.0 International License.
* To view a copy of this license, visit
* <http://creativecommons.org/licenses/by/4.0>.
* or send a letter to Creative Commons, PO Box 1866,
* Mountain View, CA 94042, USA.
*
*/

import { Middleware, Server } from "middleware/server";
import {ZIP} from "zip"

const mime = new Map;
mime.set("js", "application/javascript");
mime.set("css", "text/css");
mime.set("ico", "image/vnd.microsoft.icon");
mime.set("txt", "text/plain");
mime.set("htm", "text/html");
mime.set("html", "text/html");
mime.set("svg", "image/svg+xml");
mime.set("png", "image/png");
mime.set("gif", "image/gif");
mime.set("webp", "image/webp");
mime.set("jpg", "image/jpeg");
mime.set("jpeg", "image/jpeg");

export class MiddlewareStaticZip extends Middleware {
constructor(archive) {
super();

this.archive = new ZIP(archive);
}

handler(req, message, value, etc) {
switch (message) {
case Server.status:
// redirect home page
if (value === '/') value='/index.html';
req.path = value;
try {
req.data = this.archive.file(req.path.slice(1)); // drop leading / to match zip content
req.etag = "mod-" + req.data.crc.toString(16);
}
catch {
delete req.data;
delete req.etag;
return this.next?.handler(req, message, value, etc);
}
break;

case Server.header:
req.match ||= ("if-none-match" === value) && (req.etag === etc);
return this.next?.handler(req, message, value, etc);

case Server.prepareResponse:
if (req.match) {
return {
status: 304,
headers: [
"ETag", req.etag,
]
};
}
if (!req.data) {
trace(`prepareResponse: missing file ${req.path}\n`);

return this.next?.handler(req, message, value, etc);
}

req.data.current = 0;
const result = {
headers: [
"Content-type", mime.get(req.path.split('.').pop()) ?? "text/plain",
"Content-length", req.data.length,
"ETag", req.etag,
"Cache-Control", "max-age=60"
],
body: true
}
if (8 === req.data.method) // Compression Method
result.headers.push("Content-Encoding", "deflate");
return result;

case Server.responseFragment:
if (req.data.current >= req.data.length)
return;

const chunk = req.data.read(ArrayBuffer, (value > 1536) ? 1536 : value);
req.data.current += chunk.byteLength;
return chunk;
}
}
}

Object.freeze(mime);
Loading

0 comments on commit 7b60140

Please sign in to comment.