用usocket库实现Common Lisp中的WebSocket客户端

4

我正在尝试升级一种协议,从HTTP 1.1切换到WebSockets。我尝试使用usocket。下面是我的代码(也可在GitHub gist上查看)。在握手读取后,函数会返回NILunexpected EOF错误。

;; Define parameter sock for usocket stream
;; echo.websocket.org is a site for testing websockets
(defparameter sock (usocket:socket-connect "echo.websocket.org" 80))

;; Output confirms WebSocket protocol handshake as this implemented in browsers 
(format (usocket:socket-stream sock) "~A~%~A~%~A~%~A~%~A~A~%~A~%~A~%~%" 
        "GET /?encoding=text HTTP/1.1"
        "Connection: Upgrade"
        "Host: echo.websocket.org"
        "Origin: http://www.websocket.org"
        "Sec-WebSocket-Key: " (generate-websocket-key)
        "Sec-WebSocket-Version: 13"
        "Upgrade: websocket")

;; Write output to stream
(force-output (usocket:socket-stream sock))

;; Returns NIL
(do ((line                                                             
      (read-line (usocket:socket-stream sock) nil)                        
      (read-line (usocket:socket-stream sock) nil)))                      
    ((not line))                                                       
  (format t "~A" line))

;; Returns error: unexpected EOF
(read-line (usocket:socket-stream sock))

;; Returns NIL
(listen (usocket:socket-stream sock))

2
那个Gist是一个最小的例子吗?为了自包含性,也最好在这里添加那段代码。请同时包含实际完整的错误信息。如果你能从调试器中获取回溯信息,那可能会有所帮助。 - Joshua Taylor
谢谢您的建议,我会尽快接近我的桌面键盘并加以采纳。 - inkuzmin
3
你确定 ~% 的结果是你在 HTTP 连接中需要的换行符吗? - Joshua Taylor
1
是的,我认为是这样的。因为当我使用该行终止符发送普通的HTTP GET请求时,服务器响应良好,我可以使用read-lines读取它。 - inkuzmin
1
@JoshuaTaylor,哦,你完全正确!谢谢! - inkuzmin
1
风格提示:不要在do循环中重复使用read-line表达式,可以使用(loop :for line := (read-line in nil) :while line :do (#|...|#)) - Svante
1个回答

9
FORMAT语句中的~%对于HTTP协议而言是不可移植的。它输出换行符#\newline。换行符取决于代码运行的平台:cr(旧版Mac,Lisp机器),crlf(Windows)或lf(Unix)。因此,如果您将换行符写入流中,则输出取决于您运行的平台(或者Lisp系统认为应该执行什么操作)。Windows与HTTP具有相同的行尾约定。

注意:

  • 在Unix系统上,通常#\newline#\linefeed相同。一个字符。
  • 在Windows系统上,通常#\newline与序列#\return #\linefeed相同。两个字符。一些Lisp系统可能会忽略这一点并使用Unix惯例。
HTTP使用crlf。要可靠地编写crlf,您必须编写字符#\return#\linefeed(format stream "~a~a" #\return #\linefeed)
(defun write-crlf (stream)
  (write-char #\return stream)
  (write-char #\linefeed stream))

有些服务器可能愚蠢到读取不遵循标准HTTP crlf约定的输入。

可能有一些方法可以在打开流时指定行结束约定。然而,usocket并没有提供这样的功能。

我也会使用finish-output(等待完成)而不是force-output(不等待IO操作完成)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接