我需要从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))
我知道有另一个常见的lisp库drakma可以做我需要的东西。但我只是好奇为什么我的方法失败了?
答案 0 :(得分:2)
我不熟悉常见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标头中提取主体大小并只读取给定的字节数。在这种情况下使用Connection: keep-alive
是一个坏主意,因为这样您就可以要求服务器在发送正文后保持连接打开。无论如何,使用Connection: keep-alive
时隐含HTTP/1.1
。
答案 1 :(得分:0)
根据Steffen的重要解释,我找到了让它运作的方法:
(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)
:hostname "beta.quicklisp.org")))
(unwind-protect
(progn
(format https "GET /quicklisp.lisp HTTP/1.0~%Host:beta.quicklisp.org~%Accept: */*~2%")
(force-output https)
(loop for line = (read-line https nil)
while line do (format t "HTTPS> ~a~%" line)))
(close https))))
谢谢,Steffen。