Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detach-socket example? #175

Closed
oladon opened this issue Feb 11, 2020 · 6 comments
Closed

Detach-socket example? #175

oladon opened this issue Feb 11, 2020 · 6 comments

Comments

@oladon
Copy link

oladon commented Feb 11, 2020

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!

@hanshuebner
Copy link
Member

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.

@oladon
Copy link
Author

oladon commented Feb 11, 2020

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?

@hanshuebner
Copy link
Member

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.

@oladon
Copy link
Author

oladon commented Feb 11, 2020

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?

@hanshuebner
Copy link
Member

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)

@oladon
Copy link
Author

oladon commented Feb 13, 2020

Thank you! I'll play around with that and I believe I'll be able to get what I need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants