//! A minimal library for writing HTTP servers. let prelude = import! std.prelude let function = import! std.function let { ? } = import! std.io let { (<<), (<|), ? } = import! std.function let string = import! std.string let { Bool } = import! std.bool let { Functor, Applicative, Alternative, Monad } = prelude let { id } = import! std.prelude let { flat_map } = import! std.monad let { wrap, (*>) } = import! std.applicative let { or, empty, (<|>) } = import! std.alternative let regex = import! std.regex let result = import! std.result let { Eff, run_pure, ? } = import! std.effect let alt @ { run_alt } = import! std.effect.alt let { get, gets, eval_state } = import! std.effect.state let { lift, run_lift } = import! std.effect.lift let { Method, Failure, Request, StatusCode, Response, ResponseBody, HttpEffect, HttpState, Uri, Body } = import! std.http.types let http_prim = lift_io! lift (import! std.http.prim) let status = let code : Int -> StatusCode = id { ok = code 200, moved_permanently = 301, found = 302, temporary_redirect = 307, permanent_redirect = 308, bad_request = code 400, not_found = code 404, internal_server_error = code 500, } let method = let method : String -> Method = id { get = method "GET", post = method "POST", put = method "PUT", } let alternative : Alternative (Eff (HttpEffect r)) = alt.alternative let response : Response = { status = status.ok, headers = [] } /// Force the value to be a Handler. Necessary to make the the type inference work for /// higher-kinded types let make : Eff (HttpEffect r) a -> Eff (HttpEffect r) a = id /// Handles the request if `predicate` returns `True let test predicate : (Request -> Bool) -> Eff (HttpEffect r) () = do state = get if predicate state.request then wrap () else empty /// Handles `Get` requests let get : Eff (HttpEffect r) () = test (\request -> request.method == method.get) /// Handles `Post` requests let post : Eff (HttpEffect r) () = test (\request -> request.method == method.post) /// Processes this handler if `uri` matches the request's uri let path p : String -> Eff (HttpEffect r) () = test (\request -> http_prim.uri.path request.uri == p) let is_match uri : String -> Eff (HttpEffect r) () = let re = result.unwrap_ok (regex.new uri) test (\request -> regex.is_match re (http_prim.uri.path request.uri)) /// Retrieves the HTTP request let get_request : Eff (HttpEffect r) Request = gets (\s -> s.request) /// Retrieves the body of the http response let get_response_body : Eff (HttpEffect r) ResponseBody = gets (\s -> s.response) /// Returns `OK` with an empty body let empty_response = { status = status.ok } /// Write `bytes` to the http response let write_response bytes : Array Byte -> Eff (HttpEffect r) () = do response = get_response_body http_prim.write_response response bytes /// Throws an exception which aborts the current handler. Can be caught with `catch_error` let fail msg : String -> Eff (HttpEffect r) a = empty // TODO use msg /// Recovers from an exception thrown by `fail` let catch_error action catch : Eff (HttpEffect r) a -> (String -> Eff (HttpEffect r) a) -> Eff (HttpEffect r) a = do opt = alt.run_alt action empty match opt with | None -> catch "empty" | Some a -> wrap a /// Takes a `Handler` and a `Request` tries to process the request let handle handler state : Eff (HttpEffect r) Response -> HttpState -> IO Response = do opt = run_lift <| eval_state state <| run_alt handler empty match opt with | None -> run_lift (http_prim.write_response state.response (string.as_bytes "Page not found") *> wrap { status = status.not_found, .. response }) | Some response -> wrap response let show_uri : Show Uri = { show = http_prim.uri.to_string, } let default_listen_settings = { port = 80, tls_cert = None } { Method, Failure, Request, StatusCode, Response, HttpEffect, HttpState, Body, alternative, status, method, empty_response, get_request, handle, get, post, path, is_match, fail, catch_error, show_uri, write_response, default_listen_settings, response, .. http_prim }