[250108] build your own redis in clojure
Table of Contents
링크 : https://blog.pjam.me/posts/toy-redis-clojure/
재미있어 보여서 따라해보았다.
1 소스코드
(ns redis-server-atom
(:require [clojure.java.io :as io]
[clojure.core.async :as a]
[clojure.string :as string])
(:import (java.net ServerSocket)))
(defn atoi
"Attempt to convert a string to integer, returns nil if it can't be parsed"
[string]
(try
(Integer/valueOf string)
(catch NumberFormatException _e
nil)))
(def valid-commands
"Valid commands"
#{"GET" "SET" "DEL" "INCR"})
(defn key-request
"Helper to structure the basic parts of a command"
[command key]
{:command command :key key})
(defn key-value-request
"Helper to structure the various parts of a SET command"
[command key value]
(assoc (key-request command key) :value value))
(defn request-for-command
"Return a structured representation of a client command"
[command parts]
(cond
(= command "GET")
(key-request :get (get parts 1))
(= command "SET")
(key-value-request :set (get parts 1) (get parts 2))
(= command "INCR")
(key-request :incr (get parts 1))
(= command "DEL")
(key-request :del (get parts 1))))
(defn increment-number
"Wrap the lower level operations required to process an increment command"
[db key]
(swap-vals! db (fn [current-state]
(if (contains? current-state key)
(let [number (atoi (get current-state key))]
(if number
(assoc current-state key (str (+ number 1)))
current-state))
(assoc current-state key "1")))))
(defn process-command
"Perform various operations depending on the command sent by the client"
[db request]
(let [command (request :command)
key (request :key)
value (request :value)]
(cond
(= command :get)
(if key
(get @db key "")
"ERR wrong number of arguments for 'get' command")
(= command :set)
(if (and key value)
(do
(swap! db (fn [current-state]
(assoc current-state key value)))
"OK")
"ERR wrong number of arguments for 'set' command")
(= command :del)
(if key
(let [[old-value _] (swap-vals! db (fn [current-state]
(dissoc current-state key)))]
(if (contains? old-value key) "1" "0"))
"ERR wrong number of arguments for 'del' command")
(= command :incr)
(if key
(let [[_ new-value] (increment-number db key)
number (atoi (get new-value key))]
(if number
number
"ERR value is not an integer or out of range"))
"ERR wrong number of arguments for 'incr' command")
:else "Unknown command")))
(defn handle-client
"Read from a connected client, and handles the various commands accepted by the server"
[client-socket db]
(a/go-loop [] ;; (1)
(let [request (.readLine (io/reader client-socket))
writer (io/writer client-socket)]
(if (nil? request)
(do
(println "Nil request, closing")
(.close client-socket))
(let [parts (string/split request #" ")
command (get parts 0)]
(cond
(contains? valid-commands command)
(let [request (request-for-command command parts) ;; (2)
value (process-command db request)] ;; (3)
(.write writer (str value "\n"))
(.flush writer)
(recur))
(= command "QUIT")
(.close client-socket)
:else (do
(println "Unknown request:" request)
(recur))))))))
(defn main
"Start a server and continuously wait for new clients to connect"
[& _args]
(println "About to start ...")
(let [db (atom {})]
(with-open [server-socket (ServerSocket. 3000)]
(loop []
(let [client-socket (.accept server-socket)]
(handle-client client-socket db))
(recur)))))
(main)
;; clj -Sdeps '{:deps {org.clojure/core.async {:mvn/version "1.7.701"}}}' -M redis_server_atom.clj
;; nc localhost 3000