all repos — nimhttpd @ beb4816e98978043842e78802006ccb2c8112aa8

A useful static file web server.

Moved from nimtools rep, some minor fixes, removed htmlgen.
h3rald h3rald@h3rald.com
Sun, 11 Oct 2015 12:49:25 +0200
commit

beb4816e98978043842e78802006ccb2c8112aa8

parent

2b1e17166fcae0394b0c344e03e2bf998d92ffe3

3 files changed, 435 insertions(+), 0 deletions(-)

jump to
M .gitignore.gitignore

@@ -1,1 +1,2 @@

nimcache/ +nimhttpd
A nimhttpd.nim

@@ -0,0 +1,196 @@

+import asynchttpserver, asyncdispatch, asyncnet, os, strutils, mimetypes, times, parseopt2 +from strtabs import StringTableRef, newStringTable + +const style = "style.css".slurp + +let appname = "NimHTTPd Web Server" +let appversion = "1.0.0" +let usage = appname & " v" & appversion & " - Tiny Static File Web Server" & """ + + (c) 2014-2015 Fabio Cevasco + + Usage: + nimhttpd [-p:port] [directory] + + Arguments: + directory The directory to serve (default: current directory). + port Listen to port (default: 1337). +""" + + +type + NimHttpResponse* = tuple[ + code: HttpCode, + content: string, + headers: StringTableRef] + NimHttpSettings* = object + logging*: bool + directory*: string + mimes*: MimeDb + port*: Port + address*: string + appname: string + appversion*: string + +proc h_page(settings:NimHttpSettings, content: string, title=""): string = + var footer = """<div id="footer">$1 v$2</div>""" % [settings.appname, settings.appversion] + result = """ +<!DOCTYPE html> +<html> + <head> + <title>$1</title> + <style type="text/css">$2</style> + <meta charset="UTF-8"> + </head> + <body> + <h1>$1</h1> + $3 + $4 + </body> +</html> + """ % [title, style, content, footer] + +proc relativePath(path, cwd: string): string = + var path2 = path + if cwd == "/": + return path + else: + path2.delete(0, cwd.len) + var relpath = path2.replace("\\", "/") + if (not relpath.endsWith("/")) and (not path.existsFile): + relpath = relpath&"/" + if not relpath.startsWith("/"): + relpath = "/"&relpath + return relpath + +proc relativeParent(path, cwd: string): string = + var relparent = path.parentDir.relativePath(cwd) + if relparent == "": + return "/" + else: + return relparent + +proc sendNotFound(settings: NimHttpSettings, path: string): NimHttpResponse = + var content = "<p>The page you requested cannot be found.<p>" + return (code: Http404, content: h_page(settings, content, $Http404), headers: newStringTable()) + +proc sendNotImplemented(settings: NimHttpSettings, path: string): NimHttpResponse = + var content = "<p>This server does not support the functionality required to fulfill the request.</p>" + return (code: Http501, content: h_page(settings, content, $Http501), headers: newStringTable()) + +proc sendStaticFile(settings: NimHttpSettings, path: string): NimHttpResponse = + let mimes = settings.mimes + let mimetype = mimes.getMimetype(path.splitFile.ext[1 .. ^1]) + var file = path.readFile + return (code: Http200, content: file, headers: {"Content-Type": mimetype}.newStringTable) + +proc sendDirContents(settings: NimHttpSettings, path: string): NimHttpResponse = + let cwd = settings.directory + var res: NimHttpResponse + var files = newSeq[string](0) + if path != cwd and path != cwd&"/" and path != cwd&"\\": + files.add """<li class="i-back entypo"><a href="$1">..</a></li>""" % [path.relativeParent(cwd)] + var title = "Index of " & path.relativePath(cwd) + for i in walkDir(path): + let name = i.path.extractFilename + let relpath = i.path.relativePath(cwd) + if name == "index.html" or name == "index.htm": + return sendStaticFile(settings, i.path) + if i.path.existsDir: + files.add """<li class="i-folder entypo"><a href="$1">$2</a></li>""" % [relpath, name] + else: + files.add """<li class="i-file entypo"><a href="$1">$2</a></li>""" % [relpath, name] + let ul = """ +<ul> + $1 +</ul> +""" % [files.join("\n")] + res = (code: Http200, content: h_page(settings, ul, title), headers: newStringTable()) + return res + +proc printReqInfo(settings: NimHttpSettings, req: Request) = + if not settings.logging: + return + echo getLocalTime(getTime()), " - ", req.hostname, " ", req.reqMethod, " ", req.url.path + +proc handleCtrlC() {.noconv.} = + echo "\nExiting..." + quit() + +setControlCHook(handleCtrlC) + +proc serve*(settings: NimHttpSettings) = + var server = newAsyncHttpServer() + proc handleHttpRequest(req: Request): Future[void] {.async.} = + printReqInfo(settings, req) + let path = settings.directory/req.url.path.replace("%20", " ") + var res: NimHttpResponse + if req.reqMethod != "get": + res = sendNotImplemented(settings, path) + elif path.existsDir: + res = sendDirContents(settings, path) + elif path.existsFile: + res = sendStaticFile(settings, path) + else: + res = sendNotFound(settings, path) + await req.respond(res.code, res.content, res.headers) + echo settings.appname, " v", settings.appversion, " started on port ", int(settings.port), "." + echo "Serving directory ", settings.directory + asyncCheck server.serve(settings.port, handleHttpRequest, settings.address) + +when isMainModule: + + var port = Port(1337) + var address = "" + var logging = false + var www = getCurrentDir() + + for kind, key, val in getopt(): + case kind + of cmdLongOption, cmdShortOption: + case key + of "log", "l": + logging = true + of "help", "h": + echo usage + quit(0) + of "version", "v": + echo appversion + quit(0) + of "port", "p": + try: + port = Port(val.parseInt) + except: + if val == "": + echo "Port not set." + quit(2) + else: + echo "Error: Invalid port: '", val, "'" + echo "Running on default port instead." + else: + discard + of cmdArgument: + var dir: string + if key.isAbsolute: + dir = key + else: + dir = www/key + if dir.existsDir: + www = expandFilename dir + else: + echo "Error: Directory '"&dir&"' does not exist." + quit(1) + else: + discard + + var settings: NimHttpSettings + settings.directory = www + settings.logging = logging + settings.mimes = newMimeTypes() + settings.address = address + settings.appname = appname + settings.appversion = appversion + settings.port = port + + serve(settings) + runForever()
A style.css

@@ -0,0 +1,238 @@

+ +@font-face { + font-family: 'entypo'; + src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAWAAAsAAAAABTQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgCCL8uGNtYXAAAAFoAAAATAAAAEwaVcxZZ2FzcAAAAbQAAAAIAAAACAAAABBnbHlmAAABvAAAAcAAAAHA+MXyZmhlYWQAAAN8AAAANgAAADYB3KiuaGhlYQAAA7QAAAAkAAAAJAPiAehobXR4AAAD2AAAABwAAAAcBwAAZ2xvY2EAAAP0AAAAEAAAABAA7gFebWF4cAAABAQAAAAgAAAAIAANAD5uYW1lAAAEJAAAATwAAAE85f6fknBvc3QAAAVgAAAAIAAAACAAAwAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA5gIB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDmAv/9//8AAAAAACDmAP/9//8AAf/jGgQAAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAUATQAAAbMBzQAEACEAJgArADAAADczNSMVNyEiBgcOARURFBYXHgEzITI2Nz4BNRE0JicuASMRIREhEQMjFTM1FSMVMzW5j4/H/wALEgcHCAgHBxILAQALEgcHCAgHBxIL/wABADiQkJCQ0C4u/QgHBxMK/pkKEwcHCAgHBxMKAWcKEwcHCP5mAWf+mQEvLS3JLS0AAAAAAgAAABoCAAGzABwAOwAAASEiBgcGFBUXHgEXHgEzITI2Nz4BPwE8AScuASMnLgEnLgErASImJy4BLwEuAScuASsBIgYHDgEPASEnAej+MAkKAgMVAQIDAwsJAZwJCwMDAgEVAwIKCREBBgQECgauBgwGBgsDEAMLBgYMBlMFCgQEBQEHAbEFATMEAwQJBecFCQQDBAQDBAkF5wUJBAMEOQQHAwMDAwIDBgQPBAYDAgMEAwQJBUMVAAEAGgA4AeYBlAANAAAlMCYjNQcXNTIWFx4BFwHma6i5uS1QIiM6Fzj5Y6WrcAcNDTEqAAABAAAAAQAAIEIGsV8PPPUACwIAAAAAANBfM/IAAAAA0F8z8gAAAAACAAHNAAAACAACAAAAAAAAAAEAAAHg/+AAAAIAAAAAAAIAAAEAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAQAAAAIAAE0CAAAAAgAAGgAAAAAACgAUAB4AagDGAOAAAQAAAAcAPAAFAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAwAAAABAAAAAAACAA4AQAABAAAAAAADAAwAIgABAAAAAAAEAAwATgABAAAAAAAFABYADAABAAAAAAAGAAYALgABAAAAAAAKADQAWgADAAEECQABAAwAAAADAAEECQACAA4AQAADAAEECQADAAwAIgADAAEECQAEAAwATgADAAEECQAFABYADAADAAEECQAGAAwANAADAAEECQAKADQAWgBlAG4AdAB5AHAAbwBWAGUAcgBzAGkAbwBuACAAMQAuADAAZQBuAHQAeQBwAG9lbnR5cG8AZQBuAHQAeQBwAG8AUgBlAGcAdQBsAGEAcgBlAG4AdAB5AHAAbwBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format('woff'); + font-weight: normal; + font-style: normal; +} + +.entypo { + font-family: 'entypo'; + speak: none; + font-style: normal; + font-weight: 200; + font-variant: normal; + text-transform: none; + vertical-align: baseline; + padding: 3px; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.i-back:before { + content: "\e602\00a0"; +} +.i-file:before { + content: "\e600\00a0"; +} +.i-folder:before { + content: "\e601\00a0"; +} + +html { + font-family: sans-serif; + /* 1 */ + -ms-text-size-adjust: 100%; + /* 2 */ + -webkit-text-size-adjust: 100%; + /* 2 */ +} +body { + margin: 0; +} +a { + background: transparent; +} +a:focus { + outline: thin dotted; +} +a:active, +a:hover { + outline: 0; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +b, +strong { + font-weight: bold; +} +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +code, +pre { + font-family: monospace, serif; + font-size: 1em; +} +pre { + white-space: pre-wrap; +} +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} +small { + font-size: 80%; +} + + +table { + border-collapse: collapse; + border-spacing: 0; +} +body { + background-color: #fff; + margin: 0 auto; + height: 100%; + zoom: 1; + letter-spacing: normal; + word-spacing: normal; + color: #333333; + font: 15px "Source Sans Pro", "Calibri", "Helvetica", "Arial", "Freesans", "Clean", sans-serif; + line-height: 1.4; + -webkit-font-smoothing: antialiased; + width: 960px; +} +@media screen and (max-width: 659px) { + body { + width: 100%; + } +} +@media screen and (min-width: 660px) { + body { + width: 660px; + } +} +#main, +#footer, +#header { + width: 94%; + margin: auto; +} + +#footer { + border-top: 1px solid #dddddd; + text-align: center; + font-size: 75%; +} +#footer p { + line-height: 0.6em; +} +/* Lists */ +ul, +ol { + padding-left: 30px; +} +li p { + margin: 0 auto; +} +li { + list-style-type: none; +} +dl dd { + margin: 0 0 0 32%; + padding: 0 0 0.2em 0; +} +/* Headings */ +h1, +h2, +h3, +h4, +h5, +h6 { + color: #111111; + border-bottom: 1px solid #dddddd; +} +h1 { + text-align: center; +} +pre { + -moz-background-clip: padding; + -webkit-background-clip: padding-box; + background-clip: padding-box; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + margin: 10px auto; + padding: 2px 4px 0 4px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); + color: #333333; + background-color: #f8f8f8; + border: 1px solid #cccccc; + margin: auto; + font-family: "Source Code Pro", "Monaco", "DejaVu Sans Mono", "Courier New", monospace; + padding: 0; +} +pre a { + color: #264c72; +} +pre p { + margin: 0 auto; +} +pre code { + box-shadow: none; + background: #f8f8f8; + padding: 0px 2px 0 2px; + border: none; +} +@media screen and (max-width: 639px) { + .responsive { + width: 100%; + overflow-y: hidden; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .responsive td { + white-space: nowrap; + } +} +table { + border-collapse: collapse; + margin: 15px auto; + border-spacing: 0; + empty-cells: show; +} +table thead { + background: #f8f8f8; + color: #222; + text-align: left; + vertical-align: bottom; +} +table td, +table th { + background-color: transparent; + border: 1px solid #999999; + font-size: inherit; + margin: 0; + overflow: visible; + padding: 6px 12px; +} +hr { + border: 0; + height: 1px; + background-image: -webkit-linear-gradient(left, rgba(99, 99, 99, 0), rgba(99, 99, 99, 0.75), rgba(99, 99, 99, 0)); + background-image: -moz-linear-gradient(left, rgba(99, 99, 99, 0), rgba(99, 99, 99, 0.75), rgba(99, 99, 99, 0)); + background-image: -ms-linear-gradient(left, rgba(99, 99, 99, 0), rgba(99, 99, 99, 0.75), rgba(99, 99, 99, 0)); + background-image: -o-linear-gradient(left, rgba(99, 99, 99, 0), rgba(99, 99, 99, 0.75), rgba(99, 99, 99, 0)); +} +.center { + margin: auto; + text-align: center; +} + +a, +a:visited { + color: #4183c4; + text-decoration: none; +} +a:hover, +a:visited:hover { + text-decoration: underline; +}