Simple web server
This commit is contained in:
7
TODO.org
7
TODO.org
@@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
* Todos
|
* 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://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]]
|
- [[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://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]]
|
||||||
- [ ] [[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]]
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<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="/dist/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-custom.css" type="text/css" media="screen" />
|
||||||
<title>Nim on JS</title>
|
<title>Nim on JS</title>
|
||||||
</head>
|
</head>
|
||||||
<body onload="startApp()">
|
<body onload="startApp()">
|
||||||
<div id="app" class="container"></div>
|
<div id="app" class="container"></div>
|
||||||
|
|
||||||
<!-- React -->
|
<!-- React -->
|
||||||
<script src="./node_modules/react/umd/react.development.js"></script>
|
<script src="/dist/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-dom/umd/react-dom.development.js"></script>
|
||||||
|
|
||||||
<!-- React Wrapper Helpers for nim -->
|
<!-- React Wrapper Helpers for nim -->
|
||||||
<script>
|
<script>
|
||||||
@@ -22,9 +22,8 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Nim App -->
|
<!-- Nim App -->
|
||||||
<script src="sandbox.js"></script>
|
<script src="/dist/sandbox.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function startApp() {
|
function startApp() {
|
||||||
const root = ReactDOM.createRoot(document.getElementById('app'));
|
const root = ReactDOM.createRoot(document.getElementById('app'));
|
||||||
42
src/org/org_types_printer.nim
Normal file
42
src/org/org_types_printer.nim
Normal 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
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