我需要从https://beta.quicklisp.org/quicklisp.lisp下载最新版本的quicklisp.lisp
首先,我在代码中使用common-lisp库usocket和cl+ssl尝试下载html页面https://www.quicklisp.org/beta/ ,并且有效
(usocket:with-client-socket (sock stream "quicklisp.org" 443)
(let ((https (cl+ssl:make-ssl-client-stream
stream :unwrap-stream-p t
:external-format '(:iso-8859-1 :eol-style :lf))))
(unwind-protect
(progn
(format https "GET /beta/ HTTP/1.0~%Host:www.quicklisp.org~%Connection:keep-alive~2%")
(force-output https)
(loop for line = (read-line https nil)
while line do (format t "HTTPS> ~a~%" line)))
(close https))))
然后我稍微修改一下上面的 Quicklisp.lisp 文件的代码:
(usocket:with-client-socket (sock stream "beta.quicklisp.org" 443)
(let ((https (cl+ssl:make-ssl-client-stream
stream :unwrap-stream-p t
:external-format '(:iso-8859-1 :eol-style :lf))))
(unwind-protect
(progn
(format https "GET /quicklisp.lisp HTTP/1.1~%Host:beta.quicklisp.org~%Connection:keep-alive~%Accept: */*~2%")
(force-output https)
(loop for line = (read-line https nil)
while line do (format t "HTTPS> ~a~%" line)))
(close https))))
这一次,它失败了,运气不好。错误消息显示:
A failure in the SSL library occurred on handle #.(SB-SYS:INT-SAP #X7FFFDC019A80) (return code: 1).
SSL error queue:
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
[Condition of type CL+SSL::SSL-ERROR-SSL]
Restarts:
0: [RETRY] Retry SLIME interactive evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] abort thread (#<THREAD "worker" RUNNING {10088E6223}>)
Backtrace:
0: (CL+SSL::SSL-SIGNAL-ERROR #.(SB-SYS:INT-SAP #X7FFFDC019A80) #<FUNCTION CL+SSL::SSL-CONNECT> 1 -1)
Locals:
ERROR-CODE = 1
HANDLE = #.(SB-SYS:INT-SAP #X7FFFDC019A80)
ORIGINAL-ERROR = -1
SYSCALL = #<FUNCTION CL+SSL::SSL-CONNECT>
1: (CL+SSL:MAKE-SSL-CLIENT-STREAM #<unavailable lambda list>)
[No Locals]
2: ((LAMBDA ()))
3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (USOCKET:WITH-CLIENT-SOCKET (SOCK STREAM "beta.quicklisp.org" 443) (LET (#) (UNWIND-PROTECT # #))) #<NULL-LEXENV>)
4: (EVAL (USOCKET:WITH-CLIENT-SOCKET (SOCK STREAM "beta.quicklisp.org" 443) (LET (#) (UNWIND-PROTECT # #))))
5: ((LAMBDA NIL :IN SWANK:INTERACTIVE-EVAL))
我知道还有另一个 common-lisp 库 drakma 可以满足我的需要。但我只是好奇为什么我的方法失败了?
最佳答案
我不熟悉 Common Lisp 中的 SSL 工作原理,但来自 SSLLabs report可以看出,beta.quicklistp.org
仅在客户端使用Server Name Indication (SNI)时才起作用。而您的工作示例中的 quicklistp.org
不需要 SNI。
如果我正确地解释了您的代码,您首先会创建到目标主机的 TCP 连接,然后在该连接周围封装 TLS。由于在进行此包装时您没有提供有关目标主机名的任何信息,因此我假设 TLS 包装器不会知道主机名,因此无法在 TLS 握手中告诉服务器所请求的主机名,即它无法使用 SNI。由于服务器需要 SNI,因此连接将失败。
根据to the documentation有一种方法可以指定主机名。来自文档:
Function CL+SSL:MAKE-SSL-CLIENT-STREAM
(fd-or-stream &key
external-format certificate key password close-callback
(unwrap-streams-p t)
hostname
)
...
hostname if specified, will be sent by client during TLS negotiation,
according to the Server Name Indication (SNI) extension to the TLS.
除此之外:
您正在执行 HTTP/1.1
请求,但无法正确处理 HTTP/1.1
响应(例如分块编码) 。对于像您这样的简单请求,我建议仅使用 HTTP/1.0
。此外,您的代码期望服务器在数据传输完成后关闭连接,即您不从 HTTP header 中提取正文大小并仅读取给定的字节数。在这种情况下使用 Connection: keep-alive 是一个坏主意,因为这样您会要求服务器在发送正文后保持连接打开。此外,无论如何使用 HTTP/1.1
时,Connection: keep-alive
都是隐式的。
关于通过 https 下载文件时出现 SSL 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36705427/