我正在使用 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)- 取決于錯誤訊息。
uj5u.com熱心網友回復:
您的問題是您不了解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,它在沒有錯誤時回傳運算式形式的值。當出現錯誤時,會執行相應的錯誤處理代碼。
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*變數,則將其初始化。表格也是如此。這些變數周圍沒有初始化形式的括號是不需要的。例子:nilinit-formslet
CL-USER> (let* (a b (c 3))
(list a b c))
(NIL NIL 3)
結合這些知識
當dex:get成功(即沒有錯誤)時,它回傳 的值dex: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 并回傳nil(新值集)的值,并且由于progn回傳最后一個表單的值,因此您progn將回傳nil兩種錯誤處理情況。這就是為什么你在失敗時response-and-status必然會(nil nil)失敗的原因。
uj5u.com熱心網友回復:
這是zacque's answer的附錄,它很好地描述了問題以及您對multiple-value-bind& 其他事情的困惑。
如該答案中所述,dex:get回傳五個值:body、status、response-headers、uri、stream。但它也標志著失敗的各種條件之一。因此,一件顯而易見的事情就是簡單地創建一個回傳相同五個值的函式(沒有理由將其中一些值打包到一個串列中),但處理錯誤,依靠處理程式回傳合適的值。這樣的函式非常簡單,沒有賦值。
(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")))
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/460118.html
上一篇:無法對可觀察結果呼叫函式
