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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
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) = ... 再度ドキュメント生成させて、サーバーを起動してアクセスして、どう変わったから見てみましょ う