1 | ;;+----------------------------------------------------------------------------- |
---|
2 | ;;+ Isidorus |
---|
3 | ;;+ (c) 2008-2010 Marc Kuester, Christoph Ludwig, Lukas Georgieff |
---|
4 | ;;+ |
---|
5 | ;;+ Isidorus is freely distributable under the LLGPL license. |
---|
6 | ;;+ You can find a detailed description in trunk/docs/LLGPL-LICENSE.txt and |
---|
7 | ;;+ trunk/docs/LGPL-LICENSE.txt. |
---|
8 | ;;+----------------------------------------------------------------------------- |
---|
9 | |
---|
10 | ;; source: http://mihai.bazon.net/blog/howto-multi-threaded-tcp-server-in-common-lisp |
---|
11 | |
---|
12 | (asdf:operate 'asdf:load-op :isidorus) |
---|
13 | |
---|
14 | |
---|
15 | (defun make-server (&key (hostname "localhost") (port 8000)) |
---|
16 | (declare (string hostname) (number port)) |
---|
17 | (usocket:socket-listen hostname port :reuse-address t)) |
---|
18 | |
---|
19 | |
---|
20 | (defun wait-for-client (server-socket) |
---|
21 | (declare (usocket:stream-server-usocket server-socket)) |
---|
22 | (usocket:wait-for-input server-socket) |
---|
23 | (usocket:socket-accept server-socket)) |
---|
24 | |
---|
25 | |
---|
26 | (defun read-from-client (client-socket) |
---|
27 | (declare (usocket:stream-usocket client-socket)) |
---|
28 | (let* ((header (read-tcp-header (usocket:socket-stream client-socket))) |
---|
29 | (payload (read-tcp-payload (usocket:socket-stream client-socket) header))) |
---|
30 | (list :headers header |
---|
31 | :payload payload))) |
---|
32 | |
---|
33 | |
---|
34 | (defun read-tcp-header (stream) |
---|
35 | (declare (Stream stream)) |
---|
36 | (let ((line (string-right-trim (list #\cr) (read-line stream)))) |
---|
37 | (if (string= "" line) |
---|
38 | (list "") |
---|
39 | (progn |
---|
40 | (append (list line) (read-tcp-header stream)))))) |
---|
41 | |
---|
42 | |
---|
43 | (defun read-tcp-payload (stream header-list) |
---|
44 | (declare (Stream stream) |
---|
45 | (list header-list)) |
---|
46 | (let ((content-length |
---|
47 | (let ((val |
---|
48 | (loop for line in header-list |
---|
49 | when (search "content-length:" (string-downcase line) :test #'string=) |
---|
50 | return (let ((value (subseq line (length "content-length:")))) |
---|
51 | (parse-integer value))))) |
---|
52 | (if val val 0))) |
---|
53 | (payload "")) |
---|
54 | (dotimes (idx content-length payload) |
---|
55 | (setf payload (concatenate 'string payload (string (read-char stream))))))) |
---|
56 | |
---|
57 | |
---|
58 | (defun send-to-client (client-socket message-string &key (content-type "text/plain")) |
---|
59 | (declare (usocket:stream-usocket client-socket) |
---|
60 | (String message-string content-type)) |
---|
61 | (format (usocket:socket-stream client-socket) |
---|
62 | "~a~c~c~a~a~c~c~a~a~c~c~a~c~c~c~c~a" |
---|
63 | "HTTP/1.1 200 OK" #\return #\newline |
---|
64 | "Content-Length: " (write-to-string (length message-string)) #\return #\newline |
---|
65 | "Content-Type: " content-type #\return #\newline |
---|
66 | "Connection: close" #\return #\newline |
---|
67 | #\return #\newline |
---|
68 | message-string) |
---|
69 | (force-output (usocket:socket-stream client-socket)) |
---|
70 | (usocket:socket-close client-socket)) |
---|
71 | |
---|
72 | |
---|
73 | (defvar *stop-listen* nil "if this variable is set to t, the listener stops to listen after the next client has been accepted") |
---|
74 | |
---|
75 | |
---|
76 | (defun stop-listen-for-clients (server) |
---|
77 | (setf *stop-listen* t) |
---|
78 | (usocket:socket-close server) |
---|
79 | (base-tools:close-tm-store)) |
---|
80 | |
---|
81 | |
---|
82 | (defun client-task (client-socket) |
---|
83 | (declare (usocket:stream-usocket client-socket)) |
---|
84 | (handler-case |
---|
85 | (let ((client-data (read-from-client client-socket))) |
---|
86 | (let ((response |
---|
87 | (cond ((tools:string-starts-with (first (getf client-data :headers)) |
---|
88 | "GET /json/psis") |
---|
89 | (get-psis)) |
---|
90 | ((tools-string-starts-with (first (getf client-data :headers)) |
---|
91 | "GET /json/get/") |
---|
92 | (get-fragment (get-requested-psi-of-http-header |
---|
93 | (first (getf client-data :headers))))) |
---|
94 | (t |
---|
95 | (concatenate 'string ">> bad request: ~a~%" |
---|
96 | (first (getf client-data :headers))))))) |
---|
97 | (send-to-client client-socket response))) |
---|
98 | (condition () |
---|
99 | (usocket:socket-close client-socket)))) |
---|
100 | |
---|
101 | |
---|
102 | (defun listen-for-clients (server) |
---|
103 | (declare (usocket:stream-server-usocket server)) |
---|
104 | (setf *stop-listen* nil) |
---|
105 | (sb-thread:make-thread |
---|
106 | (lambda() |
---|
107 | (funcall (lambda(srv) |
---|
108 | (do ((stop-p *stop-listen*) (counter 0)) ((not (null stop-p))) |
---|
109 | (let ((client (wait-for-client srv))) |
---|
110 | (format t "client # ~a connected~%" counter) |
---|
111 | (sb-thread:make-thread |
---|
112 | (lambda() (funcall #'client-task client)) |
---|
113 | :name (format nil "worker-thread: ~a" counter))) |
---|
114 | (incf counter) |
---|
115 | (setf stop-p *stop-listen*))) |
---|
116 | server)) |
---|
117 | :name "server-listener")) |
---|
118 | |
---|
119 | |
---|
120 | (defun get-psis () |
---|
121 | (isidorus-threading:with-reader-lock |
---|
122 | (json-exporter:get-all-topic-psis :revision 0))) |
---|
123 | |
---|
124 | |
---|
125 | (defun get-fragment(psi) |
---|
126 | (let ((fragment (isidorus-threading:with-reader-lock |
---|
127 | (d:get-latest-fragment-of-topic psi)))) |
---|
128 | (if (and fragment |
---|
129 | (d:find-item-by-revision (d:topic fragment) 0)) |
---|
130 | (json-exporter:export-construct-as-isidorus-json-string fragment :revision 0) |
---|
131 | (concatenate 'string psi " not found")))) |
---|
132 | |
---|
133 | |
---|
134 | (defun get-requested-psi-of-http-header (first-header-line) |
---|
135 | (declare (String first-header-line)) |
---|
136 | (when (and (tools:string-starts-with first-header-line "GET /json/get/") |
---|
137 | (or (tools:string-ends-with first-header-line "HTTP/1.0") |
---|
138 | (tools:string-ends-with first-header-line "HTTP/1.1"))) |
---|
139 | (let ((psi (subseq first-header-line |
---|
140 | (length "GET /json/get/") |
---|
141 | (- (length first-header-line) (length "HTTP/1.0"))))) |
---|
142 | (hunchentoot:url-decode (string-trim '(#\space) psi))))) |
---|
143 | |
---|
144 | |
---|
145 | (defun main() |
---|
146 | (format t ">> entered (main)") |
---|
147 | (base-tools:open-tm-store "/home/lukas/.sbcl/site/isidorus/trunk/src/data_base") |
---|
148 | (defvar *server* (make-server :port 8080)) |
---|
149 | (listen-for-clients *server*)) |
---|
150 | |
---|
151 | |
---|
152 | (main) |
---|