Simple web server

This commit is contained in:
Florian Schroedl
2022-05-04 17:00:00 +02:00
parent f038e4f272
commit 05ecdb1f9e
13 changed files with 209 additions and 7 deletions

View File

@@ -2,11 +2,16 @@
* Todos
** TODO Creating hot reload
** TODO Creating a server
:LOGBOOK:
CLOCK: [2022-05-29 Sun 11:24]
:END:
- [[https://nim-lang.org/docs/asynchttpserver.html][std/asynchttpserver]]
- [[https://github.com/h3rald/nimhttpd/blob/master/src/nimhttpd.nim][nimhttpd/nimhttpd.nim at master · h3rald/nimhttpd]]
** TODO Creating hot reload
- [ ] [[https://dev.to/alemagio/implement-your-own-hot-reload-2435][Implement your own hot-reload - DEV Community]]
- [ ] [[https://maheshsenniappan.medium.com/creating-a-module-bundler-with-hot-module-replacement-b439f0cc660f][Creating a module bundler with Hot Module Replacement | by Mahesh Senniappan | Medium]]
- [ ] [[https://maheshsenniappan.medium.com/creating-a-module-bundler-with-hot-module-replacement-b439f0cc660f][Creating a module bundler with Hot Module Replacement | by Mahesh Senniappan | Medium]]

View File

@@ -3,16 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="./style.css" type="text/css" media="screen" />
<link rel="stylesheet" href="./style-custom.css" type="text/css" media="screen" />
<link rel="stylesheet" href="/dist/style.css" type="text/css" media="screen" />
<link rel="stylesheet" href="/dist/style-custom.css" type="text/css" media="screen" />
<title>Nim on JS</title>
</head>
<body onload="startApp()">
<div id="app" class="container"></div>
<!-- React -->
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="/dist/node_modules/react/umd/react.development.js"></script>
<script src="/dist/node_modules/react-dom/umd/react-dom.development.js"></script>
<!-- React Wrapper Helpers for nim -->
<script>
@@ -22,9 +22,8 @@
}
</script>
<!-- Nim App -->
<script src="sandbox.js"></script>
<script src="/dist/sandbox.js"></script>
<script>
function startApp() {
const root = ReactDOM.createRoot(document.getElementById('app'));

View File

@@ -0,0 +1,42 @@
import std/[
options,
sequtils,
strformat,
strutils,
sugar,]
import ./org_types_interface
const INDENT_SIZE = 2;
func stringifySpecialFields(x: OrgBlock, indent = 0): string =
(
case x.kind:
of orgHeading:
@[
("kind", $x.kind, true),
("level", $x.level, true),
("todo", $x.todo, x.todo.isSome()),
("headlineContent", $x.headlineContent, x.headlineContent.len != 0),
("headlineChildrenText", $x.headlineChildrenText, x.headlineChildrenText.len != 0),
("children", $x.children, true),]
else: @[])
.filter(x => x[2])
.map(x => x[0] & ": " & x[1])
.join(",\n")
proc `$`*(x: OrgBlock, indent = 0): string =
let fieldIndent = indent + INDENT_SIZE
@[
"OrgBlock(",
stringifySpecialFields(x).indent(fieldIndent),
")",]
.join("\n")
when isMainModule:
echo OrgBlock(kind: orgHeading, todo: "foo".some)

24
src/server/env.nim Normal file
View 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
View 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
View 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
View 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]()