import system/[ io, ] import std/[ asyncdispatch, asynchttpserver, mimetypes, os, strformat, strutils, sugar, ] import fp/[ map, option, maybe, resultM, ] import fusion/matching import ./env import ./errors import ./utils # (GET, (scheme: "", username: "", password: "", hostname: "", port: "", path: "/", query: "", anchor: "", opaque: false, isIpv6: false), {"accept": @["*/*"], "host": @["localhost:45201"], "user-agent": @["curl/7.82.0"]}) {.experimental: "caseStmtMacros".} type NimHttpResponse* = tuple[ code: HttpCode, content: string, headers: HttpHeaders ] proc sendNotFound(env: Env, path: seq[string]): NimHttpResponse = ( code: Http404, content: "Not Found", headers: newHttpHeaders({ "Content-Type": "text/html" }), ) proc sendStaticFile(env: Env, path: seq[string]): NimHttpResponse = let mimetype = path .last() .map((x: string) => x.splitFile.ext) .notEmpty() .map((x: string) => env.mimeTypes.getMimetype(x)) .getOrElse("text/plain") let filePath = env.htmlRoot.joinPath(path.join("/")) let file = filePath.readFile() ( code: Http200, content: file, headers: newHttpHeaders({ "Content-Type": mimetype }) ) proc sendOrgFile(env: Env, id: orgId, path: seq[string]): NimHttpResponse = type errT = tuple[err: orgEnvErr, id: orgId, path: string] type StringResult = Result[orgPath, errT] type FileResult = Result[string, errT] let content = env.orgEnv .get(id) .fold( () => StringResult.err((invalidWorkspaceId, id, "")), (x: orgPath) => StringResult.ok(x), ) .map((x: orgPath) => $x.joinPath(path.join("/"))) .flatMap((path: orgPath) => ( catch(readFile(path)).fold( _ => FileResult.err((fileNotFound, id, path)), (content: string) => FileResult.ok(content), ) )) .tapErr((err: errT) => logErr(env, err[0], &"{err[1]}, path: {err[2]}")) content.fold( (e: errT) => ( code: Http404, content: "File not Found", headers: newHttpHeaders({ "Content-Type": "application/json" }) ), (content: string) => ( code: Http200, content: content, headers: newHttpHeaders({ "Content-Type": "text/plain" }) ) ) proc sendIndex(env: Env): auto = sendStaticFile(env, path = @["index.html"]) proc handleRoute(env: Env, req: Request): NimHttpResponse = # Handle main route if req.url.path == "/": return sendIndex(env) var path = req.url.path.split("/") path.delete(0) # Router return ( case (req.reqMethod, path): of (HttpGet, ["dist", .._]): sendStaticFile(env, path) of (HttpGet, ["api", "org", @id, all @rest]): sendOrgFile(env, id, rest) else: sendIndex(env) ) proc main {.async.} = let env = initEnv(); proc cb(req: Request) {.async.} = echo (req.reqMethod, req.url, req.headers) let response = handleRoute(env, req) await req.respond(response.code, response.content , response.headers) var server = newAsyncHttpServer() server.listen(Port(env.port)) echo &"Starting server at {env.port}" while true: if server.shouldAcceptRequest(): await server.acceptRequest(cb) else: # too many concurrent connections, `maxFDs` exceeded wait 500ms for FDs to be closed await sleepAsync(500) waitFor main()