如何在 Common Lisp 中将 dex:get 的输出识别为 JSON 或 HTML?
How to identify the output of dex:get as being JSON or HTML in Common Lisp?
我正在使用 SBCL、Emacs 和 Slime。此外,我正在使用图书馆 Dexador。
因此,我可以执行 HTTP 请求。其中一些会 return JSON:
CL-USER> (dex:get "http://ip.jsontest.com/")
"{\"ip\": \"179.878.248.207\"}
别人会returnHTML:
(dex:get "https://ambrevar.xyz/")
"<!DOCTYPE html>
<html lang=\"en\">
<head>
<!-- 2021-12-29 -->
<meta charset=\"utf-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<title>‎</title>
<meta name=\"generator\" content=\"Org mode\">
<meta name=\"author\" content=\"root\">
<link rel=\"stylesheet\" type=\"text/css\" href=\"../dark.css\">
<link rel=\"icon\" type=\"image/x-icon\" href=\"../logo.png\">
</head>
<body>
<div id=\"content\">
<div id=\"outline-container-org4051da4\" class=\"outline-2\">
<h2 id=\"org4051da4\">Contact</h2>
<div class=\"outline-text-2\" id=\"text-org4051da4\">
<ul class=\"org-ul\">
<li>Email: <a href=\"mailto:mail@ambrevar.xyz\">mail@ambrevar.xyz</a></li>
<li>PGP: <a href=\"ambrevar.asc\">0x9BDCF497A4BBCC7F</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
"
将结果存储在变量中后:
CL-USER> (defparameter html-response (dex:get "https://ambrevar.xyz/"))
HTML-RESPONSE
CL-USER> (defparameter json-response (dex:get "http://ip.jsontest.com/"))
JSON-RESPONSE
我想创建一个函数来检查输出是 JSON 还是 HTML。因此,我做了这个功能:
(defun html-or-json (response)
"Check it the server response is HTML or JSON data."
(cond ((null response) nil)
((equal (subseq response 0 1) "<") "html")
(t "json")))
有效:
CL-USER> (html-or-json json-response)
"json"
CL-USER> (html-or-json html-response)
"html"
但是,我的解决方案对我来说似乎很难看。最初,我尝试使用 handle-case
并将答案序列化为 JSON。如果有效,它将是 JSON。如果失败,该函数会将对象视为 HTML。但是事情并不顺利,因为我不精通 handle-case
语法。
你能想出一个替代方案来实现这个目标吗?也许使用 handle-case
?
@ignis volens 是对的,你需要使用 Content-Type
header。更新代码还有一些工作要做。
- 使用
defparameter
,部分Dexador
响应被丢弃
如果在REPL中调用函数dex:get
,它将return 4个值:
* (dex:get "http://ip.jsontest.com/")
"{\"ip\": \"xx.xx.xx.xxx\"} ;; value 1: IP address
"
200 ;; value 2: HTTP code
#<HASH-TABLE :TEST EQUAL :COUNT 6 {100A0EA203}> ;; value 3: HTTP headers
#<QURI.URI.HTTP:URI-HTTP http://ip.jsontest.com/> ;; value 4: URI
当使用defparameter
时,只有第一个值会存储在变量中。
* (defparameter response (dex:get "http://ip.jsontest.com/"))
RESPONSE
* response
"{\"ip\": \"xx.xx.xx.xx\"} ;; value 1: IP address
"
*
保存所有值的一种方法是使用函数 MULTIPLE-VALUE-LIST
* (defparameter response (multiple-value-list (dex:get "http://ip.jsontest.com/")))
RESPONSE
* response
("{\"ip\": \"xx.xx.xx.xx\"} ;; value 1: IP address
"
200 ;; value 2: HTTP code
#<HASH-TABLE :TEST EQUAL :COUNT 6 {100A146813}> ;; value 3: HTTP headers
#<QURI.URI.HTTP:URI-HTTP http://ip.jsontest.com/>) ;; value 4: URI
- 从响应中读取
Content-Type
header
可以根据 DESTRUCTURING-BIND 宏编写函数从 HTTP headers 获取字段。
(defun get-dex-header (dex-response header)
(destructuring-bind (raw-response http-code http-headers quri)
dex-response
(gethash header http-headers)))
函数的使用是straight-forward:
* (defparameter json-response (multiple-value-list (dex:get "http://ip.jsontest.com/")))
JSON-RESPONSE
* (get-dex-header json-response "content-type")
"application/json"
* (defparameter html-response (multiple-value-list (dex:get "http://bing.com/")))
HTML-RESPONSE
(get-dex-header html-response "content-type")
"text/html; charset=utf-8"
我的回答也与@Robert 的回答一致。
但我会使用 multiple-value-bind
。
函数 content-type-get
怎么样,它类似于 get 但在第一个位置 returns 是 header 的 content-type
.
的值
(defun content-type-get (url)
(multiple-value-bind (text http-code http-headers quri x) (dex:get url)
(values (gethash "content-type" http-headers)
text
http-code
http-header
quri
x)))
;; this in-addition-content-type-returning get function you can use in return with
;; `multiple-value-bind`. The advantage is that you request the response
;; only once - which ensures speed.
(multiple-value-bind
(content-type text http-code http-headers quri x)
(content-type-get "http://ip.jsontest.com/")
(cond ((string= "application/json" content-type) 'do-sth-with-json-text)
((string= "text/html; charset=utf-8" content-type) 'do-sth-with-html-text)
(t 'or-something-else)))
例如
(defun dex-dispatch (url)
(multiple-value-bind (content http-result headers uri) (dex:get url)
;; cope with errors perhaps here
(handle-content-type (intern (string-upcase (gethash headers "content-type"))
(find-package "KEYWORD"))
content http-result headers uri)))
(defgeneric handle-content-type (content-type content http-result headers uri))
(defmethod handle-content-type :around (content-type content http-result headers uri)
(declare (ignorable content-type content headers uri))
(if (= http-result 200)
(call-next-method)
...))
(defmethod handle-content-type ((content-type eql ':application/json)
content http-result headers uri)
...)
...
我正在使用 SBCL、Emacs 和 Slime。此外,我正在使用图书馆 Dexador。 因此,我可以执行 HTTP 请求。其中一些会 return JSON:
CL-USER> (dex:get "http://ip.jsontest.com/")
"{\"ip\": \"179.878.248.207\"}
别人会returnHTML:
(dex:get "https://ambrevar.xyz/")
"<!DOCTYPE html>
<html lang=\"en\">
<head>
<!-- 2021-12-29 -->
<meta charset=\"utf-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<title>‎</title>
<meta name=\"generator\" content=\"Org mode\">
<meta name=\"author\" content=\"root\">
<link rel=\"stylesheet\" type=\"text/css\" href=\"../dark.css\">
<link rel=\"icon\" type=\"image/x-icon\" href=\"../logo.png\">
</head>
<body>
<div id=\"content\">
<div id=\"outline-container-org4051da4\" class=\"outline-2\">
<h2 id=\"org4051da4\">Contact</h2>
<div class=\"outline-text-2\" id=\"text-org4051da4\">
<ul class=\"org-ul\">
<li>Email: <a href=\"mailto:mail@ambrevar.xyz\">mail@ambrevar.xyz</a></li>
<li>PGP: <a href=\"ambrevar.asc\">0x9BDCF497A4BBCC7F</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
"
将结果存储在变量中后:
CL-USER> (defparameter html-response (dex:get "https://ambrevar.xyz/"))
HTML-RESPONSE
CL-USER> (defparameter json-response (dex:get "http://ip.jsontest.com/"))
JSON-RESPONSE
我想创建一个函数来检查输出是 JSON 还是 HTML。因此,我做了这个功能:
(defun html-or-json (response)
"Check it the server response is HTML or JSON data."
(cond ((null response) nil)
((equal (subseq response 0 1) "<") "html")
(t "json")))
有效:
CL-USER> (html-or-json json-response)
"json"
CL-USER> (html-or-json html-response)
"html"
但是,我的解决方案对我来说似乎很难看。最初,我尝试使用 handle-case
并将答案序列化为 JSON。如果有效,它将是 JSON。如果失败,该函数会将对象视为 HTML。但是事情并不顺利,因为我不精通 handle-case
语法。
你能想出一个替代方案来实现这个目标吗?也许使用 handle-case
?
@ignis volens 是对的,你需要使用 Content-Type
header。更新代码还有一些工作要做。
- 使用
defparameter
,部分Dexador
响应被丢弃
如果在REPL中调用函数dex:get
,它将return 4个值:
* (dex:get "http://ip.jsontest.com/")
"{\"ip\": \"xx.xx.xx.xxx\"} ;; value 1: IP address
"
200 ;; value 2: HTTP code
#<HASH-TABLE :TEST EQUAL :COUNT 6 {100A0EA203}> ;; value 3: HTTP headers
#<QURI.URI.HTTP:URI-HTTP http://ip.jsontest.com/> ;; value 4: URI
当使用defparameter
时,只有第一个值会存储在变量中。
* (defparameter response (dex:get "http://ip.jsontest.com/"))
RESPONSE
* response
"{\"ip\": \"xx.xx.xx.xx\"} ;; value 1: IP address
"
*
保存所有值的一种方法是使用函数 MULTIPLE-VALUE-LIST
* (defparameter response (multiple-value-list (dex:get "http://ip.jsontest.com/")))
RESPONSE
* response
("{\"ip\": \"xx.xx.xx.xx\"} ;; value 1: IP address
"
200 ;; value 2: HTTP code
#<HASH-TABLE :TEST EQUAL :COUNT 6 {100A146813}> ;; value 3: HTTP headers
#<QURI.URI.HTTP:URI-HTTP http://ip.jsontest.com/>) ;; value 4: URI
- 从响应中读取
Content-Type
header
可以根据 DESTRUCTURING-BIND 宏编写函数从 HTTP headers 获取字段。
(defun get-dex-header (dex-response header)
(destructuring-bind (raw-response http-code http-headers quri)
dex-response
(gethash header http-headers)))
函数的使用是straight-forward:
* (defparameter json-response (multiple-value-list (dex:get "http://ip.jsontest.com/")))
JSON-RESPONSE
* (get-dex-header json-response "content-type")
"application/json"
* (defparameter html-response (multiple-value-list (dex:get "http://bing.com/")))
HTML-RESPONSE
(get-dex-header html-response "content-type")
"text/html; charset=utf-8"
我的回答也与@Robert 的回答一致。
但我会使用 multiple-value-bind
。
函数 content-type-get
怎么样,它类似于 get 但在第一个位置 returns 是 header 的 content-type
.
(defun content-type-get (url)
(multiple-value-bind (text http-code http-headers quri x) (dex:get url)
(values (gethash "content-type" http-headers)
text
http-code
http-header
quri
x)))
;; this in-addition-content-type-returning get function you can use in return with
;; `multiple-value-bind`. The advantage is that you request the response
;; only once - which ensures speed.
(multiple-value-bind
(content-type text http-code http-headers quri x)
(content-type-get "http://ip.jsontest.com/")
(cond ((string= "application/json" content-type) 'do-sth-with-json-text)
((string= "text/html; charset=utf-8" content-type) 'do-sth-with-html-text)
(t 'or-something-else)))
例如
(defun dex-dispatch (url)
(multiple-value-bind (content http-result headers uri) (dex:get url)
;; cope with errors perhaps here
(handle-content-type (intern (string-upcase (gethash headers "content-type"))
(find-package "KEYWORD"))
content http-result headers uri)))
(defgeneric handle-content-type (content-type content http-result headers uri))
(defmethod handle-content-type :around (content-type content http-result headers uri)
(declare (ignorable content-type content headers uri))
(if (= http-result 200)
(call-next-method)
...))
(defmethod handle-content-type ((content-type eql ':application/json)
content http-result headers uri)
...)
...