PageRenderTime 55ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/src/redis/internal.clj

http://github.com/ragnard/redis-clojure
Clojure | 267 lines | 217 code | 43 blank | 7 comment | 28 complexity | dc7fbdf1cdc21cfbfeaaf16423294c72 MD5 | raw file
  1. (ns redis.internal
  2. (:refer-clojure :exclude [send read read-line])
  3. (:import [java.io Reader BufferedReader InputStreamReader StringReader]
  4. [java.net Socket]))
  5. (defstruct connection
  6. :host :port :password :db :timeout :socket :reader :writer)
  7. (def *connection* (struct-map connection
  8. :host "127.0.0.1"
  9. :port 6379
  10. :password nil
  11. :db 0
  12. :timeout 5000
  13. :socket nil
  14. :reader nil
  15. :writer nil))
  16. (def *cr* 0x0d)
  17. (def *lf* 0x0a)
  18. (defn- cr? [c] (= c *cr*))
  19. (defn- lf? [c] (= c *lf*))
  20. (defn- uppercase [#^String s] (.toUpperCase s))
  21. (defn- trim [#^String s] (.trim s))
  22. (defn- parse-int [#^String s] (Integer/parseInt s))
  23. ;(defn- char-array [len] (make-array Character/TYPE len))
  24. (defn connect-to-server
  25. "Create a Socket connected to server"
  26. [server]
  27. (let [{:keys [host port timeout]} server
  28. socket (Socket. #^String host #^Integer port)]
  29. (doto socket
  30. (.setTcpNoDelay true)
  31. (.setKeepAlive true))))
  32. (defn with-server*
  33. [server-spec func]
  34. (let [connection (merge *connection* server-spec)]
  35. (with-open [#^Socket socket (connect-to-server connection)]
  36. (let [input-stream (.getInputStream socket)
  37. output-stream (.getOutputStream socket)
  38. reader (BufferedReader. (InputStreamReader. input-stream))]
  39. (binding [*connection* (assoc connection
  40. :socket socket
  41. :reader reader)]
  42. (func))))))
  43. (defn socket* []
  44. (or (:socket *connection*)
  45. (throw (Exception. "Not connected to a Redis server"))))
  46. (defn send-command
  47. "Send a command string to server"
  48. [#^String cmd]
  49. (let [out (.getOutputStream (#^Socket socket*))
  50. bytes (.getBytes cmd)]
  51. (.write out bytes)))
  52. (defn read-crlf
  53. "Read a CR+LF combination from Reader"
  54. [#^Reader reader]
  55. (let [cr (.read reader)
  56. lf (.read reader)]
  57. (when-not
  58. (and (cr? cr)
  59. (lf? lf))
  60. (throw (Exception. "Error reading CR/LF")))
  61. nil))
  62. (defn read-line-crlf
  63. "Read from reader until exactly a CR+LF combination is
  64. found. Returns the line read without trailing CR+LF.
  65. This is used instead of Reader.readLine() method since that method
  66. tries to read either a CR, a LF or a CR+LF, which we don't want in
  67. this case."
  68. [#^BufferedReader reader]
  69. (loop [line []
  70. c (.read reader)]
  71. (when (< c 0)
  72. (throw (Exception. "Error reading line: EOF reached before CR/LF sequence")))
  73. (if (cr? c)
  74. (let [next (.read reader)]
  75. (if (lf? next)
  76. (apply str line)
  77. (throw (Exception. "Error reading line: Missing LF"))))
  78. (recur (conj line (char c))
  79. (.read reader)))))
  80. ;;
  81. ;; Reply dispatching
  82. ;;
  83. (defn- do-read [#^Reader reader #^chars cbuf offset length]
  84. (let [nread (.read reader cbuf offset length)]
  85. (if (not= nread length)
  86. (recur reader cbuf (+ offset nread) (- length nread)))))
  87. (defn reply-type
  88. ([#^BufferedReader reader]
  89. (char (.read reader))))
  90. (defmulti parse-reply reply-type :default :unknown)
  91. (defn read-reply
  92. ([]
  93. (let [reader (*connection* :reader)]
  94. (read-reply reader)))
  95. ([#^BufferedReader reader]
  96. (parse-reply reader)))
  97. (defmethod parse-reply :unknown
  98. [#^BufferedReader reader]
  99. (throw (Exception. (str "Unknown reply type:"))))
  100. (defmethod parse-reply \-
  101. [#^BufferedReader reader]
  102. (let [error (read-line-crlf reader)]
  103. (throw (Exception. (str "Server error: " error)))))
  104. (defmethod parse-reply \+
  105. [#^BufferedReader reader]
  106. (read-line-crlf reader))
  107. (defmethod parse-reply \$
  108. [#^BufferedReader reader]
  109. (let [line (read-line-crlf reader)
  110. length (parse-int line)]
  111. (if (< length 0)
  112. nil
  113. (let [#^chars cbuf (char-array length)]
  114. (do
  115. (do-read reader cbuf 0 length)
  116. (read-crlf reader) ;; CRLF
  117. (String. cbuf))))))
  118. (defmethod parse-reply \*
  119. [#^BufferedReader reader]
  120. (let [line (read-line-crlf reader)
  121. count (parse-int line)]
  122. (if (< count 0)
  123. nil
  124. (loop [i count
  125. replies []]
  126. (if (zero? i)
  127. replies
  128. (recur (dec i) (conj replies (read-reply reader))))))))
  129. (defmethod parse-reply \:
  130. [#^BufferedReader reader]
  131. (let [line (trim (read-line-crlf reader))
  132. int (parse-int line)]
  133. int))
  134. ;;
  135. ;; Command functions
  136. ;;
  137. (defn- str-join
  138. "Join elements in sequence with separator"
  139. [separator sequence]
  140. (apply str (interpose separator sequence)))
  141. (defn inline-command
  142. "Create a string for an inline command"
  143. [name & args]
  144. (let [cmd (str-join " " (conj args name))]
  145. (str cmd "\r\n")))
  146. (defn bulk-command
  147. "Create a string for a bulk command"
  148. [name & args]
  149. (let [data (str (last args))
  150. data-length (count (str data))
  151. args* (concat (butlast args) [data-length])
  152. cmd (apply inline-command name args*)]
  153. (str cmd data "\r\n")))
  154. (defn- sort-command-args-to-string
  155. [args]
  156. (loop [arg-strings []
  157. args args]
  158. (if (empty? args)
  159. (str-join " " arg-strings)
  160. (let [type (first args)
  161. args (rest args)]
  162. (condp = type
  163. :by (let [pattern (first args)]
  164. (recur (conj arg-strings "BY" pattern)
  165. (rest args)))
  166. :limit (let [start (first args)
  167. end (second args)]
  168. (recur (conj arg-strings "LIMIT" start end)
  169. (drop 2 args)))
  170. :get (let [pattern (first args)]
  171. (recur (conj arg-strings "GET" pattern)
  172. (rest args)))
  173. :store (let [key (first args)]
  174. (recur (conj arg-strings "STORE" key)
  175. (rest args)))
  176. :alpha (recur (conj arg-strings "ALPHA") args)
  177. :asc (recur (conj arg-strings "ASC") args)
  178. :desc (recur (conj arg-strings "DESC") args)
  179. (throw (Exception. (str "Error parsing SORT arguments: Unknown argument: " type))))))))
  180. (defn sort-command
  181. [name & args]
  182. (when-not (= name "SORT")
  183. (throw (Exception. "Sort command name must be 'SORT'")))
  184. (let [key (first args)
  185. arg-string (sort-command-args-to-string (rest args))
  186. cmd (str "SORT " key)]
  187. (if (empty? arg-string)
  188. (str cmd "\r\n")
  189. (str cmd " " arg-string "\r\n"))))
  190. (def command-fns {:inline 'inline-command
  191. :bulk 'bulk-command
  192. :sort 'sort-command})
  193. (defn parse-params
  194. "Return a restructuring of params, which is of form:
  195. [arg* (& more)?]
  196. into
  197. [(arg1 arg2 ..) more]"
  198. [params]
  199. (let [[args rest] (split-with #(not= % '&) params)]
  200. [args (last rest)]))
  201. (defmacro defcommand
  202. "Define a function for Redis command name with parameters
  203. params. Type is one of :inline, :bulk or :sort, which determines how
  204. the command string is constructued."
  205. ([name params type] `(defcommand ~name ~params ~type (fn [reply#] reply#)))
  206. ([name params type reply-fn] `(~name ~params ~type ~reply-fn)
  207. (do
  208. (let [command (uppercase (str name))
  209. command-fn (type command-fns)
  210. [command-params
  211. command-params-rest] (parse-params params)]
  212. `(defn ~name
  213. ~params
  214. (let [request# (apply ~command-fn
  215. ~command
  216. ~@command-params
  217. ~command-params-rest)]
  218. (send-command request#)
  219. (~reply-fn (read-reply)))))
  220. )))
  221. (defmacro defcommands
  222. [& command-defs]
  223. `(do ~@(map (fn [command-def]
  224. `(defcommand ~@command-def)) command-defs)))