Simple web server
This commit is contained in:
24
src/server/env.nim
Normal file
24
src/server/env.nim
Normal file
@@ -0,0 +1,24 @@
|
||||
import std/[
|
||||
mimetypes,
|
||||
os,
|
||||
]
|
||||
|
||||
const PORT_DEFAULT* = 1337
|
||||
const HTML_ROOT* = "../js"
|
||||
const DIST_ROOT* = HTML_ROOT.joinPath("dist")
|
||||
|
||||
type Env* = ref object
|
||||
port*: int
|
||||
mimeTypes*: MimeDb
|
||||
htmlRoot*: string
|
||||
distRoot*: string
|
||||
|
||||
proc initEnv*(): auto =
|
||||
Env(
|
||||
port: PORT_DEFAULT,
|
||||
mimeTypes: newMimetypes(),
|
||||
htmlRoot: HTML_ROOT,
|
||||
distRoot: DIST_ROOT,
|
||||
)
|
||||
|
||||
echo newMimetypes().getMimetype("txt")
|
||||
96
src/server/server.nim
Normal file
96
src/server/server.nim
Normal file
@@ -0,0 +1,96 @@
|
||||
import system/[
|
||||
io,
|
||||
]
|
||||
import std/[
|
||||
asyncdispatch,
|
||||
asynchttpserver,
|
||||
mimetypes,
|
||||
os,
|
||||
strformat,
|
||||
strutils,
|
||||
sugar,
|
||||
]
|
||||
import fp/[
|
||||
option,
|
||||
]
|
||||
import fusion/matching
|
||||
import ./env
|
||||
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 handleRoute(env: Env, req: Request): NimHttpResponse =
|
||||
# Handle main route
|
||||
if req.url.path == "/":
|
||||
return sendStaticFile(env, path = @["index.html"])
|
||||
|
||||
var path = req.url.path.split("/")
|
||||
path.delete(0)
|
||||
|
||||
# Router
|
||||
return (
|
||||
case path:
|
||||
of ["dist", .._]:
|
||||
sendStaticFile(env, path)
|
||||
else:
|
||||
sendNotFound(env, path)
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
23
src/server/test.nim
Normal file
23
src/server/test.nim
Normal file
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
import std/[sugar, strutils, sequtils]
|
||||
|
||||
import fusion/matching
|
||||
{.experimental: "caseStmtMacros".}
|
||||
|
||||
proc sendIndex(): auto =
|
||||
return "foo"
|
||||
|
||||
proc handleRoute(path: string): string =
|
||||
if path == "/":
|
||||
return sendIndex()
|
||||
|
||||
var paths: seq[string] = path.split("/")
|
||||
paths.delete(0)
|
||||
|
||||
return case paths:
|
||||
of ["foo", "bar", "baz"]:
|
||||
"yes"
|
||||
else:
|
||||
"no"
|
||||
|
||||
echo "/foo/bar/baz".handleRoute()
|
||||
13
src/server/utils.nim
Normal file
13
src/server/utils.nim
Normal file
@@ -0,0 +1,13 @@
|
||||
import fp/[
|
||||
option,
|
||||
]
|
||||
|
||||
func last*[T](xs: seq[T]): Option[T] {.inline.} =
|
||||
if xs.len == 0:
|
||||
none[T]()
|
||||
else:
|
||||
some(xs[^1])
|
||||
|
||||
when isMainModule:
|
||||
assert @[1, 2, 3].last() == some(3)
|
||||
assert newSeq[int]().last() == none[int]()
|
||||
Reference in New Issue
Block a user