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 alsosetfandprogn.)TLDR
Quick fix:
Output:
So, Why Do You Get Your Current Result?
Preliminary
For
multiple-value-bind, it binds multiple values returning from avaluesform 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 tonilifinit-formsare not provided. Same goes to theletform. The parentheses around these variables without init-forms are unneeded. Example:Combining These Knowledges
When
dex:getsuccess (i.e. no error), it returns the values ofdex:get, which is(values body status response-headers uri stream). Withmultiple-value-bind, yourresponse-and-statusis bound to the value of(list response status-code), which is("big html" 200).Since the code in both
dex:http-request-bad-requestanddex:http-request-failedwill only be executed whendex:getfailed, bothresponseandstatus-codehave the initial valuenil. That's why you get(("big html" 200) nil nil)on success.When
dex:getfailed, bothresponseandstatus-codearesetfto new values. Since(setf response nil)mutates the value ofresponseand returnsnil(the new value set), and sinceprognreturns the value of last form, yourprognreturnsnilfor both error handling cases. That's why yourresponse-and-statusis bound to(nil nil)when failed.