每过一段时间总会燃起一种用Common Lisp(下文简称CL)来写Web应用的冲动,继而就会开始感慨在CL的生态圈中居然没有一款好用的Web框架。尽管放狗搜索“common lisp web framework”可以找到一些——例如Caveman2,以及在Cliki中记录的一些其它框架。然后使用过其中一部分的人就会知道,大部分用起来的体验都不咋地。
在业界摸爬打滚了一小段时光(从业几年姑且可以这么说吧)后,感觉制作一款专门用于编写JSON-in-JSON-out的Web应用的Web框架应该是一个不错的点子——反正大家都是发出application/json的请求期望application/json的响应,于是乎就撸起袖子自己干了。不过完全从零开始编写起是不现实的,于是乎选择了一个“平台”来作为基础。这个平台就是Clack 啦
Clack会负责屏蔽下层的Web Server的差异,它只需要我提供一个函数给它作为来访的HTTP请求的“handler”即可,然后在这个handler中我就可以为所欲为啦。Clack在收到HTTP请求后,会把HTTP请求中的一些信息组织为一个列表类型的值传递给这个handler。在这个handler中,我只需要综合运用CAR、CDR之类的奇怪名字的函数就可以拿到自己需要的东西了——当然了,鉴于这个列表是个plist,用CL提供的DESTRUCTURING-BIND就可以很方便地提取啦。
在这个plist中,就有一个叫做:RAW-BODY的p,它的值是一个“流”——是的,就是那种文件流的流!但它又不是一个路边随处可见的妖艳贱货的流,而是一个来自FLEXI-STREAMS这个包(指CL中的package)的流。FLEXI-STREAMS 是一个提供流操作的库,鉴于我没有看过Gray streams 相关的内容,就不在这里瞎逼逼误导读者了。总而言之,我必须找到一个办法可以从一个FLEXI-STREAMS提供的输入流类型的值中读出一些东西来。
其实这个办法很简单,就是用CL原生提供的读取流的函数即可——比如READ-SEQUENCE这样的函数。不过我得验证一下不是,为此,我需要有办法可以构造出一个FLEXI-STREAMS流类型的值出来。FLEXI-STREAMS提供了一个叫做MAKE-FLEXI-STREAM的函数,显然这个就是我所需要调用的最后一个函数了。从它的描述来看,它需要一个CL中的原生流来作为第一个参数才行。为此,我试了一下下面的代码
1 2 3 4 5 (let ((text "Hello, world!" )) (with-input-from-string (s text) (let ((buffer (make-array (length text))) (fs (flexi-streams :make-flexi-stream s))) (read-sequence buffer fs))))
遗憾的是,运行上面的代码会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 The value #\H is not of type (UNSIGNED-BYTE 8) when setting an element of (ARRAY (UNSIGNED-BYTE 8)) [Condition of type TYPE-ERROR] Restarts: 0: [RETRY] Retry SLIME interactive evaluation request. 1: [*ABORT] Return to SLIME's top level. 2: [ABORT] abort thread (#<THREAD "worker" RUNNING {1007766D53}>) Backtrace: 0: ((SB-VM::OPTIMIZED-DATA-VECTOR-SET (UNSIGNED-BYTE 8)) #<unavailable argument> #<unavailable argument> #<unavailable argument>) 1: (SB-IMPL:ANSI-STREAM-READ-SEQUENCE #(0 0 0 0 0 0 ...) #<SB-IMPL::STRING-INPUT-STREAM {1007975973}> 0 13) 2: (READ-SEQUENCE #(0 0 0 0 0 0 ...) #<SB-IMPL::STRING-INPUT-STREAM {1007975973}> :START 0 :END 13) 3: ((FLET FLEXI-STREAMS::FILL-BUFFER :IN FLEXI-STREAMS::READ-SEQUENCE*) 13) 4: ((:METHOD FLEXI-STREAMS::READ-SEQUENCE* (FLEXI-STREAMS::FLEXI-LATIN-1-FORMAT T T T T)) #<FLEXI-STREAMS::FLEXI-LATIN-1-FORMAT (:ISO-8859-1 :EOL-STYLE :LF) {1007975B43}> #<FLEXI-STREAMS:FLEXI-INPUT-STRE.. 5: ((:METHOD STREAM-READ-SEQUENCE (TRIVIAL-GRAY-STREAMS:FUNDAMENTAL-INPUT-STREAM T)) #<FLEXI-STREAMS:FLEXI-INPUT-STREAM {1007975C73}> #(0 0 0 0 0 0 ...) 0 NIL) [fast-method] 6: (READ-SEQUENCE #(0 0 0 0 0 0 ...) #<FLEXI-STREAMS:FLEXI-INPUT-STREAM {1007975C73}> :START 0 :END NIL) 7: ((LAMBDA ())) 8: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LET ((TEXT "Hello, world!")) (WITH-INPUT-FROM-STRING (S TEXT) (LET # #))) #<NULL-LEXENV>) 9: (EVAL (LET ((TEXT "Hello, world!")) (WITH-INPUT-FROM-STRING (S TEXT) (LET # #)))) 10: ((LAMBDA NIL :IN SWANK:INTERACTIVE-EVAL)) --more--
既然在调用READ-SEQUENCE的时候出状况了,不妨试试下面的代码
1 2 3 4 (let ((text "Hello, world!" )) (with-input-from-string (s text) (let ((fs (flexi-streams :make-flexi-stream s))) (read-char fs))))
再次令人遗憾的,它会抛出另一个状况(CL中的condition啦)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #<SB-IMPL::STRING-INPUT-STREAM {10020A7243}> is not a binary input stream. [Condition of type SIMPLE-TYPE-ERROR] Restarts: 0: [RETRY] Retry SLIME interactive evaluation request. 1: [*ABORT] Return to SLIME's top level. 2: [ABORT] abort thread (#<THREAD "worker" RUNNING {1001F6E813}>) Backtrace: 0: (SB-KERNEL:ILL-BIN #<SB-IMPL::STRING-INPUT-STREAM {10020A7243}>) 1: (READ-BYTE #<SB-IMPL::STRING-INPUT-STREAM {10020A7243}> NIL NIL) 2: ((:METHOD FLEXI-STREAMS::READ-BYTE* (FLEXI-STREAMS:FLEXI-INPUT-STREAM)) #<FLEXI-STREAMS:FLEXI-INPUT-STREAM {10020A74C3}>) [fast-method] 3: ((:METHOD FLEXI-STREAMS::OCTETS-TO-CHAR-CODE (FLEXI-STREAMS::FLEXI-LATIN-1-FORMAT T)) #<FLEXI-STREAMS::FLEXI-LATIN-1-FORMAT (:ISO-8859-1 :EOL-STYLE :LF) {10020A7393}> #<unavailable argument>) [fast-me.. 4: ((:METHOD STREAM-READ-CHAR (FLEXI-STREAMS:FLEXI-INPUT-STREAM)) #<FLEXI-STREAMS:FLEXI-INPUT-STREAM {10020A74C3}>) [fast-method] 5: (READ-CHAR #<FLEXI-STREAMS:FLEXI-INPUT-STREAM {10020A74C3}> T NIL #<unused argument>) 6: ((LAMBDA ())) 7: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LET ((TEXT "Hello, world!")) (WITH-INPUT-FROM-STRING (S TEXT) (LET # #))) #<NULL-LEXENV>) 8: (EVAL (LET ((TEXT "Hello, world!")) (WITH-INPUT-FROM-STRING (S TEXT) (LET # #)))) 9: ((LAMBDA NIL :IN SWANK:INTERACTIVE-EVAL)) --more--
看来从一开始提供给MAKE-FLEXI-STREAM函数的参数就应当是一个“二进制”的流才对。为此,我需要借助FLEXI-STREAMS自身的力量——调用它的STRING-TO-OCTETS函数。使用这个函数,可以将一个字符串转换为某种编码下的字节数组,例如下面的代码
1 (flexi-streams :string-to-octets "Hello" )
得到一串“octet”后,还需要将其转换为“流”才行。再次借助FLEXI-STREAMS的力量,调用它的MAKE-IN-MEMORY-INPUT-STREAM函数,然后将这个函数调用的返回值作为MAKE-FLEXI-STREAM的第一个参数即可,最终的代码如下
1 2 3 4 5 6 7 (let* ((text "Hello, world!" ) (octets (flexi-streams :string-to-octets text)) (memory-input (flexi-streams :make-in-memory-input-stream octets)) (flexi-stream (flexi-streams :make-flexi-stream memory-input)) (buffer (make-array (flexi-streams :octet-length text)))) (read-sequence buffer flexi-stream) (print (coerce buffer 'string)))
全文完