I am using SBCL, Emacs, and Slime. In addition, I am using the library Dexador.
Dexador documentation provides an example on how to handle failed HTTP requests.
From the official documentation, it says:
;; 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))))
Thus, I tried the following. It must be highlighted that this is part of a major system, so I am simplifying it as:
;;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)))
My code output is close to what I want. But I do not understand it is output.
When the HTTP request is successful, this is the output:
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)
I was expecting (or wished) the output to be something like: '(("big html" 200) "big html" 200)
.
But, things happen to be even weirder when the HTTP request fails. For instance:
CL-USER> (my-get "https://httpstat.us/400")
((NIL NIL) NIL
"The server returned a failed request of 400 (bad request) status.")
I was expecting: '((NIL "The server returned a failed request of 400 (bad request) status.") NIL "The server returned a failed request of 400 (bad request) status.")
Or:
CL-USER> (my-get "https://httpstat.us/425")
((NIL NIL) NIL "The server returned a failed request of 425 status.")
Again, I was expecting: ((NIL "The server returned a failed request of 425 status.") NIL "The server returned a failed request of 425 status.")
I am afraid there is a variable overshadowing problem happening - not sure, though.
How can I create a function so that I can safely store in variables the response and the status-code independent of being a failed or successful request?
If the request is successful, I have ("html" 200)
. If it fails, it would be: (nil 400)
or other number (nil 425)
- depending on the error message.
Your problem is that you failed to understand how
multiple-value-bind
,handler-case
, andlet*
work. (Probably alsosetf
andprogn
.)TLDR
Quick fix:
Output:
So, Why Do You Get Your Current Result?
Preliminary
For
multiple-value-bind
, it binds multiple values returning from avalues
form into the corresponding variables.For
handler-case
, it returns the value of the expression form when there is no error. When there is an error, it will execute the corresponding error-handling code.For
let*
form, variables are initialised tonil
ifinit-forms
are not provided. Same goes to thelet
form. The parentheses around these variables without init-forms are unneeded. Example:Combining These Knowledges
When
dex:get
success (i.e. no error), it returns the values ofdex:get
, which is(values body status response-headers uri stream)
. Withmultiple-value-bind
, yourresponse-and-status
is bound to the value of(list response status-code)
, which is("big html" 200)
.Since the code in both
dex:http-request-bad-request
anddex:http-request-failed
will only be executed whendex:get
failed, bothresponse
andstatus-code
have the initial valuenil
. That's why you get(("big html" 200) nil nil)
on success.When
dex:get
failed, bothresponse
andstatus-code
aresetf
to new values. Since(setf response nil)
mutates the value ofresponse
and returnsnil
(the new value set), and sinceprogn
returns the value of last form, yourprogn
returnsnil
for both error handling cases. That's why yourresponse-and-status
is bound to(nil nil)
when failed.