5

我正在尝试学习更多关于在 SBCL 中处理套接字和网络连接的知识;所以我为 HTTP 写了一个简单的包装器。到目前为止,它只是制作一个流并执行一个请求,以最终获取网站的标题数据和页面内容。

到目前为止,它的工作还算不错。没有什么可吹嘘的,但它至少奏效了。

但是,我遇到了一个奇怪的问题;我不断收到“400 Bad Request”错误。

起初,我对如何处理 HTTP 请求有些怀疑(或多或少将请求字符串作为函数参数传递),然后我创建了一个函数,用我需要的所有部分格式化查询字符串并将其返回以供使用后来......但我仍然得到错误。

更奇怪的是,错误并非每次都发生。如果我在像 Google 这样的页面上尝试该脚本,我会得到“200 Ok”的返回值......但在其他网站上的其他时间,我会得到“400 Bad Request”。

我确定这是我的代码的问题,但如果我确切知道是什么原因造成的,我会被诅咒的。

这是我正在使用的代码:

(use-package :sb-bsd-sockets)

(defun read-buf-nonblock (buffer stream)
  (let ((eof (gensym)))
    (do ((i 0 (1+ i))
         (c (read-char stream nil eof)
            (read-char-no-hang stream nil eof)))
        ((or (>= i (length buffer)) (not c) (eq c eof)) i)
      (setf (elt buffer i) c))))

(defun http-connect (host &optional (port 80))
"Create I/O stream to given host on a specified port"
  (let ((socket (make-instance 'inet-socket
                   :type :stream
                   :protocol :tcp)))
    (socket-connect
     socket (car (host-ent-addresses (get-host-by-name host))) port)
    (let ((stream (socket-make-stream socket
                    :input t
                    :output t
                    :buffering :none)))
      stream)))

(defun http-request (stream request &optional (buffer 1024))
"Perform HTTP request on a specified stream"
  (format stream "~a~%~%" request )
  (let ((data (make-string buffer)))
    (setf data (subseq data 0
               (read-buf-nonblock data
                      stream)))
    (princ data)
    (> (length data) 0)))

(defun request (host request)
"formated HTTP request"
  (format nil "~a HTTP/1.0 Host: ~a" request host))

(defun get-page (host &optional (request "GET /"))
"simple demo to get content of a page"
  (let ((stream (http-connect host)))
    (http-request stream (request host request)))
4

2 回答 2

4

一些东西。首先,对于您返回的 400 错误的担忧,我想到了一些可能性:

  • “主机:”实际上不是 HTTP/1.0 中的有效标头字段,并且根据您联系的 Web 服务器对标准的法西斯程度,它会根据您声称正在使用的协议将其视为错误请求而拒绝。
  • 您需要在请求行和每个标题行之间添加一个 CRLF。
  • 您的 (request) 函数可能会为 Request-URI 字段返回某些内容——您将 request 的值替换为 Request-line 的这一部分的内容——这是一种或另一种方式的虚假(严重转义字符等)。查看它输出的内容可能会有所帮助。

其他一些更通用的指针可以帮助您一路走好:

  • (read-buf-nonblock) 非常令人困惑。符号“c”在哪里定义?为什么 'eof' (gensym)ed 然后没有分配任何值?它看起来非常像直接从命令式程序中取出并放入 Lisp 的逐字节副本。看起来您在这里重新实现的是(读取序列)。去这里看看Common Lisp Hyperspec,看看这是否是你需要的。另一半是将您创建的套接字设置为非阻塞。这很容易,尽管 SBCL 文档几乎没有提及该主题。用这个:

    (socket-make-stream socket :input t :output t :buffering :none :timeout 0)

  • (http-connect) 的最后一种 (let) 形式不是必需的。只评价

    (socket-make-stream socket :input t :output t :buffering :none)

如果没有 let,http-connect 仍应返回正确的值。

  • 在(http请求)...

代替:

 (format stream "~a~%~%" request )
 (let ((data (make-string buffer)))
 (setf data (subseq data 0
            (read-buf-nonblock data
                               stream)))
 (princ data)
 (> (length data) 0)))

(format stream "~a~%~%" request )
(let ((data (read-buf-nonblock stream)))
    (princ data)
    (> (length data) 0)))

并 make (read-buf-nonblock) 返回数据字符串,而不是让它在函数中分配。因此,在您buffer被分配的位置,在其中创建一个变量buffer,然后将其返回。您正在做的事情被称为依赖“副作用”,并且往往会产生更多错误并且更难发现错误。仅在必要时使用它,尤其是在一种易于不依赖它们的语言中。

  • 我最喜欢定义 get-page 的方式。在函数式编程范式中感觉非常好。但是,您应该更改(请求)函数的名称或变量请求。两者都在那里令人困惑。

咳咳,手疼。但希望这会有所帮助。完成打字。:-)

于 2009-01-15T05:54:52.177 回答
3

这是一种可能性:

HTTP/1.0 将序列 CR LF 定义为行尾标记。

~%格式指令正在生成一个(在大多数平台上#\Newline都是 LF,尽管参见CLHS)。

一些站点可能容忍丢失的 CR,而其他站点则不能容忍。

于 2009-01-15T05:02:51.190 回答