78 lines
3.6 KiB
Fennel
78 lines
3.6 KiB
Fennel
;; a simple websocket echo server
|
|
;;
|
|
;; usage: fennel echo.fnl <port>
|
|
;;
|
|
;; test via websocat ws://127.0.0.1:<port>
|
|
|
|
(local port (or (. arg 1) 0))
|
|
(local http-server (require :http.server))
|
|
(local http-headers (require :http.headers))
|
|
(local websocket (require :http.websocket))
|
|
|
|
;; this handler contains the main logic. it is called further down in this
|
|
;; file, as soon as a websocket connection has been established.
|
|
|
|
(fn handle-websocket [ws]
|
|
;; TODO: Generate birb name
|
|
(assert (ws:accept))
|
|
;; connection is open
|
|
(print "connection opened")
|
|
(ws:send "Welcome to the echo server! Send any command, it will be sent back to you.")
|
|
(var closed? false)
|
|
(while (not closed?)
|
|
(local (data opcode) (ws:receive))
|
|
(if data
|
|
(do
|
|
(assert (= opcode :text))
|
|
(print "opcode" opcode "data" data)
|
|
(ws:send data))
|
|
;; connection has been closed
|
|
(do
|
|
(print "connection closed")
|
|
(ws:close)
|
|
(set closed? true)))))
|
|
|
|
;; this is the low-level server code. it's an adapted version of
|
|
;; https://github.com/daurnimator/lua-http/blob/ddab2835/examples/server_hello.lua
|
|
|
|
(local server
|
|
(assert (http-server.listen {:host :localhost
|
|
:onerror (fn [server context op err errno]
|
|
(var msg (.. op " on " (tostring context) " failed"))
|
|
(when err
|
|
(set msg (.. msg ": " (tostring err))))
|
|
(assert (io.stderr:write msg "\n")))
|
|
:onstream (fn [server stream]
|
|
(let [headers (assert (stream:get_headers))
|
|
method (headers:get ":method")]
|
|
;; log request
|
|
(assert (io.stdout:write (string.format "[%s] \"%s %s HTTP/%g\" \"%s\" \"%s\"\n"
|
|
(os.date "%d/%b/%Y:%H:%M:%S %z")
|
|
(or method "")
|
|
(or (headers:get ":path") "")
|
|
stream.connection.version
|
|
(or (headers:get :referer) "-")
|
|
(or (headers:get :user-agent) "-"))))
|
|
;; start and handle websocket connection
|
|
(local ws (websocket.new_from_stream stream headers))
|
|
(if ws
|
|
(handle-websocket ws)
|
|
;; if we couldn't establish the websocket connection, something's wrong
|
|
(assert (stream:write_headers (doto (http-headers.new)
|
|
(: :append ::status :400)) true)))))
|
|
: port})))
|
|
|
|
(assert (server:listen))
|
|
|
|
(let [(_ _ bound-port) (server:localname)]
|
|
(assert (io.stderr:write (.. "Now listening on port " bound-port "\n"))))
|
|
|
|
;; automatically start server when run from the command line
|
|
(when (> (length arg) 0)
|
|
(assert (server:loop)))
|
|
|
|
(comment
|
|
;; run this to handle a response manually
|
|
(for [i 1 3]
|
|
(server:step))
|
|
)
|