Giter Club home page Giter Club logo

fast-http's Introduction

fast-http

Build Status

This is a fast HTTP request/response protocol parser for Common Lisp.

Features

  • Parses both HTTP requests and responses.
  • Handles persistent streams (keep-alive).
  • Decodes chunked encoding.
  • Exports low-level APIs which don't any memory allocations during parsing.

API differences from http-parse

The API is quite similar to http-parse, although there's some differences.

  • http, http-request and http-response are structure classes, not standard classes.
  • http doesn't have :force-stream option. (always streaming)
  • http doesn't have :store-body option because it can consume much memory.
  • body-callback for make-parser doesn't take a flag body-complete-p.
    • Use finish-callback to know if the parsing is finished.
  • body-callback for make-parser takes pointers start and end.
  • multipart-callback for make-parser has been deleted.
    • Use make-multipart-parser and body-callback by yourself.
  • :callback of make-multipart-parser takes a stream, not a body octet vector at the 4th argument.
  • Raises errors aggressively while parsing.
    • Handle fast-http-error as you needed.
  • Doesn't use a property list as a representation of HTTP headers. (See issue #1)

APIs

[Structure] http

Base structure class extended by http-request and http-response.

NOTE: Don't use this class directly unless you're intended to use low-level APIs of fast-http.

(make-http)
;=> #S(FAST-HTTP.HTTP:HTTP
;     :METHOD NIL
;     :MAJOR-VERSION 0
;     :MINOR-VERSION 9
;     :STATUS 0
;     :CONTENT-LENGTH NIL
;     :CHUNKED-P NIL
;     :UPGRADE-P NIL
;     :HEADERS NIL
;     :HEADER-READ 0
;     :MARK -1
;     :STATE 0)

Methods

  • http-method: Returns a HTTP request method in a keyword (such like :GET, :POST or :HEAD).
  • http-major-version: Returns a HTTP protocol major version in an integer (such like 1 or 0).
  • http-minor-version: Returns a HTTP protocol minor version in an integer (such like 1 or 0).
  • http-version: Returns a HTTP protocol version in a float (such like 1.0 or 1.1).
  • http-status: Returns a HTTP response status code in an integer (such like 200 or 302).
  • http-content-length: Returns a value of Content-Length header in an integer. If the header doesn't exist, it returns NIL.
  • http-chunked-p: Returns T if the value of Transfer-Encoding header is chunked. If the header doesn't exist, it returns NIL.
  • http-upgrade-p: Returns T if Upgrade header exists.
  • http-headers: Returns a hash-table which represents HTTP headers. Note all hash keys are lower-cased and all values are string except Set-Cookie header, whose value is a list of strings. (Content-Length -> "content-length").

[Structure] http-request (extends http)

Structure class holds values specific to an HTTP request.

(make-http-request)
;=> #S(FAST-HTTP.HTTP:HTTP-REQUEST
;     :METHOD NIL
;     :MAJOR-VERSION 0
;     :MINOR-VERSION 9
;     :STATUS 0
;     :CONTENT-LENGTH NIL
;     :CHUNKED-P NIL
;     :UPGRADE-P NIL
;     :HEADERS NIL
;     :HEADER-READ 0
;     :MARK -1
;     :STATE 0
;     :RESOURCE NIL)

Methods

  • http-resource: Returns an URI string.

[Structure] http-response (extends http)

Structure class holds values specific to an HTTP response.

(make-http-response)
;=> #S(FAST-HTTP.HTTP:HTTP-RESPONSE
;     :METHOD NIL
;     :MAJOR-VERSION 0
;     :MINOR-VERSION 9
;     :STATUS 0
;     :CONTENT-LENGTH NIL
;     :CHUNKED-P NIL
;     :UPGRADE-P NIL
;     :HEADERS NIL
;     :HEADER-READ 0
;     :MARK -1
;     :STATE 0
;     :STATUS-TEXT NIL)

Methods

  • http-status-text: Returns an response status text (such like Continue, OK or Bad Request).

[Function] make-parser (http &key first-line-callback header-callback body-callback finish-callback)

Makes a parser closure and returns it.

(let ((http (make-http-request)))
  (make-parser http
               :body-callback
               (lambda (data start end)
                 (write-to-buffer data start end))
               :finish-callback
               (lambda ()
                 (handle-response http))))
;=> #<CLOSURE (LAMBDA (DATA &KEY (START 0) END)
;              :IN
;              FAST-HTTP.PARSER:MAKE-PARSER) {10090BDD0B}>

The closure takes one required argument data, that is a simple byte vector and two keyword arguments start and end.

Callbacks

  • first-line-callback (): This callback function will be called when the first line is parsed.
  • header-callback (headers-hash-table): This callback function will be called when the header lines are parsed. This function is the same object to the http object holds.
  • body-callback (data-byte-vector): This callback function will be called whenever it gets a chunk of HTTP body. Which means this can be called multiple times.
  • finish-callback (): This callback function will be called when the HTTP message ends.

NOTE: If the HTTP request/response has multiple messages (like HTTP/1.1 pipelining), all these functions can be called multiple times.

[Function] make-multipart-parser (content-type callback)

Makes a multipart/form-data parser closure and returns it.

This takes 2 arguments, content-type (such like "multipart/form-data; boundary=--AsB03x") and callback. The callback is a function which takes exact 4 arguments -- a field name, field headers, field meta data and body bytes.

Low-level APIs

The following functions are intended to be used for internally. These APIs are likely to change in the future.

Most of functions are declared as (optimize (speed 3) (safety 0)) which means it won't check the type of arguments.

[Structure] callbacks

Structure class holds callback functions. The callbacks are similar to make-parser's, but don't correspond to them directly.

Slots

  • message-begin (http): This will be called when a new HTTP message begins.
  • url (http data start end): This will be called when an URL part of the HTTP request parsed.
  • first-line (http): This will be called when the first line of the HTTP request/response parsed.
  • status (http data start end): This will be called when the status text (not code) of the HTTP response parsed.
  • header-field (http data start end): This will be called when a header field parsed.
  • header-value (http data start end): This will be called when a header value parsed. This function can be called multiple times when the header value is folded onto multiple lines.
  • headers-complete (http): This will be called when all headers parsed.
  • body (http data start end): This will be called whenever the parser gets a chunk of HTTP body.
  • message-complete (http): This will be called when the HTTP message ends.

[Function] parse-request (http callbacks data &key (start 0) end)

Parses data as an HTTP request, sets values to http and invokes callbacks in callbacks.

This takes a http object, a callbacks object, and a simple byte vector data and two pointers -- start and end. If end is nil, the length of data will be used.

[Function] parse-response (http callbacks data &key (start 0) end)

Parses data as an HTTP response, sets values to http and invokes callbacks in callbacks.

Takes a http object, a callbacks object, and a simple byte vector data and two pointers -- start and end. If end is nil, the length of data will be used.

[Condition] eof

Will be raised when the data ends in the middle of parsing.

Installation

(ql:quickload :fast-http)

Running tests

(asdf:test-system :fast-http)

Benchmark

  • Parsing an HTTP request header 100000 times.

In this benchmark, fast-http is 1.25 times faster than http-parser, a C equivalent.

http-parser (C) fast-http
0.108s 0.086s

Environment

  • Travis CI
  • SBCL 1.2.6

You can see the latest result at Travis CI.

fast-http (Common Lisp)

(ql:quickload :fast-http-test)
(fast-http-test.benchmark:run-ll-benchmark)
Evaluation took:
  0.086 seconds of real time
  0.085897 seconds of total run time (0.084763 user, 0.001134 system)
  100.00% CPU
  257,140,751 processor cycles
  0 bytes consed

http-parser (C)

#include "http_parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>

static http_parser *parser;

static http_parser_settings settings_null =
  {.on_message_begin = 0
  ,.on_header_field = 0
  ,.on_header_value = 0
  ,.on_url = 0
  ,.on_status = 0
  ,.on_body = 0
  ,.on_headers_complete = 0
  ,.on_message_complete = 0
  };

int
main (void)
{
  const char *buf;
  int i;
  float start, end;
  size_t parsed;

  parser = malloc(sizeof(http_parser));

  buf = "GET /cookies HTTP/1.1\r\nHost: 127.0.0.1:8090\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17\r\nAccept-Encoding: gzip,deflate,sdch\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\nCookie: name=wookie\r\n\r\n";

  start = (float)clock()/CLOCKS_PER_SEC;
  for (i = 0; i < 100000; i++) {
    http_parser_init(parser, HTTP_REQUEST);
    parsed = http_parser_execute(parser, &settings_null, buf, strlen(buf));
  }
  end = (float)clock()/CLOCKS_PER_SEC;

  free(parser);
  parser = NULL;

  printf("Elapsed %f seconds.\n", (end - start));

  return 0;
}
$ make http_parser.o
$ gcc -Wall -Wextra -Werror -Wno-error=unused-but-set-variable -O3 http_parser.o mybench.c -o mybench
$ mybench
Elapsed 0.108815 seconds.

Author

Copyright

Copyright (c) 2014 Eitaro Fukamachi

License

Licensed under the MIT License.

fast-http's People

Contributors

cxxxr avatar deadtrickster avatar eudoxia0 avatar fukamachi avatar orivej avatar puercopop avatar rudolph-miller avatar slyrus avatar svetlyak40wt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fast-http's Issues

Stop body buffering. It's unsafe

Honestly, I don't recommend to use :store-body because it's unsafe for DoS attack. It would be better that :body-callback will be called for every coming TCP data. (this is the behaviour when :http-force-stream is T, but it should be the default)

http-multipart-parse fail at uploading DOS text file

Hi.
I found a problem when I was sending a DOS text file to the ningle/clack/lack server.
when the last line of the DOS file is only a pair of #\Return #\Newline, and it is sent by "multipart/form-data" post, the end of post contents are below:

--<boundary text>
<some text>#\Return #\Newline
<some text>#\Return #\Newline
#\Return #\Newline
--<boundary text>--

When 'http-multipart-parse' reads the last part of the contents, the inner state changes like below:

+looking-for-delimiter+ #\Return
+maybe-delimiter-start+ #\Newline
+maybe-delimiter-first-dash+ #\Return
+looking-for-delimiter+ #\Newline
+looking-for-delimiter+ #\-
+looking-for-delimiter+ #\-

It indicates'http-multipart-parse' losts the start of the boundary text.
So I tried patching 'src/multipart-parser.lisp':

line 231
from:

                (+maybe-delimiter-first-dash+
                (if (= byte +dash+)
                    (go-state +maybe-delimiter-second-dash+)
                    (go-state +looking-for-delimiter+)))

to:

             (+maybe-delimiter-first-dash+
               (if (= byte +dash+)
                   (go-state +maybe-delimiter-second-dash+)
   	    (if (= byte +cr+)
   		(go-state +maybe-delimiter-start+)
   		(go-state +looking-for-delimiter+))))

I think it works well.

thanks.

Some systems failed to build for Quicklisp dist

Building with SBCL 2.0.5 / ASDF 3.3.1 for quicklisp dist creation.

Trying to build commit id 502a377

fast-http-test fails to build with the following error:

Unhandled SB-KERNEL:SIMPLE-PACKAGE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING {1000A18083}>: no symbol named "FINALIZE-BUFFER" in "SMART-BUFFER"

fast-http fails to build with the following error:

Unhandled SB-KERNEL:SIMPLE-PACKAGE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING {1000A18083}>: no symbol named "FINALIZE-BUFFER" in "SMART-BUFFER"

Full log here

Does not build on SBCL 1.4.0 today

I get this:

; file: /home/quicklisp/quicklisp-controller/dist/build-cache/fast-http/c212515526dd33712c281023b2d7b4e9f3e2d254/fast-http-20180110-git/src/parser.lisp
; in: DEFUN-SPEEDY PARSE-HEADER-FIELD-AND-VALUE
;     (FAST-HTTP.PARSER::EXPECT-FIELD-END
;      (FAST-HTTP.PARSER::SKIP-UNTIL-VALUE-START-AND
;       (SETF (FAST-HTTP.HTTP:HTTP-UPGRADE-P FAST-HTTP.HTTP:HTTP) T)
;       (LET ((FAST-HTTP.PARSER::VALUE-START #))
;         (PROC-PARSE:SKIP* (NOT #\Return))
;         (PROC-PARSE:ADVANCE)
;         (PROC-PARSE:SKIP #\Newline)
;         (FAST-HTTP.PARSER::CALLBACK-DATA :HEADER-FIELD FAST-HTTP.HTTP:HTTP
;                                          FAST-HTTP.PARSER:CALLBACKS
;                                          FAST-HTTP.PARSER::DATA
;                                          FAST-HTTP.PARSER::FIELD-START
;                                          FAST-HTTP.PARSER::FIELD-END)
;         (FAST-HTTP.PARSER::CALLBACK-DATA :HEADER-VALUE FAST-HTTP.HTTP:HTTP
;                                          FAST-HTTP.PARSER:CALLBACKS
;                                          FAST-HTTP.PARSER::DATA
;                                          FAST-HTTP.PARSER::VALUE-START (- # 2)))))
; --> IF FAST-HTTP.PARSER::HANDLE-OTHERWISE PROGN DO BLOCK LET SVREF LET THE 
; --> SB-KERNEL:DATA-VECTOR-REF SB-KERNEL:DATA-VECTOR-REF-WITH-OFFSET 
; ==>
;   (THE SIMPLE-VECTOR #:N-VECTOR-88)
; 
; caught WARNING:
;   Derived type of #:N-VECTOR-88 is (VALUES (SIMPLE-ARRAY CHARACTER (128)) &OPTIONAL), conflicting with its asserted type SIMPLE-VECTOR.
;   See also:
;     The SBCL Manual, Node "Handling of Types"

INVALID-HEADER-TOKEN: invalid character in header

Looks like an error in fast-http.

I have also attached the error message from my repl.
lisp-error.txt

And this is how to reproduce the error:

// filename  /tmp/tmp.js

var app = angular.module('upl', ['ngFileUpload']);
  app.controller('myCtrl', function(Upload) {
    var file = new Blob([new Uint8Array(1000000)], {type: 'application/octet-stream'});

    Upload.upload({
      url: '/upload',
      data: {file: file}
    }).then(
      function (resp){
    console.log('Success ' + resp.config.data.file.name + 'uploaded. Response: ' + resp.data);
      },
      function (resp) {
    console.log('Error status: ' + resp.status);
      },
      function (evt) {
    var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
        console.log('progress: ' + progressPercentage + '% ' + evt.config.data.file.name);
      });
});
;; (ql:quickload "clack")
;; (ql:quickload "ningle")
;; (ql:quickload "cl-who")

(defpackage upload
  (:use :cl :cl-who :clack))

(in-package :upload)
(setf (html-mode) :html5)

(defun page (&optional arg &rest args)
  (declare (ignore arg args))
  (with-html-output-to-string (s nil :indent t :prologue t)
    (:html :ng-app "upl"
     (:head
      (:title "upload-test")
      (:meta :charset "UTF-8")
      (:meta :http-equiv "cache-control" :content "no-cache")
      (:script :src "https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js")
      (:script :src "https://cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/12.0.4/ng-file-upload-all.min.js")
      (:script :src "https://cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/12.0.4/FileAPI.js")
      (:script :src "/tmp.js"))
      (:body :ng-controller "myCtrl"
         "Uploading.. Wating for server to crash"))))

(defvar *app* (make-instance 'ningle:<app>))

(setf (ningle:route *app* "/")
      (lambda (&rest args)(declare (ignore args)) (page)))

(setf (ningle:route *app* "/tmp.js")
      #p"/tmp/tmp.js")

(setf (ningle:route *app* "/upload" :method :post)
      #'(lambda (&rest args)
      (declare (ignore args))
      "{\"Status\":\"OK\"}"))

(defvar *handler* nil)

(when *handler*
  (clack:stop *handler*))

(setf *handler*
      (clack:clackup 
       *app*
       :server :woo))

Chunking failure

Hello, I am getting

Expectation failed: NIL
   [Condition of type FAST-HTTP.PARSER::EXPECT-FAILED]

when doing the following:

(let* ((bytes (with-open-file (f "./chunk-test.http" :direction :input :element-type '(unsigned-byte 8))
                (let ((buff (make-array (file-length f) :element-type '(unsigned-byte 8))))
                  (read-sequence buff f)
                  buff)))
       (http (fast-http:make-http-response))
       (parser (fast-http:make-parser http)))
  (funcall parser (subseq bytes 0 1430))
  (funcall parser (subseq bytes 1430)))

chunk-test.http is as follows (also emailed you the binary version just in case it's a line-endings issue):

HTTP/1.1 200 OK

Date: Thu, 11 Dec 2014 18:39:15 GMT

Expires: -1

Cache-Control: private, max-age=0

Content-Type: text/html; charset=windows-1251

Set-Cookie: PREF=ID=714f975c931d0648:FF=0:TM=1418323155:LM=1418323155:S=gk9jV6JHg-YVFBsC; expires=Sat, 10-Dec-2016 18:39:15 GMT; path=/; domain=.google.com.ua

Set-Cookie: NID=67=X3L_r6qc87PRaACqn610x6iSD12rr6x6KQDHv7WkmpkKTwhPYPh7r21cLq3RG2rRlN3mwwMkzSlnd5Jw9kzZ_Lty3s8J0jtV4aN9PzXQAgPLokKrTFE5R9ZEb6N3yF4x; expires=Fri, 12-Jun-2015 18:39:15 GMT; path=/; domain=.google.com.ua; HttpOnly

P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."

Server: gws

X-XSS-Protection: 1; mode=block

X-Frame-Options: SAMEORIGIN

Alternate-Protocol: 80:quic,p=0.02

Transfer-Encoding: chunked



473b

<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="uk"><head><meta content="/images/google_favicon_128.png" itemprop="image"><title>Google</title><script>(function(){window.google={kEI:'0-SJVNmWC5W4ogSlvYL4Aw',kEXPI:'4010073,4011559,4013920,4016824,4017578,4020110,4020347,4020562,4021587,4021598,4023566,4024625,4025103,4025170,4025743,4025769,4025828,4026540,4027041,4027316,8500394,8500851,8500923,10200083,10200716,10200743,10200765,10200835,10200838,10200876',authuser:0,kSID:'0-SJVNmWC5W4ogSlvYL4Aw'};google.kHL='uk';})();(function(){google.lc=[];google.li=0;google.getEI=function(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute("eid")));)a=a.parentNode;return b||google.kEI};google.https=function(){return"https:"==window.location.protocol};google.ml=function(){};google.time=function(){return(new Date).getTime()};google.log=function(a,b,d,e,k){var c=new Image,h=google.lc,f=google.li,g="",l=google.ls||"";c.onerror=c.onload=c.onabort=function(){delete h[f]};h[f]=c;d||-1!=b.search("&ei=")||(e=google.getEI(e),g="&ei="+e,e!=google.kEI&&(g+="&lei="+google.kEI));a=d||"/"+(k||"gen_204")+"?atyp=i&ct="+a+"&cad="+b+g+l+"&zx="+google.time();/^http:/i.test(a)&&google.https()?(google.ml(Error("a"),!1,{src:a,glmm:1}),delete h[f]):(c.src=a,google.li=f+1)};google.y={};google.x=function(a,b){google.y[a.id]=[a,b];return!1};google.load=function(a,b,d){google.x({id:a+m++},function(){google.load(a,b,d)})};var m=0;})();google.kCSI={};var _gjwl=location;function _gjuc(){var a=_gjwl.href.indexOf("#");if(0<=a&&(a=_gjwl.href.substring(a),0<a.indexOf("&q=")||0<=a.indexOf("#q="))&&(a=a.substring(1),-1==a.indexOf("#"))){for(var d=0;d<a.length;){var b=d;"&"==a.charAt(b)&&++b;var c=a.indexOf("&",b);-1==c&&(c=a.length);b=a.substring(b,c);if(0==b.indexOf("fp="))a=a.substring(0,d)+a.substring(c,a.length),c=d;else if("cad=h"==b)return 0;d=c}_gjwl.href="/search?"+a+"&cad=h";return 1}return 0}
function _gjh(){!_gjuc()&&window.google&&google.x&&google.x({id:"GJH"},function(){google.nav&&google.nav.gjh&&google.nav.gjh()})}

Server fails on URI "/ RTSP/1.0" contains an illegal character #\ at position 1

Hello, I've got the following error, how can I find the request and illegal character which cause that?

220.200.24.189 - [18/Feb/2016:04:03:31 +00:00] "GET / HTTP/1.0" 200 381 "-" "-"
27.10.78.48 - [18/Feb/2016:04:03:32 +00:00] "OPTIONS / HTTP/1.0" 404 683 "-" "-"

debugger invoked on a FAST-HTTP.ERROR:CB-MESSAGE-COMPLETE in thread
#<THREAD "clack-handler-woo" RUNNING {1009FB7053}>:
  Callback Error: the message-complete callback failed
  URI "/ RTSP/1.0" contains an illegal character #\  at position 1.

Thank you.

A question about the benchmark

I have a question about the benchmark of the README.
Briefly saying, "How much memory your fast-http use?".
Below, I describe the reason why I argue it.

I looked the http-parser of C, and I noticed it takes great effort to use less and less memory. The README of http-parser says, "it only requires about 40 bytes of data per message stream". And, by looking its codes, it tries to be less stack allocation, and to make a smaller object file after compilation.

Using less memory is generally bad for speed at runtime. As far as I saw, these points in http-parser are quite bad for speed (but good for less memory).

  1. Using bit-fields for struct members.

    You know, accessing bit-fields is slow.

  2. Using less local variables.

    Especially, it don't use a local variable for controlling. This is obviously bad for speed.

  3. Not inlining too much code.

    One way to optimizing a state-machine is using the 'direct threaded code' technique.
    (I think you used this technique for your fast-http).
    If control-flows are taken into codes, the code will be faster, and larger.

By the way, the README of your fast-http doesn't say about its memory usage. So, I checked memory usages of these systems.

http-parser fast-http customized C
parser struct size 32 112 48
code size 13355 > 41556 15243
stack usage of parser 104 16 88
stack usage of URL parser 40 16 40
any other functions 0 16 0
'Elapsed Time' 0.2924059 0.1169 0.1053272

All 'size' are taken at x86-64 MacOS X 10.9
At http-parser, 'code size' is the size of the entire system. At fast-http, the 'code size' is only for the 'http-parse' function. The size of the entire system is much more.

('customized C' is the C http-parser I customized to remove bad points for speed. In exchange for a bit more memory, it works more faster.
code is here: https://github.com/y2q-actionman/http-parser/tree/direct-threaded )

The speed of http-parser is obviously restricted by its small memory, but I think this is intended and designed. So, comparing two systems only with the speed is quite unfair for the http-parser. I think you should say something about the fact that your fast-http spends more memory than the http-parser.
(Moreover, the README of http-parser doesn't feature its speed!)

Thank you for reading.

Expectation failed: NIL when parsing response from google.com

Hello! I'm building out carrier and I'm getting an error while grabbing some responses from Google:

(as:with-event-loop (:catch-app-errors nil)
  (future-handler-case
    (multiple-future-bind (nil status)
        (carrier:request "http://google.com")
      (format t "status: ~a~%" status))
    (t (e) (format t "err: ~a~%" e))))

This is throwing the error Expectation failed: NIL with backtrace

> Error: Expectation failed: NIL
> While executing: FAST-HTTP.PARSER::PARSE-CHUNKED-BODY, in process listener(1).
> Type :GO to continue, :POP to abort, :R for a list of available restarts.
> If continued: Skip evaluation of (load "c:/htdocs/test/lisp/carrier")
> Type :? for other options.
1 > b
 (2EA9B04) : 0 (PARSE-CHUNKED-BODY #S(FAST-HTTP.HTTP:HTTP-RESPONSE :METHOD NIL :MAJOR-VERSION 1 ...) #S(FAST-HTTP.PARSER:CALLBACKS :MESSAGE-BEGIN #<CCL:COMPILED-LEXICAL-CLOSURE # #x18238496> :URL #<Compiled-function # (Non-Global)  #x181C049E> ...) #(60 33 100 111 99 ...) 0 9204) 1935
 (2EA9B2C) : 1 (PARSE-RESPONSE #S(FAST-HTTP.HTTP:HTTP-RESPONSE :METHOD NIL :MAJOR-VERSION 1 ...) #S(FAST-HTTP.PARSER:CALLBACKS :MESSAGE-BEGIN #<CCL:COMPILED-LEXICAL-CLOSURE # #x18238496> :URL #<Compiled-function # (Non-Global)  #x181C049E> ...) #(60 33 100 111 99 ...) :START 0 :END NIL) 6007
 (2EA9B5C) : 2 (FUNCALL #'#<(:INTERNAL FAST-HTTP:MAKE-PARSER)> #(60 33 100 111 99 ...) :START 0 :END NIL) 783
 (2EA9BA4) : 3 (READ-SOCKET-DATA #<CL-ASYNC:SOCKET #x18238156> #<CCL:COMPILED-LEXICAL-CLOSURE (:INTERNAL CL-ASYNC::TCP-READ-CB) #x1824DB1E> :SOCKET-IS-EVBUFFER NIL) 647
 (2EA9BD0) : 4 (FUNCALL #'#<CFFI-CALLBACKS::|CL-ASYNC::TCP-READ-CB|> 11943762) 295
 (2EA9BE8) : 5 (%PASCAL-FUNCTIONS% 10 11943762) 343
 (2EA9C30) : 6 (EVENT-BASE-DISPATCH #<A Foreign Pointer #x64F410>) 127
 (2EA9C3C) : 7 (START-EVENT-LOOP #<Anonymous Function #x18224D56> :FATAL-CB NIL :LOGGER-CB NIL :DEFAULT-EVENT-CB NIL :CATCH-APP-ERRORS NIL) 2247
 (2EA9CB8) : 8 (CALL-CHECK-REGS CL-ASYNC:START-EVENT-LOOP #<Anonymous Function #x18224D56> :FATAL-CB NIL :LOGGER-CB NIL :DEFAULT-EVENT-CB NIL :CATCH-APP-ERRORS NIL) 247
 (2EA9CD4) : 9 (FUNCALL #'#<(:INTERNAL CCL::WITH-COMPILATION-UNIT-BODY CCL::LOAD-FROM-STREAM)>) 655
 (2EA9D10) : 10 (CALL-WITH-COMPILATION-UNIT #<CCL:COMPILED-LEXICAL-CLOSURE (:INTERNAL CCL::WITH-COMPILATION-UNIT-BODY CCL::LOAD-FROM-STREAM) #x25418FE> :OVERRIDE NIL) 183
 (2EA9D34) : 11 (LOAD-FROM-STREAM #<BASIC-FILE-CHARACTER-INPUT-STREAM ("c:/htdocs/test/lisp/carrier.lisp"/996 UTF-8) #x175FCD06> NIL) 319
 (2EA9D54) : 12 (%LOAD #P"c:/htdocs/test/lisp/carrier.lisp" NIL NIL :ERROR :DEFAULT NIL) 4599
 (2EA9DF8) : 13 (LOAD "c:/htdocs/test/lisp/carrier" :VERBOSE NIL :PRINT NIL :IF-DOES-NOT-EXIST :ERROR :EXTERNAL-FORMAT :DEFAULT :PRESERVE-OPTIMIZATION-SETTINGS NIL) 927
 (2EA9E4C) : 14 (CALL-CHECK-REGS LOAD "c:/htdocs/test/lisp/carrier") 247
 (2EA9E68) : 15 (CHEAP-EVAL (LOAD "c:/htdocs/test/lisp/carrier")) 87
 (2EA9E84) : 16 (FUNCALL #'#<(:INTERNAL CCL::EVAL-STRING CCL::STARTUP-CCL)> "(load \"c:/htdocs/test/lisp/carrier\")") 431
 (2EA9EA8) : 17 (STARTUP-CCL "home:ccl-init") 1423
 (2EA9ED8) : 18 (FUNCALL #'#<(:INTERNAL (CCL:TOPLEVEL-FUNCTION (CCL::LISP-DEVELOPMENT-SYSTEM T)))>) 55
 (2EA9EE8) : 19 (FUNCALL #'#<(:INTERNAL CCL::MAKE-MCL-LISTENER-PROCESS)>) 559
 (2EA9F34) : 20 (RUN-PROCESS-INITIAL-FORM #<TTY-LISTENER listener(1) [Active] #x173437AE> (#<CCL:COMPILED-LEXICAL-CLOSURE # #x173434E6>)) 639
 (2EA9F78) : 21 (FUNCALL #'#<(:INTERNAL (CCL::%PROCESS-PRESET-INTERNAL (CCL:PROCESS)))> #<TTY-LISTENER listener(1) [Active] #x173437AE> (#<CCL:COMPILED-LEXICAL-CLOSURE # #x173434E6>)) 519
 (2EA9FCC) : 22 (FUNCALL #'#<(:INTERNAL CCL::THREAD-MAKE-STARTUP-FUNCTION)>) 255

This is tested with the latest git version if carrier and fast-io, CCL x86_64 (Windows 7, if that matters).

Do not use keywords for headers

While issue not SBCL specific this code is:

(ql:quickload :fast-http)
(ql:quickload :cl-interpol)
(ql:quickload :trivial-utf-8)
(cl-interpol:enable-interpol-syntax)

(defun random-garbage (length)
           (let ((chars "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
                 (garbage (make-string length)))
             (dotimes (i length)
               (setf (aref garbage i) (aref chars (random (length chars)))))
             garbage))

(defun test-memory-leak (iterations)
           (loop for i from 0 to iterations do
             (let* ((request (list #?"POST / HTTP/1.1\r\n"
                              #?"Host: www.example.com\r\n"
                              #?"Content-Type${(random-garbage 1000)}: application/x-www-form-urlencoded\r\n"
                              #?"Content-Length: 4\r\n"
                              #?"Connection: close\r\n"
                              #?"\r\n"
                              #?"q=42\r\n"))
                    (http (fast-http:make-http-request :store-body t ))
                    (parser (fast-http:make-parser http :store-body t :header-callback (lambda (h) (declare (ignore h)) ))))
               (loop for req-chunk in request do              
                 (funcall parser (trivial-utf-8:string-to-utf-8-bytes req-chunk))))))

(define-alien-routine print-generation-stats void)
(sb-ext:gc :full t)
(print-generation-stats)

;; MAGIC
(test-memory-leak 200000) ;; maybe you should adapt this constant to your memory settings

Output

 Load 1 ASDF system:
    fast-http
; Loading "fast-http"
..
(:FAST-HTTP)
* To load "cl-interpol":
  Load 1 ASDF system:
    cl-interpol
; Loading "cl-interpol"
...
#<ASDF/SYSTEM:SYSTEM "cl-interpol"> 
(:CL-INTERPOL)
* To load "trivial-utf-8":
  Load 1 ASDF system:
    trivial-utf-8
; Loading "trivial-utf-8"

(:TRIVIAL-UTF-8)
* 
* 
RANDOM-GARBAGE
* 
TEST-MEMORY-LEAK
* 
PRINT-GENERATION-STATS
* 
NIL
*  Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB   LUB  !move  Alloc  Waste   Trig    WP  GCs Mem-age
   0:     0     0     0     0     1     0     0     0     0        0 32768 10737418    0   0  0.0000
   1:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   2:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   3:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   4:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   5:     0     0     0     0   636   357    68   128     6 37547216 1413936 48284634  698   1  0.0000
   6:     0     0     0     0  1702   155     0     0     0 60850176     0  2000000 1608   0  0.0000
   Total bytes allocated    = 98397392
   Dynamic-space-size bytes = 1073741824

NIL
*
Heap exhausted during garbage collection: 128 bytes available, 480 requested.
 Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB   LUB  !move  Alloc  Waste   Trig    WP  GCs Mem-age
   0:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   1:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   2:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   3: 15219 23801     0     0  1350 13413   348    44     0 494612320 1986720 10737418    0   0  0.9943
   4:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   5:     0     0     0     0   636   357    68   128     6 37547216 1413936 48284634  694   1  0.0000
   6:     0     0     0     0  1702   155     0     0     0 60850176     0  2000000 1613   0  0.0000
   Total bytes allocated    = 1068602064
   Dynamic-space-size bytes = 1073741824
GC control variables:
   *GC-INHIBIT* = true
   *GC-PENDING* = true
   *STOP-FOR-GC-PENDING* = false
fatal error encountered in SBCL pid 57429(tid 140737353971520):
Heap exhausted, game over.

Welcome to LDB, a low-level debugger for the Lisp runtime environment.
ldb>

PS. funny thing - chunga and its friends do the same mistake.

type error in collect-prev-header-value

So I'm trying to track down a memory corruption bug. First step is to turn off all of the speed/safety optimizations. When I do that I get a compilation error in make-parser's collect-prev-header-value flet:

...
    (flet ((collect-prev-header-value ()
             (when header-value-buffer
               (let ((header-value))
                 (if (string= parsing-header-field "set-cookie")
                     (push header-value (gethash "set-cookie" headers))
                     (multiple-value-bind (previous-value existp)
                         (gethash (the simple-string parsing-header-field) headers)
                       (setf (gethash (the simple-string parsing-header-field) headers)
                             (if existp
                                 (if (simple-string-p previous-value)
                                     (concatenate 'string (the simple-string previous-value) ", " header-value)
                                     (format nil "~A, ~A" previous-value header-value))
                                 (if (number-string-p header-value)
                                     (read-from-string header-value)
                                     header-value)))))))))

gives:

; 
; caught WARNING:
;   Derived type of HEADER-VALUE is
;     (VALUES NULL &OPTIONAL),
;   conflicting with its asserted type
;     SIMPLE-STRING.
;   See also:
;     The SBCL Manual, Node "Handling of Types"

the problem being that header-value is going to be nil when we call number-string-p on it. Probably not the bug I'm looking for, but a problem nonetheless.

Type error during load fast-http using quicklisp.

I got this when I load this system using quicklisp.

Error: Variable +TOKENS+ was declared type (SIMPLE-ARRAY CHARACTER (128)) but is being set to value #(#\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\Null #\! #\Null #\# #\$ #\% #\& #\' ...).
   1 (continue) Try loading ......

Actually, according to CLHS, #( and ) are used to notate a simple vector., and the compound type specifiers derive from simple-vector is the same as (simple-array t (size)). I think this is the reason why an error was signaled.

My lisp implementation is LispWorks 7.1, and this bug can be fixed by using coerceon +tokens+ and +unhex+ like this:

(define-constant +tokens+
                 (coerce #( #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul
                            #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul
                            #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul
                            #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul
                            #\Nul   #\!   #\Nul   #\#    #\$    #\%    #\&    #\'
                            #\Nul  #\Nul   #\*    #\+   #\Nul    #\-   #\.   #\Nul
                            #\0    #\1    #\2    #\3    #\4    #\5    #\6    #\7
                            #\8    #\9   #\Nul  #\Nul  #\Nul  #\Nul  #\Nul  #\Nul
                            #\Nul   #\a    #\b    #\c    #\d    #\e    #\f    #\g
                            #\h    #\i    #\j    #\k    #\l    #\m    #\n    #\o
                            #\p    #\q    #\r    #\s    #\t    #\u    #\v    #\w
                            #\x    #\y    #\z   #\Nul  #\Nul  #\Nul   #\^    #\_
                            #\`    #\a    #\b    #\c    #\d    #\e    #\f    #\g
                            #\h    #\i    #\j    #\k    #\l    #\m    #\n    #\o
                            #\p    #\q    #\r    #\s    #\t    #\u    #\v    #\w
                            #\x    #\y    #\z   #\Nul   #\|   #\Nul   #\~   #\Nul ) 
                         '(simple-array character (128)))
                 :test 'equalp)

Don't collapse Set-Cookie header values

This would be a problem mainly of parsing an HTTP response.

RFC 2109 is saying:

   An origin server may include multiple Set-Cookie headers in a
   response.  Note that an intervening gateway could fold multiple such
   headers into a single header.

However, It is very common an attribute "Expires" contains "," in its value like "Wed, 04 Mar 2015 07:05:42 GMT".

In RFC 6265, which deprecates RFC 2109, Set-Cookie header can have only single Cookie in its value.

Error when parsing chunked data (response)

Hello, I'm seeing an error when parsing chunked data. Actually two errors:

  1. An exception, INVALID-HEADER-TOKEN: invalid character in header
  2. The finish-callback is called before the other chunks come in

Test case:

(defparameter *response*
  (concatenate 'string
#?"HTTP/1.1 200 OK\r
Server: Wookie (0.3.11)\r
Transfer-Encoding: chunked\r
\r
7\r
hello, \r
4\r
how \r
4\r
are \r
4\r
you?\r
0\r
"))

(let* ((http (fast-http:make-http-response))
       (parser (fast-http:make-parser http
                 :header-callback
                   (lambda (x)
                     (format t "headers: ~a~%" x))
                 :body-callback
                   (lambda (chunk start end)
                     (format t "chunk: ~a~%" (babel:octets-to-string (subseq chunk start end))))
                 :finish-callback
                   (lambda ()
                     (format t "finish.~%"))))
       (chunk1 (subseq *response* 0 72))    ; <headers>
       (chunk2 (subseq *response* 72 84))   ; "hello, "
       (chunk3 (subseq *response* 84 102))  ; "how are "
       (chunk4 (subseq *response* 102))     ; "you?"
       (do-parse (lambda (chunk)
                   (funcall parser (babel:string-to-octets chunk)))))
  (funcall do-parse chunk1)
  (funcall do-parse chunk2)
  (funcall do-parse chunk3)
  (funcall do-parse chunk4))

Expected:

headers: #<HASH-TABLE :TEST EQUAL size 2/60 #x1915AB6E>
chunk: hello, 
chunk: how are 
chunk: you?
finish.

Actual:

headers: #<HASH-TABLE :TEST EQUAL size 2/60 #x190C162E>
chunk: hello, 
finish.
INVALID-HEADER-TOKEN: invalid character in header
   [Condition of type FAST-HTTP.ERROR:INVALID-HEADER-TOKEN]

Any ideas? Thanks!

Fails when parsing URLs with a space

See the test case below. It's not strictly good HTTP to pass a URI with non-escaped spaces, but this is fairly common to my knowledge.

(ql:quickload '(:fast-http :babel))

(defpackage :fast-test
  (:use :cl))
(in-package :fast-test)

(let* ((str (format nil "GET /test?name=my name HTTP/1.1~c~c~c~c" #\return #\newline #\return #\newline))
       (data (babel:string-to-octets str))
       (http (fast-http:make-http-request))
       (parser (fast-http:make-parser
                 http
                 :header-callback (lambda (headers)
                                    (format t "headers: ~a~%" headers)))))
  (funcall parser data))

That can also be tested via the master version of Wookie:

(ql:quickload :wookie)

(let ((wookie-config:*debug-on-error* t))
  (as:with-event-loop ()
    (wookie:start-server (make-instance 'wookie:listener :port 8090))))
curl '127.0.0.1:8090/test?myname=andrew lyon'

Thanks for taking a look!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.