Detach-socket example?
oladon opened this issue · 6 comments
Greetings!
Minimal(ish) test case:
(defvar *api-server* nil)
(defvar *acceptor* ())
(defvar *temp-stream* ())
(defvar *temp-stream2* ())
(defun stop ()
(when *api-server* (hunchentoot:stop *api-server*) (setq *api-server* nil)))
(defun start (&key (port 5000))
(stop)
(setq *api-server* (hunchentoot:start (setf *acceptor* (make-instance 'hunchentoot:easy-acceptor :port port)))))
(hunchentoot:define-easy-handler (test :uri "/test") ()
(format t "Got here... ~A~%" (hunchentoot:within-request-p))
(setf (hunchentoot:content-type*) "text/event-stream")
(setf *temp-stream* (hunchentoot:detach-socket hunchentoot:*acceptor*))
(let ((stream (hunchentoot:send-headers)))
(format t "~&And here... ~a~%" (type-of stream))
(setf *temp-stream2* stream)
(write-sequence (flexi-streams:string-to-octets
(format nil "~&data: here is event #~d for thread #1~%" i)
:external-format :utf8)
stream)
(finish-output stream)))
(start)
Then, run curl -v -N -H 'Accept: text/event-stream' -H 'Connection: keep-alive' localhost:5000/test
My understanding, based on the documentation, is that the call to detach-socket
should return the socket (which should be the same as the one returned by send-headers
), and prevent Hunchentoot from closing the connection. Unfortunately... the connection still gets closed, and the format messages show that detach-socket
returns NIL.
I suspect I'm misunderstanding something about how to use this function — could someone provide feedback, and/or add a simple example to the documentation?
Thanks!
DETACH-SOCKET
was incorrectly documented to return a stream, when it in reality always returns NIL
. You'll have to use the stream returned by SEND-HEADERS
, i.e.:
(hunchentoot:define-easy-handler (test :uri "/test") ()
(format t "Got here... ~A~%" (hunchentoot:within-request-p))
(setf (hunchentoot:content-type*) "text/event-stream")
(let ((stream (hunchentoot:send-headers)))
(format t "~&And here... ~a~%" (type-of stream))
(setf *temp-stream2* stream)
(write-sequence (flexi-streams:string-to-octets
(format nil "~&data: here is the event~%")
:external-format :utf8)
stream)
(finish-output stream)
(hunchentoot:detach-socket hunchentoot:*acceptor*)
(setf *temp-stream* stream)))
I've opened #176 to correct the documentation.
Got it, thanks Hans. I can confirm that send-headers
does return a chunked-io-stream
.
That said, I'm still experiencing the connection being immediately closed when the handler returns, instead of being left open. Do you have any thoughts or suggestions on that?
How do you determine that the connection is immediately closed? When I use
(drakma:http-request "http://localhost:5000" :keep-alive t :close nil)
to send a request to the running server, I see that the connection stays open until Hunchentoot decides to close it because of a timeout.
Hmm... I'm going off curl exiting, and the browser client I have set up reconnecting every 15 seconds. It seems like I'm probably missing something... would you be able to help me get to a minimal example of, say, a counter which sends an incrementing number to a set of sockets every few seconds?
Here's a self-contained example, again using DRAKMA
as client. This is to show that HUNCHENTOOT
in fact can detach the socket and that that socket can then be used to talk to the client in whatever fashion desired. To test, LOAD
this file and then call (test-request)
to establish a connection and see the messages sent by the server.
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload :hunchentoot)
(ql:quickload :drakma))
(defvar *server* nil)
(defun stop ()
(when *server*
(hunchentoot:stop *server*)
(setf *server* nil)))
(defun start (&key (port 5000))
(stop)
(setf *server* (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port port))))
(defun send-socket (stream format &rest args)
(let ((message (with-output-to-string (*standard-output*)
(apply #'format t format args)
(write-char #\Return)
(write-char #\Linefeed)
(finish-output))))
(write-sequence (flexi-streams:string-to-octets message :external-format :utf8)
stream)
(finish-output stream)))
(hunchentoot:define-easy-handler (test :uri "/test") ()
(setf (hunchentoot:content-type*) "text/event-stream")
(let ((stream (hunchentoot:send-headers)))
(hunchentoot:detach-socket hunchentoot:*acceptor*)
(sb-thread:make-thread (lambda ()
(loop
(send-socket stream "Hello ~D" (get-universal-time))
(sleep 1))))))
(defun test-request ()
(let ((stream (nth-value 4 (drakma:http-request "http://localhost:5000/test" :keep-alive t :close nil))))
(format t "Connected~%")
(loop
(format t "Server says: ~A~%" (read-line stream)))))
(start)
Thank you! I'll play around with that and I believe I'll be able to get what I need.