From e9ce6d68d117bcd3181b4b7a6a1bf14922ce149e Mon Sep 17 00:00:00 2001 From: Theo Butler Date: Thu, 2 Jan 2020 19:52:06 -0500 Subject: [PATCH] replace router mux --- Makefile | 6 +- jennet/_test.pony | 48 +--------- jennet/context.pony | 8 +- jennet/jennet.pony | 6 +- jennet/multiplexer.pony | 195 ---------------------------------------- jennet/mux.pony | 35 ++++++++ jennet/radix/_test.pony | 3 + jennet/router.pony | 25 +++--- 8 files changed, 62 insertions(+), 264 deletions(-) delete mode 100644 jennet/multiplexer.pony create mode 100644 jennet/mux.pony diff --git a/Makefile b/Makefile index 85d8c06..5f16869 100644 --- a/Makefile +++ b/Makefile @@ -26,8 +26,8 @@ build/$(config)/test: PONYC_FLAGS += --bin-name=test build/$(config)/test: .deps build jennet/*.pony stable env ${PONYC} ${PONYC_FLAGS} jennet -build/$(config)/examples: - mkdir -p build/$(config)/examples +build/$(config): + mkdir -p build/$(config) build: mkdir -p build/$(config) @@ -38,7 +38,7 @@ build: test: build/$(config)/test build/$(config)/test -examples: build/$(config)/examples .deps build jennet/*.pony examples/*/*.pony +examples: build/$(config) .deps build jennet/*.pony examples/*/*.pony stable env ${PONYC} ${PONYC_FLAGS} examples/basicauth stable env ${PONYC} ${PONYC_FLAGS} examples/params stable env ${PONYC} ${PONYC_FLAGS} examples/servedir diff --git a/jennet/_test.pony b/jennet/_test.pony index 55fcc35..58072a4 100644 --- a/jennet/_test.pony +++ b/jennet/_test.pony @@ -2,59 +2,15 @@ use "collections" use "encode/base64" use "http" use "ponytest" +use radix = "radix" actor Main is TestList new create(env: Env) => PonyTest(env, this) - new make() => None fun tag tests(test: PonyTest) => - test(_TestMultiplexer) + radix.Main.make().tests(test) test(_TestBasicAuth) -class iso _TestMultiplexer is UnitTest - fun name(): String => "Multiplexer" - - fun apply(h: TestHelper) ? => - let ts = recover Array[(String, _HandlerGroup)] end - ts.push(("/", _HandlerGroup(_TestHandler("0")))) - ts.push(("/foo", _HandlerGroup(_TestHandler("1")))) - ts.push(("/:foo", _HandlerGroup(_TestHandler("2")))) - ts.push(("/foo/bar/", _HandlerGroup(_TestHandler("3")))) - ts.push(("/baz/bar", _HandlerGroup(_TestHandler("4")))) - ts.push(("/:foo/baz", _HandlerGroup(_TestHandler("5")))) - ts.push(("/foo/bar/*baz", _HandlerGroup(_TestHandler("6")))) - let tests = recover val consume ts end - let routes = recover Array[_Route] end - for (p, hg) in tests.values() do - routes.push(_Route("GET", p, hg)) - end - let mux = recover val _Multiplexer(consume routes)? end - - (var hg, var ps) = mux("GET", "/")? - h.assert_eq[String]("0", (hg.handler as _TestHandler val).msg) - - (hg, ps) = mux("GET", "/foo")? - h.assert_eq[String]("1", (hg.handler as _TestHandler val).msg) - - (hg, ps) = mux("GET", "/stuff")? // TODO error in non-debug mode - h.assert_eq[String]("2", (hg.handler as _TestHandler val).msg) - h.assert_eq[String]("stuff", ps("foo")?) - - h.assert_error({()(mux) ? => mux("GET", "/foo/bar")? }) - (hg, ps) = mux("GET", "/foo/bar/")? - h.assert_eq[String]("3", (hg.handler as _TestHandler val).msg) - - (hg, ps) = mux("GET", "/baz/bar")? - h.assert_eq[String]("4", (hg.handler as _TestHandler val).msg) - - (hg, ps) = mux("GET", "/stuff/baz")? - h.assert_eq[String]("5", (hg.handler as _TestHandler val).msg) - h.assert_eq[String]("stuff", ps("foo")?) - - (hg, ps) = mux("GET", "/foo/bar/stuff/and/things")? - h.assert_eq[String]("6", (hg.handler as _TestHandler val).msg) - h.assert_eq[String]("stuff/and/things", ps("baz")?) - class iso _TestBasicAuth is UnitTest fun name(): String => "BasicAuth" diff --git a/jennet/context.pony b/jennet/context.pony index 0570efd..bff987e 100644 --- a/jennet/context.pony +++ b/jennet/context.pony @@ -2,20 +2,18 @@ use "collections" use "http" use "time" -// TODO Separate map in context for iso values? - class iso Context """ Contains the data passed between middleware and the handler. """ let _responder: Responder - let _params: Map[String, String] + let _params: Map[String, String] val let _data: Map[String, Any val] let _start_time: U64 - new iso create(responder': Responder, params': Map[String, String] iso) => + new iso create(responder': Responder, params': Map[String, String] val) => _responder = responder' - _params = consume params' + _params = params' _data = Map[String, Any val] _start_time = Time.nanos() diff --git a/jennet/jennet.pony b/jennet/jennet.pony index 7b0fc89..065fc7e 100644 --- a/jennet/jennet.pony +++ b/jennet/jennet.pony @@ -125,7 +125,7 @@ class iso Jennet """ Serve incomming HTTP requests. """ - let mux = _Multiplexer(_routes)? + let mux = _Mux(_routes)? let router_factory = _RouterFactory(consume mux, _responder, _notfound) _server.set_handler(router_factory) @@ -143,12 +143,12 @@ class iso Jennet _routes.push(route) class val _RouterFactory - let _mux: _Multiplexer val + let _mux: _Mux let _responder: Responder let _not_found: _HandlerGroup new val create( - mux: _Multiplexer val, + mux: _Mux, responder: Responder, not_found: _HandlerGroup) => _mux = mux diff --git a/jennet/multiplexer.pony b/jennet/multiplexer.pony deleted file mode 100644 index 0916c69..0000000 --- a/jennet/multiplexer.pony +++ /dev/null @@ -1,195 +0,0 @@ -use "collections" -use "http" - -// TODO weight optimization -// TODO path auto-correction - -class iso _Multiplexer - let _methods: Map[String, _Node] - - new iso create(routes: Array[_Route] val) ? => - _methods = Map[String, _Node] - for r in routes.values() do - let method = r.method - let hg = _HandlerGroup(r.hg.handler, r.hg.middlewares) - if _methods.contains(method) then - _methods(method)?.add(r.path.clone(), hg)? - else - _methods(method) = _Node(r.path, hg) - end - end - - fun apply(method: String, path: String): - (_HandlerGroup, Map[String, String] iso^) ? => - let path' = if path(0)? != '/' then - let p = recover String(path.size() + 1) end - p.append("/") - p.append(consume path) - consume p - else - consume path - end - let n = _methods(method)? - n(consume path', recover Map[String, String] end)? - -class _Node - let prefix: String - let _params: Map[USize, String] - var _hg: (_HandlerGroup | None) - let _children: Array[_Node] - - new create( - prefix': String, - hg': (_HandlerGroup | None) = None, - params': Map[USize, String] = Map[USize, String], - children': Array[_Node] = Array[_Node]) - => - _params = params' - _hg = hg' - _children = children' - // store param names and remove them form prefix - let pfx = recover prefix'.clone() end - if params'.size() == 0 then - for i in Range[USize](0, pfx.size()) do - try - if pfx(i)? == ':' then - let ns = try - pfx.find("/", i.isize())? - else - pfx.size().isize() - end - _params(i) = pfx.substring(i.isize() + 1, ns) - pfx.delete(i.isize() + 1, ns.usize()) - elseif pfx(i)? == '*' then - _params(i) = pfx.substring(i.isize() + 1) - pfx.delete(i.isize() + 1, pfx.size()) - end - end - end - end - prefix = consume pfx - - fun ref add(path: String iso, hg: _HandlerGroup): _Node ? => - if path.size() < prefix.size() then - let n1 = create(consume path, hg, _params) - n1.add_child(this) - return n1 - end - for i in Range[USize](0, prefix.size()) do - if prefix(i)? == ':' then - let ns = try - path.find("/", i.isize())? - else - path.size().isize() - end - let value = path.substring(i.isize(), ns) - if value == "" then error end - path.delete(i.isize(), ns.usize()) - elseif prefix(i)? == '*' then - path.delete(i.isize(), path.size()) - elseif prefix(i)? != path(i)? then - // branch in prefix - let params0 = Map[USize, String] - let params1 = Map[USize, String] - for (k, v) in _params.pairs() do - if k < i then - params0(k) = v - else - params1(k) = v - end - end - // branch - let n1 = create(prefix.substring(0, i.isize()), None, params0) - let n2 = create(prefix.substring(i.isize()), _hg, params1, _children) - let n3 = create(path.substring(i.isize()), hg) - n1.add_child(n2) - n1.add_child(n3) - return n1 - end - end - - let remaining = path.substring(prefix.size().isize()) - // create edge - if remaining == "" then - if _hg is None then - _hg = hg - return this - else - error - end - end - // pass on to child - for c in _children.values() do - if c.prefix(0)? == remaining(0)? then - return c.add(consume remaining, hg)? - end - end - // add child and reorder - let c = create(consume remaining, hg) - _children.push(c) - reorder()? - this - - fun ref add_child(child: _Node) => - _children.push(child) - - fun ref reorder() ? => - // check if there are more than one param children - var ps: USize = 0 - for c in _children.values() do - if c.prefix(0)? == ':' then - ps = ps + 1 - end - end - if ps > 1 then error end - // give param child last priority - if ps != 0 then - for (i, c) in _children.pairs() do - if c.prefix(0)? == ':' then - _children.delete(i)? - _children.push(c) - break - end - end - end - - fun apply(path: String, params: Map[String, String] iso): - (_HandlerGroup, Map[String, String] iso^) ? - => - var path' = path - for i in Range[ISize](0, prefix.size().isize()) do - if prefix(i.usize())? == ':' then - // store params - let ns = try - path'.find("/", i.isize())? - else - path'.size().isize() - end - let value = path'.substring(i, ns) - if value == "" then error end - params(_params(i.usize())?) = consume value - path' = path'.cut(i.isize(), ns) - elseif prefix(i.usize())? == '*' then - params(_params(i.usize())?) = path'.substring(i) - path' = path'.cut(i.isize(), path'.size().isize()) - elseif prefix(i.usize())? != path'(i.usize())? then - // not found - error - end - end - - let remaining = path'.substring(prefix.size().isize()) - // check for edge - if remaining == "" then - return (_hg as _HandlerGroup, consume params) - end - // pass on to child - for c in _children.values() do - match c.prefix(0)? - | '*' => return c(consume remaining, consume params)? - | ':' => return c(consume remaining, consume params)? - | remaining(0)? => return c(consume remaining, consume params)? - end - end - // not found - error diff --git a/jennet/mux.pony b/jennet/mux.pony new file mode 100644 index 0000000..c52aad6 --- /dev/null +++ b/jennet/mux.pony @@ -0,0 +1,35 @@ +use "collections" +use "http" +use "radix" + +// TODO weight optimization +// TODO path auto-correction + +class val _Mux + let _methods: Map[String, Radix[_HandlerGroup]] + + new trn create(routes: Array[_Route] val) ? => + _methods = Map[String, Radix[_HandlerGroup]] + for r in routes.values() do + let method = r.method + let hg = _HandlerGroup(r.hg.handler, r.hg.middlewares) + if not _methods.contains(method) then + _methods(method) = Radix[_HandlerGroup] + end + _methods(method)?(r.path.clone())? = hg + end + + // TODO: no unwind + fun val apply(method: String, path: String, params: Map[String, String]) + : _HandlerGroup ? + => + let path' = + if path(0)? != '/' then + let p = recover String(path.size() + 1) end + p.append("/") + p.append(path) + p + else + path + end + _methods(method)?(consume path', params) as _HandlerGroup diff --git a/jennet/radix/_test.pony b/jennet/radix/_test.pony index b28b796..a70ba96 100644 --- a/jennet/radix/_test.pony +++ b/jennet/radix/_test.pony @@ -6,6 +6,9 @@ actor Main is TestList new create(env: Env) => PonyTest(env, this) + new make() => + None + fun tag tests(test: PonyTest) => test(_TestRadix) test(Property1UnitTest[Array[String]](_TestRadixBasic)) diff --git a/jennet/router.pony b/jennet/router.pony index 730268e..0963fcf 100644 --- a/jennet/router.pony +++ b/jennet/router.pony @@ -2,27 +2,28 @@ use "collections" use "http" class val _Router is HTTPHandler - let _mux: _Multiplexer val + let _mux: _Mux let _responder: Responder let _notfound: _HandlerGroup - new create(mux: _Multiplexer val, responder: Responder, - notfound: _HandlerGroup) - => + new create(mux: _Mux, responder: Responder, notfound: _HandlerGroup) => _mux = mux _responder = responder _notfound = notfound fun ref apply(request: Payload val) => - (let hg, let c) = try - (let hg, let params) = _mux(request.method, request.url.path)? - let c = Context(_responder, consume params) - (hg, consume c) - else - (_notfound, Context(_responder, recover Map[String, String] end)) - end + (let hg, let params: Map[String, String] val) = + try + recover + let params = Map[String, String] + let hg = _mux(request.method, request.url.path, params)? + (hg, params) + end + else + (_notfound, recover Map[String, String] end) + end try - hg(consume c, consume request)? + hg(Context(_responder, consume params), consume request)? end primitive _UnavailableFactory is HandlerFactory