;; a simple websocket echo server ;; ;; usage: fennel echo.fnl ;; ;; test via websocat ws://127.0.0.1: (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)) )