https://github.com/monkey-projects/socket-async
Clojure lib that links Java SocketChannels to core.async channels
https://github.com/monkey-projects/socket-async
Last synced: 5 months ago
JSON representation
Clojure lib that links Java SocketChannels to core.async channels
- Host: GitHub
- URL: https://github.com/monkey-projects/socket-async
- Owner: monkey-projects
- License: mit
- Created: 2023-09-15T08:11:57.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-09-15T08:46:58.000Z (over 2 years ago)
- Last Synced: 2023-09-15T23:44:51.644Z (over 2 years ago)
- Language: Clojure
- Size: 13.7 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Socket Async
This is a small Clojure lib that provides functionality to links
[Java SocketChannels](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/nio/channels/SocketChannel.html) to Clojure's [core.async channels](https://clojure.github.io/core.async/).
## Why?
Although it seems like something fairly straightforward and basic, I could not find
anything like it. I just needed something that allows you to write Clojure structures
that can be serialized (into `edn` for example) to a channel, and then can be read
back on the other end. With plain `core.async` channels this is easy. But for some
IPC purposes, it's handy to stream this over a socket channel (like a Unix domain
socket, aka UDS).
It's more complicated than it seems, especially since Java does not provide an
out-of-the-box way to read from a socket channel to an `InputStream`, unless you're
using network channels. So it was not possible with UDS.
## Usage
Include it in your project:
```clojure
# deps.edn
{:deps {com.monkeyprojects/socket-async {:mvn/version "..latest.."}}}
# Or project.clj
[com.monkeyprojects/socket-asybnc "..latest"]
```
The core functionality is in `monkey.socket-async.core`. There are also some
helper functions for UDS in `monkey.socket-async.uds`, but you could also directly
use the [Java classes](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/nio/channels/package-summary.html). You also need `core.async` channels, of course. The two "work horses"
are `read-onto-channel` and `write-from-channel`. The former reads `EDN` from a
`SocketChannel` and puts the decoded objects onto a channel. The latter does the
opposite, it reads objects from a channel, encodes them as `EDN` and writes that
string to the socket channel. For example:
```clojure
(require '[monkey.socket-async.core :as c])
(require '[monkey.socket-async.uds :as uds])
;; Set up socket connection
(def path "test.sock")
(def addr (uds/make-address path))
(def listener (uds/listen-socket addr)) ; Start listening
(def client (uds/connect-socket addr)) ; Connect to the server
(def server (uds/accept listener) ; Accept incoming connection
;; At this point you have to socket channels, client and server
;; that are connected to each other.
(require '[clojure.core.async :as ca :refer [go >!]])
(def in (ca/chan))
(def out (ca/chan))
;; Connect the channels to the sockets, in one direction in this example
(c/read-onto-channel server in)
(c/write-from-channel out client)
;; Send something
(go (>! out {:message "Hi, this is a test"}))
;; Receive it. Set a timeout for safety.
(ca/alts!! [in (ca/timeout 1000)])
;; This should return the message we just wrote
```
The `read-onto-channel` function also accepts an additional exception handler fn,
that gets called when an IO exception occurs. This typically means the connection
was closed. The handler should return `false` if we should not continue processing
further messages. This is the default behaviour. But you could provide your own,
for example to do error reporting or logging, or handle certain edge cases.
## TODO
Currently this only supports `EDN`, but it could be expanded so that you can configure
the serialization method (e.g. use `JSON` instead).
## License
Copyright (c) 2023 by Monkey Projects
[MIT license](LICENSE)