如何在此 HTTP 请求接口上正确使用 Common Lisp 的多值绑定和处理程序案例?
How to properly use Common Lisp's multiple-value-bind and handler-case on this HTTP-request interface?
我正在使用 SBCL、Emacs 和 Slime。此外,我正在使用库 Dexador.
Dexador 文档提供了有关如何处理失败的 HTTP 请求的示例。
根据官方文档,它说:
;; Handles 400 bad request
(handler-case (dex:get "http://lisp.org")
(dex:http-request-bad-request ()
;; Runs when 400 bad request returned
)
(dex:http-request-failed (e)
;; For other 4xx or 5xx
(format *error-output* "The server returned ~D" (dex:response-status e))))
因此,我尝试了以下方法。必须强调的是,这是一个主要系统的一部分,所以我将其简化为:
;;Good source to test errors: https://httpstat.us/
(defun my-get (final-url)
(let* ((status-code)
(response)
(response-and-status (multiple-value-bind (response status-code)
(handler-case (dex:get final-url)
(dex:http-request-bad-request ()
(progn
(setf status-code
"The server returned a failed request of 400 (bad request) status.")
(setf response nil)))
(dex:http-request-failed (e)
(progn
(setf status-code
(format nil
"The server returned a failed request of ~a status."
(dex:response-status e)))
(setf response nil))))
(list response status-code))))
(list response-and-status response status-code)))
我的代码输出接近我想要的。但是我不明白是输出的
当 HTTP 请求成功时,这是输出:
CL-USER> (my-get "http://www.paulgraham.com")
(("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html><script type=\"text/javascript\">
<!--
... big HTML omitted...
</script>
</html>"
200)
NIL NIL)
我期待(或希望)输出类似于:'(("big html" 200) "big html" 200)
。
但是,当HTTP请求失败时,事情就更奇怪了。例如:
CL-USER> (my-get "https://httpstat.us/400")
((NIL NIL) NIL
"The server returned a failed request of 400 (bad request) status.")
我期待:'((NIL "The server returned a failed request of 400 (bad request) status.") NIL "The server returned a failed request of 400 (bad request) status.")
或者:
CL-USER> (my-get "https://httpstat.us/425")
((NIL NIL) NIL "The server returned a failed request of 425 status.")
同样,我期待:((NIL "The server returned a failed request of 425 status.") NIL "The server returned a failed request of 425 status.")
恐怕发生了 变量遮蔽 问题 - 不过不确定。
我如何创建一个函数,以便我可以 安全地 将响应和状态代码存储在变量中,而不管请求是失败还是成功?
如果请求成功,我有("html" 200)
。如果失败,它将是:(nil 400)
或其他数字 (nil 425)
- 取决于错误消息。
您的问题是您未能理解 multiple-value-bind
、handler-case
和 let*
的工作原理。 (可能还有 setf
和 progn
。)
TLDR
快速修复:
(defun my-get (final-url)
(let* ((status-code)
(response)
(response-and-status
(multiple-value-bind (bresponse bstatus-code)
(handler-case (dex:get final-url)
(dex:http-request-bad-request ()
(values nil
"The server returned a failed request of 400 (bad request) status."))
(dex:http-request-failed (e)
(values nil
(format nil "The server returned a failed request of ~a status." (dex:response-status e)))))
(list (setf response bresponse)
(setf status-code bstatus-code)))))
(list response-and-status response status-code)))
输出:
CL-USER> (my-get "http://www.paulgraham.com")
(("big html" 200) "big html" 200)
CL-USER> (my-get "https://httpstat.us/400")
((NIL "The server returned a failed request of 400 (bad request) status.") NIL "The server returned a failed request of 400 (bad request) status.")
CL-USER> (my-get "https://httpstat.us/425")
((NIL "The server returned a failed request of 425 status.") NIL "The server returned a failed request of 425 status.")
那么,为什么要得到当前结果?
初步
对于 multiple-value-bind
,它将从 values
表单返回的多个值绑定到相应的变量中。
CL-USER> (multiple-value-bind (a b)
nil
(list a b))
(NIL NIL)
CL-USER> (multiple-value-bind (a b)
(values 1 2)
(list a b))
(1 2)
CL-USER> (multiple-value-bind (a b)
(values 1 2 3 4 5)
(list a b))
(1 2)
对于handler-case
,它是returns没有错误时的表达式形式的值。当出现错误时,会执行相应的error-handling代码。
CL-USER> (handler-case (values 1 2 3)
(type-error () 'blah1)
(error () 'blah2))
1
2
3
CL-USER> (handler-case (signal 'type-error)
(type-error () 'blah1)
(error () 'blah2))
BLAH1
CL-USER> (handler-case (signal 'error)
(type-error () 'blah1)
(error () 'blah2))
BLAH2
对于let*
形式,如果没有提供init-forms
,变量将被初始化为nil
。 let
形式也是如此。这些没有 init-forms 的变量周围的括号是不需要的。示例:
CL-USER> (let* (a b (c 3))
(list a b c))
(NIL NIL 3)
结合这些知识
当dex:get
成功(即没有错误)时,它returnsdex:get
的值,即(values body status response-headers uri stream)
。使用 multiple-value-bind
,您的 response-and-status
绑定到 (list response status-code)
的值,即 ("big html" 200)
.
由于dex:http-request-bad-request
和dex:http-request-failed
中的代码只有在dex:get
失败时才会执行,所以response
和status-code
都有初始值nil
。这就是为什么您在成功时获得 (("big html" 200) nil nil)
。
当dex:get
失败时,response
和status-code
都setf
为新值。由于 (setf response nil)
改变了 response
和 returns nil
的值(新值集),并且由于 progn
returns 最后一个表单的值,你的 progn
returns nil
两种错误处理情况。这就是为什么你的 response-and-status
在失败时绑定到 (nil nil)
。
这是 的附录,它很好地描述了问题以及您对 multiple-value-bind
和其他事情的困惑。
如该答案中所述,dex:get
return 有五个值:body、status、response-headers、uri、stream。但它也表示失败的各种条件之一。因此,一件显而易见的事情就是简单地拥有一个 return 具有相同五个值的函数(没有理由将其中一些值打包到列表中),但处理错误,依赖于 [=22= 的处理程序] 合适的值。这样的函数简单的要命,而且没有赋值。
(defun my-get (final-url)
(handler-case (dex:get final-url)
(dex:http-request-failed (e)
;; These are probably not the right set of values, but if the
;; first one is NIL we're basically OK.
(values nil
(dex:response-status e)
e
(format nil
"The server returned a failed request of ~a status."
(dex:response-status e))
nil))))
如果您真的想将值打包成某种结构,您可以这样做,但通常处理多个值很容易。例如,此函数的用户现在可以忽略除第一个值以外的所有值,而不是解压各种 grot,因此:
(defun my-get-user (url)
(or (my-get url)
(error "oops")))
我正在使用 SBCL、Emacs 和 Slime。此外,我正在使用库 Dexador.
Dexador 文档提供了有关如何处理失败的 HTTP 请求的示例。
根据官方文档,它说:
;; Handles 400 bad request
(handler-case (dex:get "http://lisp.org")
(dex:http-request-bad-request ()
;; Runs when 400 bad request returned
)
(dex:http-request-failed (e)
;; For other 4xx or 5xx
(format *error-output* "The server returned ~D" (dex:response-status e))))
因此,我尝试了以下方法。必须强调的是,这是一个主要系统的一部分,所以我将其简化为:
;;Good source to test errors: https://httpstat.us/
(defun my-get (final-url)
(let* ((status-code)
(response)
(response-and-status (multiple-value-bind (response status-code)
(handler-case (dex:get final-url)
(dex:http-request-bad-request ()
(progn
(setf status-code
"The server returned a failed request of 400 (bad request) status.")
(setf response nil)))
(dex:http-request-failed (e)
(progn
(setf status-code
(format nil
"The server returned a failed request of ~a status."
(dex:response-status e)))
(setf response nil))))
(list response status-code))))
(list response-and-status response status-code)))
我的代码输出接近我想要的。但是我不明白是输出的
当 HTTP 请求成功时,这是输出:
CL-USER> (my-get "http://www.paulgraham.com")
(("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html><script type=\"text/javascript\">
<!--
... big HTML omitted...
</script>
</html>"
200)
NIL NIL)
我期待(或希望)输出类似于:'(("big html" 200) "big html" 200)
。
但是,当HTTP请求失败时,事情就更奇怪了。例如:
CL-USER> (my-get "https://httpstat.us/400")
((NIL NIL) NIL
"The server returned a failed request of 400 (bad request) status.")
我期待:'((NIL "The server returned a failed request of 400 (bad request) status.") NIL "The server returned a failed request of 400 (bad request) status.")
或者:
CL-USER> (my-get "https://httpstat.us/425")
((NIL NIL) NIL "The server returned a failed request of 425 status.")
同样,我期待:((NIL "The server returned a failed request of 425 status.") NIL "The server returned a failed request of 425 status.")
恐怕发生了 变量遮蔽 问题 - 不过不确定。
我如何创建一个函数,以便我可以 安全地 将响应和状态代码存储在变量中,而不管请求是失败还是成功?
如果请求成功,我有("html" 200)
。如果失败,它将是:(nil 400)
或其他数字 (nil 425)
- 取决于错误消息。
您的问题是您未能理解 multiple-value-bind
、handler-case
和 let*
的工作原理。 (可能还有 setf
和 progn
。)
TLDR
快速修复:
(defun my-get (final-url)
(let* ((status-code)
(response)
(response-and-status
(multiple-value-bind (bresponse bstatus-code)
(handler-case (dex:get final-url)
(dex:http-request-bad-request ()
(values nil
"The server returned a failed request of 400 (bad request) status."))
(dex:http-request-failed (e)
(values nil
(format nil "The server returned a failed request of ~a status." (dex:response-status e)))))
(list (setf response bresponse)
(setf status-code bstatus-code)))))
(list response-and-status response status-code)))
输出:
CL-USER> (my-get "http://www.paulgraham.com")
(("big html" 200) "big html" 200)
CL-USER> (my-get "https://httpstat.us/400")
((NIL "The server returned a failed request of 400 (bad request) status.") NIL "The server returned a failed request of 400 (bad request) status.")
CL-USER> (my-get "https://httpstat.us/425")
((NIL "The server returned a failed request of 425 status.") NIL "The server returned a failed request of 425 status.")
那么,为什么要得到当前结果?
初步
对于 multiple-value-bind
,它将从 values
表单返回的多个值绑定到相应的变量中。
CL-USER> (multiple-value-bind (a b)
nil
(list a b))
(NIL NIL)
CL-USER> (multiple-value-bind (a b)
(values 1 2)
(list a b))
(1 2)
CL-USER> (multiple-value-bind (a b)
(values 1 2 3 4 5)
(list a b))
(1 2)
对于handler-case
,它是returns没有错误时的表达式形式的值。当出现错误时,会执行相应的error-handling代码。
CL-USER> (handler-case (values 1 2 3)
(type-error () 'blah1)
(error () 'blah2))
1
2
3
CL-USER> (handler-case (signal 'type-error)
(type-error () 'blah1)
(error () 'blah2))
BLAH1
CL-USER> (handler-case (signal 'error)
(type-error () 'blah1)
(error () 'blah2))
BLAH2
对于let*
形式,如果没有提供init-forms
,变量将被初始化为nil
。 let
形式也是如此。这些没有 init-forms 的变量周围的括号是不需要的。示例:
CL-USER> (let* (a b (c 3))
(list a b c))
(NIL NIL 3)
结合这些知识
当dex:get
成功(即没有错误)时,它returnsdex:get
的值,即(values body status response-headers uri stream)
。使用 multiple-value-bind
,您的 response-and-status
绑定到 (list response status-code)
的值,即 ("big html" 200)
.
由于dex:http-request-bad-request
和dex:http-request-failed
中的代码只有在dex:get
失败时才会执行,所以response
和status-code
都有初始值nil
。这就是为什么您在成功时获得 (("big html" 200) nil nil)
。
当dex:get
失败时,response
和status-code
都setf
为新值。由于 (setf response nil)
改变了 response
和 returns nil
的值(新值集),并且由于 progn
returns 最后一个表单的值,你的 progn
returns nil
两种错误处理情况。这就是为什么你的 response-and-status
在失败时绑定到 (nil nil)
。
这是 multiple-value-bind
和其他事情的困惑。
如该答案中所述,dex:get
return 有五个值:body、status、response-headers、uri、stream。但它也表示失败的各种条件之一。因此,一件显而易见的事情就是简单地拥有一个 return 具有相同五个值的函数(没有理由将其中一些值打包到列表中),但处理错误,依赖于 [=22= 的处理程序] 合适的值。这样的函数简单的要命,而且没有赋值。
(defun my-get (final-url)
(handler-case (dex:get final-url)
(dex:http-request-failed (e)
;; These are probably not the right set of values, but if the
;; first one is NIL we're basically OK.
(values nil
(dex:response-status e)
e
(format nil
"The server returned a failed request of ~a status."
(dex:response-status e))
nil))))
如果您真的想将值打包成某种结构,您可以这样做,但通常处理多个值很容易。例如,此函数的用户现在可以忽略除第一个值以外的所有值,而不是解压各种 grot,因此:
(defun my-get-user (url)
(or (my-get url)
(error "oops")))