clj-sockets

0.1.0


Sockets library for Clojure

dependencies

org.clojure/clojure
1.6.0
org.clojure/core.typed
0.2.77



(this space intentionally left almost blank)
 

A Clojure library wrapping Java Sockets

I noticed there were a number of projects in the Clojure ecosystem using native Java sockets to work with HTTP, IRC, etc., and it seemed a shame that they were all forced to use the same ugly Java interop code. What if they could just write idiomatic Clojure instead?

Contains all of the functions that make up the public API of this project.

(ns clj-sockets.core
  (:require [clojure.core.typed :as t :refer [ann defn fn]]
            [clojure.java.io :refer [writer reader]])
  (:refer-clojure :exclude [defn fn read-line])
  (:import (java.net Socket ServerSocket)
           (java.io BufferedWriter BufferedReader)
           (clojure.lang Seqable)))

We need to annotate these Clojure functions if we want to call them, so that core.typed knows what they take as arguments and what they return. Most clojure.core functions are already annotated, but some still aren't.

(ann ^:no-check clojure.java.io/writer [Socket -> BufferedWriter])
(ann ^:no-check clojure.java.io/reader [Socket -> BufferedReader])
(ann ^:no-check clojure.core/line-seq [BufferedReader -> (Seqable String)])

Connect a socket to a remote host. The call blocks until the socket is connected.

(defn create-socket
  [^String hostname :- String ^Integer port :- Integer] :- Socket
  (Socket. hostname port))
(defn close-socket [^Socket socket :- Socket] :- nil
  "Close the socket, and also closes its input and output streams."
  (.close socket))

Write a string to a BufferedWriter, then flush it.

(defn write-to-buffer
  ^:private [^BufferedWriter output-stream :- BufferedWriter
             ^String string :- String] :- nil
  (.write output-stream string)
  (.flush output-stream))

These fns are annotated manually (instead of using defn) because core.typed blows up if I try to put it in the definition.

Send a string over the socket.

(ann write-to [Socket String -> nil])
(defn write-to
  [socket message]
  (write-to-buffer (writer socket) message))

Send a line over the socket.

(ann write-line [Socket String -> nil])
(defn write-line
  [socket message]
  (write-to socket (str message "\n")))

Get the BufferedReader for a socket.

This is memoized so that we always get the same reader for a given socket. If we didn't do this, every time we did e.g. read-char on a socket we'd get back a new reader, and that last one would be thrown away despite having loaded input into its buffer.

(ann get-reader [Socket -> BufferedReader])
(def ^:private get-reader
  (memoize (fn [^Socket socket :- Socket] :- BufferedReader
             (reader socket))))

Read a single character from a socket.

(ann read-char [Socket -> Character])
(defn read-char
  [socket]
  (let [read-from-buffer (fn [^BufferedReader input-stream :- BufferedReader] :- Integer
                           (.read input-stream))]
    (-> socket
        get-reader
        read-from-buffer
        char)))

Read all the lines currently loaded into the input stream of a socket.

(ann read-lines [Socket -> (Seqable String)])
(defn read-lines
  [socket]
  (line-seq (get-reader socket)))

core.typed is paranoid about Java methods returning nil, but lets you override that if you're fairly sure that it's not going to.

(t/non-nil-return java.io.BufferedReader/readLine :all)

Read a line from the given socket

(ann read-line [Socket -> String])
(defn read-line
  [^Socket socket]
  (let [read-line-from-reader (fn [^BufferedReader reader :- BufferedReader] :- String
                                (.readLine reader))]
    (read-line-from-reader (get-reader socket))))

Initialise a ServerSocket on localhost using a port.

Passing in 0 for the port will automatically assign a port based on what's available.

(defn create-server
  [^Integer port :- Integer] :- ServerSocket
  (ServerSocket. port))

Waits for a connection from another socket to come through, then returns the server's now-connected Socket.

(t/non-nil-return java.net.ServerSocket/accept :all)
(defn listen
  [^ServerSocket server-socket :- ServerSocket] :- Socket
  (.accept server-socket))