Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Nimで静的ファイルサーバーを作る

Avatar for medy medy
November 09, 2022

 Nimで静的ファイルサーバーを作る

Avatar for medy

medy

November 09, 2022
Tweet

More Decks by medy

Other Decks in Programming

Transcript

  1. こういうコマンドを作る localserver -h Usage: localserver [optional-params] Options: -h, --help print

    this cligen-erated help --help-syntax advanced: prepend,plurals,.. -p=, --port= int 8080 set port localserver -p=8080 > start server on localhost:8080
  2. 標準ライブラリのasynchttpserverに書いてる内容をまるっとコピペ https://nim-lang.org/docs/asynchttpserver.html src/localserver/server.nim import std/asynchttpserver import std/asyncdispatch proc main() {.async.}

    = var server = newAsyncHttpServer() proc cb(req: Request) {.async.} = echo (req.reqMethod, req.url, req.headers) let headers = {"Content-type": "text/plain; charset=utf-8"} await req.respond(Http200, "Hello World", headers.newHttpHeaders()) server.listen(Port(0)) let port = server.getPort echo "test this with: curl localhost:" & $port.uint16 & "/" while true: if server.shouldAcceptRequest(): await server.acceptRequest(cb) else: await sleepAsync(500) waitFor main()
  3. nim c -r src/localserver.nim -h CLIの説明が表示される Usage: localserver [optional-params] Options:

    -h, --help print this cligen-erated help --help-syntax advanced: prepend,plurals,..
  4. proc localserver(port=8080) = # 引数portを追加 デフォルト値8080 discard when isMainModule: import

    cligen dispatch(localserver) 起動する nim c -r src/localserver.nim -h
  5. Usage: localserver [optional-params] Options: -h, --help print this cligen-erated help

    --help-syntax advanced: prepend,plurals,.. -p=, --port= int 8080 set port コマンドライン引数 --port の説明が追加された!
  6. ヘルプの内容を編集する import std/tables proc localserver(port=8080) = ## ローカルでサーバーを起動するコマンドです discard const

    HELP = {"port": "ここに指定したポート番号でサーバーが起動します"}.toTable() when isMainModule: import cligen dispatch(localserver, help=HELP)
  7. src/localserver/server.nim proc main*(port:int) {.async.} = # 引数 port:int を追加、*を付けてpublicにする var

    server = newAsyncHttpServer() proc cb(req: Request) {.async.} = echo (req.reqMethod, req.url, req.headers) let headers = {"Content-type": "text/plain; charset=utf-8"} await req.respond(Http200, "Hello World", headers.newHttpHeaders()) server.listen(Port(port)) # <<<<< 引数を渡す >>>>> let port = server.getPort echo "test this with: curl localhost:" & $port.uint16 & "/" while true: if server.shouldAcceptRequest(): await server.acceptRequest(cb) else: await sleepAsync(500)
  8. src/localserver.nim import std/asyncdispatch import std/tables import ./localserver/server proc localserver(port=8080) =

    ## ローカルでサーバーを起動するコマンドです waitFor main(port) const HELP = {"port": "ここに指定したポート番号でサーバーが起動します"}.toTable() when isMainModule: import cligen dispatch(localserver, help=HELP)
  9. 起動してみる nim c -r src/localserver.nim -p 7000 nim c -r

    src/localserver.nim -p 8000 nim c -r src/localserver.nim -p 9000
  10. import std/os import std/asyncfile let filepath = getCurrentDir() / "example/index.html"

    echo filepath let file = openAsync(filepath, fmRead) defer: file.close() let data = file.readAll().await echo data
  11. dataの中に example/index.html の中身を読み込めたことがわかったので、これをクライア ントに返すようにする let filepath = getCurrentDir() / "example/index.html"

    let file = openAsync(filepath, fmRead) defer: file.close() let data = file.readAll().await let headers = {"Content-type": "text/plaintext; charset=utf-8"} await req.respond(Http200, data, headers.newHttpHeaders())
  12. まずURLから拡張子を取り出します let path = req.url.path echo path > /examples/index.html ドットで分割して配列にする

    let pathArr = path.split(".") echo pathArr > @["/example/index", "html"] 配列の一番最後を取りだす let ext = pathArr[^1] echo ext > "html"
  13. 全体像 ついでにファイルの存在確認も行う let filePath = getCurrentDir() / req.url.path if fileExists(filepath):

    # URLから拡張子を取り出し、拡張子からContentTypeを取りだす let ext = req.url.path.split(".")[^1] let contentType = newMimetypes().getMimetype(ext) # ファイルの中身を読み込む let file = openAsync(filepath, fmRead) defer: file.close() let data = file.readAll().await # レスポンスヘッダーにContent-Typeをセットする let headers = newHttpHeaders() headers["Content-Type"] = contentType # レスポンスを返す await req.respond(Http200, data, headers) await req.respond(Http404, "")
  14. src/localserver/file.nim import std/os import std/strutils proc getFiles*(path:string):seq[string] = let currentPath

    = getCurrentDir() / path var files = newSeq[string]() for row in walkDir(currentPath, relative=true): if row.kind == pcDir or row.path.contains("."): files.add(row.path) return files
  15. src/localserver.nim import ./files . . if req.url.path.contains("."): . . else:

    let files = getFiles(req.url.path) # 今回作った関数を呼び出す let headers = newHttpHeaders() await req.respond(Http200, $files, headers) await req.respond(Http404, "") http://localhost8080/example にアクセス @["index.html", "style.css"]
  16. src/localserver/view.nim #? stdtmpl | standard #proc displayView*(path:string, files:seq[string]): string =

    <!DOCTYPE html> <html lang="en"> <head> <title>Current Directory Files</title> </head> <body> # let urlPath = if path == "/": "" else: path <h1>Directory listing for ${path}</h1> <hr> #if files.len > 0: <ul> #for file in files: <li><a href="${urlPath}/${file}">${file}</a></li> #end for </ul> #end if <hr> </body> </html>
  17. src/localserver.nim import ./files import ./view . . if req.url.path.contains("."): .

    . else: let files = getFiles(req.url.path) let body = displayView(req.url.path, files) # 今回作った関数を呼び出す let headers = newHttpHeaders() headers["Content-Type"] = "text/html" await req.respond(Http200, body, headers) await req.respond(Http404, "")
  18. ファイル自体にもドキュメントを書くことができます。 src/localserver.nim ## # local server ## 現在のディレクトリのファイルを返すサーバーです。 ## ```sh

    ## localserver -p:8080 ## > start server on http://localhost:8080 ## ``` import asyncdispatch import tables import ./localserver/server proc localserver(port=8080) = ... 再度ドキュメント生成させて、サーバーを起動してアクセスして、どう変わったから見てみましょ う