Странная проблема HTTP / ошибка с Лиспом - PullRequest
5 голосов
/ 15 января 2009

Я пытаюсь узнать немного больше об обработке сокетов и сетевых подключений в 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)))

Ответы [ 2 ]

4 голосов
/ 15 января 2009

Несколько вещей. Во-первых, в связи с 400 ошибками, которые вы получаете, на ум приходит несколько вариантов:

  • «Host:» на самом деле не является допустимым полем заголовка в HTTP / 1.0, и в зависимости от того, насколько фашистский веб-сервер, к которому вы обращаетесь, относится к стандартам, он отклонит это как неверный запрос на основе протокола, который вы утверждаете говорить.
  • Вам необходим CRLF между строкой запроса и каждой строкой заголовка.
  • Вполне возможно, что ваша (request) функция возвращает что-то для поля Request-URI - вы подставляете в значении request содержимое этой части строки запроса - то есть фальшивой в одном смысле или другое (плохо экранированные персонажи и т. д.). Просмотр того, что он выводит, может помочь некоторым.

Еще один более общий указатель, который поможет вам на вашем пути:

  • (read-buf-nonblock) очень запутанно. Где определяется символ «с»? Почему 'eof' (gensym) редактируется, а затем ему не присваивается никакого значения? Это очень похоже на побитовую копию, взятую прямо из императивной программы и добавленную в Лисп. Похоже, что то, что вы переопределили здесь (последовательность чтения). Посмотрите здесь в Common Lisp Hyperspec и посмотрите, действительно ли это то, что вам нужно. Другая половина этого - чтобы ваш сокет был неблокирующим. Это довольно просто, хотя документация по SBCL почти не обсуждает эту тему. Используйте это:

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

  • Последняя (let) форма (http-connect) не обязательна. Просто оцени

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

без let, и http-connect все равно должен возвращать правильное значение.

  • In (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. Это очень похоже на парадигму функционального программирования. Однако вам следует либо изменить имя функции (request), либо переменную request. И то, и другое вызывает путаницу.

Yikes, руки болят. Но, надеюсь, это поможет. Готово печатать. : -)

3 голосов
/ 15 января 2009

Вот возможность:

HTTP / 1.0 определяет CR LF последовательности как маркер конца строки.

Директива формата ~% генерирует #\Newline (LF на большинстве платформ, хотя см. CLHS ).

Некоторые сайты могут быть терпимы к отсутствующему CR, другие не так сильно.

...