source: trunk/playground/tcp-connector.lisp

Last change on this file was 702, checked in by lgiessmann, 13 years ago

trunk: playground: replaced string-starts-with and string-ends-with by the correpondings functions defined in 'base-tools'

File size: 5.1 KB
Line 
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)
Note: See TracBrowser for help on using the repository browser.