Skip to content

Add cors and options support #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"[haxe]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.sortImports": true
"source.sortImports": "explicit"
}
},
"testExplorer.codeLens": false
Expand Down
4 changes: 4 additions & 0 deletions haxe_libraries/hxcs.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# @install: lix --silent download "haxelib:/hxcs#4.2.0" into hxcs/4.2.0/haxelib
# @run: haxelib run-dir hxcs "${HAXE_LIBCACHE}/hxcs/4.2.0/haxelib"
-cp ${HAXE_LIBCACHE}/hxcs/4.2.0/haxelib/
-D hxcs=4.2.0
7 changes: 7 additions & 0 deletions haxe_libraries/hxnodejs.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @install: lix --silent download "haxelib:/hxnodejs#12.2.0" into hxnodejs/12.2.0/haxelib
-cp ${HAXE_LIBCACHE}/hxnodejs/12.2.0/haxelib/src
-D hxnodejs=12.2.0
--macro allowPackage('sys')
# should behave like other target defines and not be defined in macro context
--macro define('nodejs')
--macro _internal.SuppressDeprecated.run()
4 changes: 3 additions & 1 deletion tests.hxml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
-cp tests
-main Test
--macro nullSafety("weblink._internal.ds", StrictThreaded)
-hl test.hl
#-hl test.hl
--js test.js
-L hxnodejs
5 changes: 5 additions & 0 deletions tests/sys/thread/Thread.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#if js
function create(f) {
Timers.setImmdiate(f);
}
#end
5 changes: 5 additions & 0 deletions testscs.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-cp tests
-main Test
--macro nullSafety("weblink._internal.ds", StrictThreaded)
#-hl test.hl
--cs testcs
7 changes: 7 additions & 0 deletions testsjs.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-cp tests
-main Test
--macro nullSafety("weblink._internal.ds", StrictThreaded)
#-hl test.hl
--js test.js
-L hxnodejs
-D allow_deasync
25 changes: 19 additions & 6 deletions weblink/Request.hx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import haxe.ds.StringMap;
import haxe.http.HttpMethod;
import haxe.io.Bytes;
import weblink._internal.Server;
import weblink._internal.ds.FieldMap;

class Request {
public var cookies:List<Cookie>;
// these exclude the query string
public var path:String;
// 1st segment only
public var basePath:String;
// this includes the query string
public var url:String;

/** Contains values for parameters declared in the route matched, if there are any. **/
public var routeParams:Map<String, String>;
public var routeParams:FieldMap<String, String>;

public var ip:String;
public var baseUrl:String;

public var headers:StringMap<String>;
public var text:String;
public var method:HttpMethod;
Expand All @@ -37,18 +45,23 @@ class Request {
var first = lines[0];
var index = first.indexOf("/");
path = first.substring(index, first.indexOf(" ", index + 1));
url = path; // preserve the url path;
var q = path.indexOf("?", 1);
if (q < 0)
q = path.length;
path = path.substring(0, q);
var index2 = path.indexOf("/", 1);
var index3 = path.indexOf("?", 1);
if (index2 == -1)
index2 = index3;
if (index2 != -1) {
basePath = path.substr(0,index2);
}else{
basePath = path.substr(0, index2);
} else {
basePath = path;
}
// trace(basePath);
// trace(path);
//trace(first.substring(0, index - 1).toUpperCase());
// trace(first.substring(0, index - 1).toUpperCase());
method = first.substring(0, index - 1).toUpperCase();
for (i in 0...lines.length - 1) {
if (lines[i] == "") {
Expand Down Expand Up @@ -143,11 +156,11 @@ class Request {
final r = ~/(?:\?|&|;)([^=]+)=([^&|;]+)/;
var obj = {};
var init:Bool = true;
var string:String = path;
var string:String = url;
while (r.match(string)) {
if (init) {
var pos = r.matchedPos().pos;
path = path.substring(0, pos);
url = url.substring(0, pos);
init = false;
}
// 0 entire, 1 name, 2 value
Expand Down
11 changes: 11 additions & 0 deletions weblink/Response.hx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package weblink;

import haxe.Json;
import haxe.http.HttpStatus;
import haxe.io.Bytes;
import haxe.io.Encoding;
Expand Down Expand Up @@ -63,9 +64,19 @@ class Response {
this.sendBytes(Bytes.ofString(data, Encoding.UTF8));
}

public inline function json(data:Dynamic, pretty = false) {
send(if (pretty) Json.stringify(data, null, " ") else Json.stringify(data));
}


public dynamic function onclose() {

}

private function end() {
this.server = null;
final socket = this.socket;
onclose();
if (socket != null) {
if (this.close) {
socket.close();
Expand Down
39 changes: 33 additions & 6 deletions weblink/Weblink.hx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ using haxe.io.Path;
class Weblink {
public var server:Null<Server>;
public var routeTree:RadixTree<Handler>;
public var allowed_methods = new Map<HttpMethod, Bool>();

private var middlewareToChain:Array<Middleware> = [];

Expand All @@ -26,10 +27,18 @@ class Weblink {
response.send("Error 404, Route Not found.");
}

public function cors_middleware(request:Request, response:Response):Void {
response.headers = new List<Header>();
response.headers.add({key: "Access-Control-Allow-Origin", value: cors});
response.headers.add({key: "Access-Control-Allow-Headers", value: "*"});
}

var _serve:Bool = false;
var _path:String;
var _dir:String;
var _cors:String = "*";

public var cors:String = "*";
public var allowed_methods_string = "";

public function new() {
this.routeTree = new RadixTree();
Expand Down Expand Up @@ -64,33 +73,51 @@ class Weblink {
this.routeTree.put(path, method, chainMiddleware(handler));
}

public function enable_cors(_cors:String) {
cors = _cors;
this.middlewareToChain.push(cors_middleware);
}

public function get(path:String, func:Handler, ?middleware:Middleware) {
if (middleware != null) {
func = middleware(func);
}
allowed_methods[Get] = true;
_updateRoute(path, Get, func);
}

public function post(path:String, func:Handler) {
allowed_methods[Post] = true;
_updateRoute(path, Post, func);
}

public function put(path:String, func:Handler) {
allowed_methods[Put] = true;
_updateRoute(path, Put, func);
}

public function head(path:String, func:Handler) {
allowed_methods[Head] = true;
_updateRoute(path, Head, func);
}

public function listen(port:Int, blocking:Bool = true) {
this.pathNotFound = chainMiddleware(this.pathNotFound);
allowed_methods[Options] = true;
var allowed_methods_array = new Array<String>();
for (k => v in allowed_methods) {
if (v) {
allowed_methods_array.push(k);
}
}

allowed_methods_string = allowed_methods_array.join(", ");
server = new Server(port, this);
server.update(blocking);
}

public function serve(path:String = "", dir:String = "", cors:String = "*") {
_cors = cors;
this.cors = cors;
_path = path;
_dir = dir;
_serve = true;
Expand Down Expand Up @@ -123,14 +150,14 @@ class Weblink {

private inline function _serveEvent(request:Request, response:Response):Bool {
if (request.path.charAt(0) == "/")
request.path = request.basePath.substr(1);
request.path = request.path.substr(1);
var ext = request.path.extension();
var mime = weblink._internal.Mime.types.get(ext);
response.headers = new List<Header>();
if (_cors.length > 0)
response.headers.add({key: "Access-Control-Allow-Origin", value: _cors});
if (cors.length > 0)
response.headers.add({key: "Access-Control-Allow-Origin", value: cors});
response.contentType = mime == null ? "text/plain" : mime;
var path = Path.join([_dir, request.basePath.substr(_path.length)]).normalize();
var path = Path.join([_dir, request.path.substr(_path.length-1)]).normalize();
if (path == "")
path = ".";
if (sys.FileSystem.exists(path)) {
Expand Down
63 changes: 53 additions & 10 deletions weblink/_internal/Server.hx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,47 @@ package weblink._internal;
import haxe.MainLoop;
import haxe.http.HttpMethod;
import haxe.io.Bytes;
import hl.uv.Loop.LoopRunMode;
import hl.uv.Stream;
import sys.net.Host;
import weblink._internal.Socket;
import weblink._internal.ds.FieldMap;

using Lambda;

#if hl
import hl.uv.Loop.LoopRunMode;
import hl.uv.Loop;
import hl.uv.Stream;
#else
class Loop {
public function new() {}

public static function getDefault():Loop {
return new Loop();
}

public function stop() {}

public function run(a) {}
}
#end

class Server extends SocketServer {
// var sockets:Array<Socket>;
var parent:Weblink;
var stream:Stream;

// var stream:Stream;
public var running:Bool = true;
var loop:hl.uv.Loop;

var loop:Loop;

public function new(port:Int, parent:Weblink) {
// sockets = [];
loop = hl.uv.Loop.getDefault();
loop = Loop.getDefault();
super(loop);
bind(new Host("0.0.0.0"), port);
noDelay(true);
listen(100, function() {
stream = accept();
var stream = accept();
var socket:Socket = cast stream;
var request:Request = null;
var done:Bool = false;
Expand Down Expand Up @@ -76,6 +97,13 @@ class Server extends SocketServer {
private function complete(request:Request, socket:Socket) {
@:privateAccess var response = request.response(this, socket);

if (request.method == Options) {
if (parent.cors.length > 0)
parent.cors_middleware(request, response);
response.send("Allow: " + parent.allowed_methods_string);
return;
}

if (request.method == Get
&& @:privateAccess parent._serve
&& response.status == OK
Expand All @@ -85,15 +113,27 @@ class Server extends SocketServer {
}
}

var execute = (handler) -> {
try {
handler(request, response);
} catch (ex) {
trace(ex, ex.stack);
// TODO check PRODUCTION env var
response.status = 500;
response.send(ex.toString() + "\n" + ex.stack);
}
}

switch (parent.routeTree.tryGet(request.basePath, request.method)) {
case Found(handler, params):
request.routeParams = params;
handler(request, response);
request.routeParams = new FieldMap(params);
execute(handler);

case _:
switch (parent.routeTree.tryGet(request.path, request.method)) {
case Found(handler, params):
request.routeParams = params;
handler(request, response);
request.routeParams = new FieldMap(params);
execute(handler);
case _:
@:privateAccess parent.pathNotFound(request, response);
}
Expand All @@ -103,6 +143,9 @@ class Server extends SocketServer {
public function update(blocking:Bool = true) {
do {
@:privateAccess MainLoop.tick(); // for timers
#if (js||cs)
var NoWait = 0;
#end
loop.run(NoWait);
} while (running && blocking);
}
Expand Down
Loading