Skip to content

Commit 0e036fc

Browse files
committed
lesson 16 source code
1 parent 6817247 commit 0e036fc

File tree

14 files changed

+941
-0
lines changed

14 files changed

+941
-0
lines changed

mini-redis/README.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
### Запуск сервера
2+
3+
```shell
4+
lein repl
5+
```
6+
7+
```clojure
8+
(-main)
9+
```
10+
11+
Сервер запустится на 6379 порту
12+
13+
redis-cli устанавливается вместе с самим redis https://redis.io/docs/getting-started/installation/
14+
15+
Запуск клиента
16+
17+
```shell
18+
redis-cli
19+
```
20+
21+
Поддерживаемые команды
22+
23+
```shell
24+
echo hello
25+
26+
ping
27+
28+
set my-key some-value
29+
30+
set my-key some-value px 1200
31+
32+
get my-key
33+
```

mini-redis/project.clj

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(defproject mini-redis "0.1.0-SNAPSHOT"
2+
:description "Redis (ligth version of it) implemented in Clojure"
3+
4+
:dependencies [[org.clojure/clojure "1.11.1"]
5+
[org.clojure/core.async "1.6.673"]]
6+
7+
:repl-options {:init-ns mini-redis.core})

mini-redis/src/mini_redis/core.clj

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
(ns mini-redis.core
2+
(:require
3+
[clojure.java.io :as io]
4+
[clojure.string :as str])
5+
(:import
6+
[java.io BufferedReader Writer]
7+
[java.net ServerSocket])
8+
(:gen-class))
9+
10+
11+
;; используем атом в качестве in-memory хранилища
12+
(def database
13+
(atom {}))
14+
15+
16+
17+
;; обрабатываем команды от клиентов
18+
(defmulti handle-command
19+
(fn [[command & _]]
20+
(keyword (str/lower-case command))))
21+
22+
23+
(defmethod handle-command :ping
24+
[_]
25+
"PONG")
26+
27+
28+
(defmethod handle-command :echo
29+
[[_ [arg-len arg]]]
30+
arg)
31+
32+
33+
(defmethod handle-command :set
34+
[[_ [key-len key val-len val opt-len opt optarg-len optarg]]]
35+
;; сохраняем значение
36+
(swap! database assoc key val)
37+
38+
(when (and opt (= (.toUpperCase opt) "PX"))
39+
(let [timeout (Integer/parseInt optarg)]
40+
(future
41+
(Thread/sleep timeout)
42+
(swap! database dissoc key))))
43+
;; ответ клиенту
44+
"OK")
45+
46+
47+
(defmethod handle-command :get
48+
[[_ [key-len key]]]
49+
(if-some [entry (find @database key)]
50+
(val entry)
51+
"(nil)"))
52+
53+
54+
;; needed for redis-cli
55+
(defmethod handle-command :command
56+
[_]
57+
"O hai")
58+
59+
60+
61+
;; обрабатываем сообщения от клиента
62+
(defn error? [data]
63+
(-> (class data)
64+
(supers)
65+
(contains? Throwable)))
66+
67+
68+
(defn reply [data]
69+
(let [data-type (cond
70+
(string? data) "+"
71+
(integer? data) ":"
72+
(error? data) "-")]
73+
(str/join [data-type data "\r\n"])))
74+
75+
76+
(defn handle-message
77+
"Pass parsed message to the dispatch function and format result for client"
78+
[message]
79+
(let [[number-of-arguments command-string-len command & args] message]
80+
(-> (handle-command [command args])
81+
(reply))))
82+
83+
84+
85+
(comment
86+
(handle-command ["ECHO" [5 "HELLO"]])
87+
(handle-message [nil nil "ECHO" 5 "HELLO"]))
88+
89+
90+
91+
(defn read-message
92+
"Read all lines of textual data from the given socket"
93+
[^BufferedReader socket-reader]
94+
(loop [line (.readLine socket-reader)
95+
res []]
96+
(cond
97+
;; сокет закрылся
98+
(nil? line) res
99+
;; клиент ничего не ввёл, но соединение еще открыто
100+
(not (.ready socket-reader)) (conj res line)
101+
;; читаем следующую строку
102+
:otherwise (recur (.readLine socket-reader)
103+
(conj res line)))))
104+
105+
106+
107+
(comment
108+
(read-message (io/reader (char-array "*2\r\n$4\r\necho\r\n$5\r\nhello")))
109+
(read-message (io/reader (char-array "*5\r\n$3\r\nset\r\n$4\r\nname\r\n$6\r\nSergey\r\n$2\r\nRX\r\n$5\r\n10000"))))
110+
111+
112+
113+
(defn send-message
114+
"Send the given string message out over the given socket"
115+
[^Writer socket-writer ^String msg]
116+
(.write socket-writer msg)
117+
(.flush socket-writer))
118+
119+
120+
121+
(defn handle-client
122+
"Create a separate thread for each client to execute commands"
123+
[socket handler]
124+
;; запускаем отдельный поток для каждого клиента
125+
(future
126+
(with-open [reader (io/reader socket)
127+
writer (io/writer socket)]
128+
;; обрабатываем команды от клиента
129+
(doseq [msg-in (repeatedly #(read-message reader))
130+
:while (not (empty? msg-in))
131+
:let [msg-out (handler msg-in)]]
132+
(println "msg-in" msg-in "msg-out" msg-out "\n")
133+
;; отправляем ответ
134+
(send-message writer msg-out)))))
135+
136+
137+
138+
(defn run-server
139+
"Run socket server on the specified port"
140+
[port handler]
141+
;; создаём сокет сервер
142+
(let [server-sock (ServerSocket. port)]
143+
(.setReuseAddress server-sock true)
144+
145+
;; обрабатываем подключения в отдельном потоке
146+
(future
147+
(while true
148+
(let [socket (.accept server-sock)]
149+
(handle-client socket handler))))
150+
151+
;; возвращаем объект сервера
152+
server-sock))
153+
154+
155+
156+
;; graceful shutdown
157+
(defn shutdown-hook [server]
158+
(.addShutdownHook (Runtime/getRuntime)
159+
(Thread. ^Runnable
160+
(fn []
161+
(.close server)
162+
(shutdown-agents)))))
163+
164+
165+
;; точка входа
166+
(defn -main
167+
[& args]
168+
(let [server (run-server 6379 handle-message)]
169+
(shutdown-hook server)
170+
server))
171+
172+
173+
174+
(comment
175+
(def server
176+
(-main))
177+
178+
@database
179+
180+
(.close server)
181+
nil)
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(ns mini-redis.core-test
2+
(:require [clojure.test :refer :all]
3+
[mini-redis.core :refer :all]))
4+
5+
(deftest a-test
6+
(testing "FIXME, I fail."
7+
(is (= 0 1))))

otus-16/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Clojure Developer. Урок 16
2+
3+
Домашнее задание
4+
5+
- распаковать архив [logs.7z](logs.7z)
6+
- написать функцию для чтения логов из файлов архива
7+
- написать функцию для парсинга логов согласно стандарту https://httpd.apache.org/docs/2.4/logs.html#combined
8+
- реализовать функционал для агрегации метрик из записей логов в многопоточном стиле
9+
- подсчитать общее количество байт
10+
- количество байт, суммарно отданных по заданному URL (или по всем URLам)
11+
- количество URL, запрошенных с заданным полем Referer

otus-16/project.clj

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(defproject otus-16 "0.1.0-SNAPSHOT"
2+
:description "OTUS Clojure Developer | lesson #16"
3+
4+
:dependencies [[org.clojure/clojure "1.11.1"]
5+
[compojure "1.6.3"]
6+
[ring/ring-defaults "0.4.0"]
7+
[ring/ring-jetty-adapter "1.9.6"]
8+
[clj-http "3.12.3"]
9+
[schejulure "1.0.1"]
10+
[org.flatland/useful "0.11.6"]]
11+
12+
:repl-options {:init-ns otus-16.core})

0 commit comments

Comments
 (0)