Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 

Repository files navigation

https://www.lisperati.com/lisplogo_fancy_256.png

wscli – WebSocket Client for Common Lisp

A Common Lisp thread‑safe WebSocket client library.

Why a new library?

wscli is made for the use case where a long-running Lisp process must continuously connects and disconnects to multiple sockets. None of the existing web socket client libraries are satisfactory for this use case as of 2026.

Although there are a few Common Lisp web socket libraries, most of them focus on server but not the client side. Among web socket clients on the market, Allergo’s aserve client seems to be the most mature but it is not open source. wet-sprockets uses global variable and looks a bit incomplete. While fukamachi’s popular websocket-driver contains a performant client that works excellently for most typical use, it is less designed with concurrency and robust long-running services in mind compared to wscli. For example, wscli provides better thread-safety for multiple threads writing to the same socket and some request closing; wscli also does safer gentle shutdown (the user’s handler code can be in a foreign library doing any side-effects and we should wait for it to finish).

API

connect host port path &key handler background origin protocols secure verify hostname

Establish a WebSocket connection. Returns the connection object.

  • host, port – server address.
  • path – resource path, e.g. "/chat".
  • handler – a function of two arguments (type, data) called for incoming messages.
  • background – if true (default) starts the listener in a separate thread.
  • origin – optional Origin header.
  • protocols – list of subprotocol strings to offer.
  • securet for WSS (TLS).
  • verify – passed to cl+ssl:make-ssl-client-stream (default nil).
  • hostname – optional SNI hostname (defaults to host).

connect-url url &rest args

Parses ws://... or wss://... URLs and calls connect. All other keyword arguments are passed through.

Handler callback

The handler function receives two values:

  • Event type – a keyword: :text, :binary, :ping, :pong, :error, or :close.
  • Payload – a string for :text, a (simple-array (unsigned-byte 8)) for :binary, :ping, or :pong, a condition for :error, and (close-code . reason-string) for :close.

For example:

(defun my-handler (type data)
  (format t "~&[WS] ~A: ~S~%" type data))

close-connection conn &optional code reason

Initiate the WebSocket closing handshake (non‑blocking). Default close code is 1000 (normal closure). reason is a UTF‑8 string, truncated to ≤ 123 bytes. After calling this, the connection state becomes :closing and will eventually transition to :closed (either after the peer replies or after the timeout).

wait-until-closed conn &optional timeout

Block the calling thread until the connection reaches :closed. timeout in seconds, nil means wait forever. Returns t on success, nil on timeout.

send-text conn string

Send a text frame. String is encoded as UTF‑8.

send-binary conn octets

Send a binary frame. Octets must be a (unsigned-byte 8) array.

send-ping conn &optional payload

Send a ping control frame. Payload ≤ 125 bytes. The listener automatically replies with a pong.

run-message-loop conn

If the connection was created with :background nil, call this function in your thread to start the reading loop (blocks until the connection closes).

Examples

Echo test

(defun handler (type data)
  (print (list type data)))

(defparameter *conn* (connect "echo.websocket.org" 443 "/"
                              :secure t
                              :handler #'handler))

(send-text *conn* "Hello from the other side!")
(send-text *conn* "I must have called a thousand times...")

;; After the echo comes back, close it.
(close-connection *conn*)
(wait-until-closed *conn*)

Background listener

(defparameter *binance*
  (connect-url "wss://stream.binance.com:9443/stream?streams=btcusdt@trade"
               :handler #'handler))

;; The listener runs in the background, printing trades.
;; Later, shut it down:
(close-connection *binance*)

Foreground loop (no background thread)

(defparameter *conn* (connect "localhost" 8080 "/ws"
                              :background nil
                              :handler #'handler))
;; This blocks until the connection closes:
(run-message-loop *conn*)

Handling different event types

(defun my-handler (type data)
  (case type
    (:text   (format t "Text: ~a~%" data))
    (:binary (format t "Binary: ~a bytes~%" (length data)))
    (:ping   (format t "Received a ping from remote: ~a~%" data))
    (:pong   (format t "Received a pong from remote: ~a~%" data))
    (:error  (format t "Socket about to close due to error ~a~%" data))
    (:close  (format t "Closed with code ~d and reason ~a~%" (car data) (cdr data)))))

Thread‑safety

  • Writing from multiple threads is fine.
  • You can safely call close-connection, send-text, etc. from inside the handler.
  • Multiple concurrent calls to close-connection are harmless; only the first one takes effect.

License

BSD 2-clause.

Common Lisp Implementations

The library is known to work on SBCL, CCL and ECL.

However, ECL’s execution/memory management model is ill‑suited to the library’s concurrent I/O performance; multiple streams or heavy loads are thus inadvisable on ECL.

About

RFC-6455 Common Lisp WebSocket client

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages