http-parser added by caolanm on Wed Sep 30 16:52:04 2015
(module feathers-http-parser
;; exports
(read-request
http-connection
destroy-connection!
make-request
request-method
request-method-set!
request-url
request-url-set!
request-path
request-path-set!
request-query
request-query-set!
request-headers
request-headers-set!
request-headers-ref
request-port
request-port-set!
request-keep-alive
request-keep-alive-set!
split-path-string
http-chunk-size)
(import scheme chicken foreign)
(use lolevel
ports
srfi-13
srfi-69
defstruct
data-structures
uri-common
miscmacros)
(define http-chunk-size (make-parameter 1024))
(foreign-declare "#include \"http-parser/http_parser.h\"")
(define HTTP_REQUEST
(foreign-value "HTTP_REQUEST" int))
(define PAUSED
(foreign-value "HPE_PAUSED" int))
(define-syntax method
(syntax-rules ()
((_ name symbol)
(cons (foreign-value name int) (quote symbol)))))
(define METHODS
(alist->hash-table
(list
;; Request Methods
(method "HTTP_DELETE" DELETE)
(method "HTTP_GET" GET)
(method "HTTP_HEAD" HEAD)
(method "HTTP_POST" POST)
(method "HTTP_PUT" PUT)
;; pathological
(method "HTTP_CONNECT" CONNECT)
(method "HTTP_OPTIONS" OPTIONS)
(method "HTTP_TRACE" TRACE)
;; WebDAV
(method "HTTP_COPY" COPY)
(method "HTTP_LOCK" LOCK)
(method "HTTP_MKCOL" MKCOL)
(method "HTTP_MOVE" MOVE)
(method "HTTP_PROPFIND" PROPFIND)
(method "HTTP_PROPPATCH" PROPPATCH)
(method "HTTP_SEARCH" SEARCH)
(method "HTTP_UNLOCK" UNLOCK)
(method "HTTP_BIND" BIND)
(method "HTTP_REBIND" REBIND)
(method "HTTP_UNBIND" UNBIND)
(method "HTTP_ACL" ACL)
;; subversion
(method "HTTP_REPORT" REPORT)
(method "HTTP_MKACTIVITY" MKACTIVITY)
(method "HTTP_CHECKOUT" CHECKOUT)
(method "HTTP_MERGE" MERGE)
;; upnp
(method "HTTP_MSEARCH" MSEARCH)
(method "HTTP_NOTIFY" NOTIFY)
(method "HTTP_SUBSCRIBE" SUBSCRIBE)
(method "HTTP_UNSUBSCRIBE" UNSUBSCRIBE)
;; RFC-5789
(method "HTTP_PATCH" PATCH)
(method "HTTP_PURGE" PURGE)
;; CalDAV
(method "HTTP_MKCALENDAR" MKCALENDAR))
size: 31
hash: number-hash
test: =))
(define method->symbol
(cut hash-table-ref METHODS <>))
(define parser-request
(foreign-lambda* scheme-object
(((nonnull-c-pointer (struct http_parser)) parser))
"C_return(CHICKEN_gc_root_ref(parser->data));"))
(define parser-method
(foreign-lambda* unsigned-int
(((nonnull-c-pointer (struct http_parser)) parser))
"C_return(parser->method);"))
(define parser-error
(foreign-lambda* unsigned-int
(((const (nonnull-c-pointer (struct http_parser))) parser))
"C_return(parser->http_errno);"))
(define error-description
(foreign-lambda* c-string ((int errno))
"C_return(http_errno_description(errno));"))
(define-external
(on_message_begin ((nonnull-c-pointer (struct http_parser)) parser)) int
;(print "message_begin")
0)
(define-external
(on_url ((nonnull-c-pointer (struct http_parser)) parser)
((const c-string) at)
(size_t len)) int
;(printf "url: ~S~n" (string-take at len))
(let ((req (parser-request parser)))
(request-url-set! req
(string-append/shared (request-url req) (string-take at len))))
0)
(define-external
(on_header_field ((nonnull-c-pointer (struct http_parser)) parser)
((const c-string) at)
(size_t len)) int
;(printf "header_field: ~S~n" (string-take at len))
(let* ((req (parser-request parser))
(h (request-headers req)))
(if (and (not (null? h)) (string? (car h)))
(set-car! h (string-append/shared
(car (request-headers req))
(string-downcase! (string-take at len))))
(request-headers-set! req
(cons (string-downcase! (string-take at len)) h))))
0)
(define-external
(on_header_value ((nonnull-c-pointer (struct http_parser)) parser)
((const c-string) at)
(size_t len)) int
;(printf "header_value: ~S~n" (string-take at len))
(let* ((req (parser-request parser))
(h (request-headers req))
(curr (car h)))
(if (pair? curr)
(set-cdr! curr (string-append/shared (cdr curr) (string-take at len)))
(set-car! h (cons (string->symbol curr)
(string-take at len)))))
0)
(define http-should-keep-alive
(foreign-lambda bool "http_should_keep_alive"
(const (nonnull-c-pointer (struct http_parser)))))
(define (split-path-string str)
(cons '/ (map uri-decode-string (string-split str "/"))))
(define-external
(on_headers_complete ((nonnull-c-pointer (struct http_parser)) parser)) int
;(print "headers_complete")
(let ((req (parser-request parser)))
(request-headers-complete-set! req #t)
(request-keep-alive-set! req (http-should-keep-alive parser))
(request-method-set! req (method->symbol (parser-method parser)))
(let* ((url (request-url req))
(qi (string-index url #\?)))
(request-path-set! req
(split-path-string (if qi (substring url 0 qi) url)))
(when qi
(request-query-set! req (form-urldecode (substring url (+ qi 1)))))))
;(parser-pause parser 1)
0)
(define-external
(on_body ((nonnull-c-pointer (struct http_parser)) parser)
((const c-string) at)
(size_t len)) int
;(printf "body: ~S~n" (string-take at len))
(let ((req (parser-request parser)))
(request-chunk-position-set! req 0)
(request-chunk-length-set! req len)
(request-chunk-data-set! req (string-take at len)))
0)
(define-external
(on_message_complete ((nonnull-c-pointer (struct http_parser)) parser)) int
;(print "message_complete")
(let ((req (parser-request parser)))
(request-complete-set! req #t))
(parser-pause parser 1)
0)
(define make-settings
(foreign-lambda* (nonnull-c-pointer (struct http_parser_settings)) ()
"struct http_parser_settings *settings = malloc(
sizeof(struct http_parser_settings)
);
memset(settings, 0, sizeof(*settings));
settings->on_message_begin = on_message_begin;
settings->on_url = on_url;
settings->on_header_field = on_header_field;
settings->on_header_value = on_header_value;
settings->on_headers_complete = on_headers_complete;
settings->on_body = on_body;
settings->on_message_complete = on_message_complete;
C_return(settings);"))
(define SETTINGS (make-settings))
(define make-parser
(foreign-lambda* (nonnull-c-pointer (struct http_parser))
(((enum http_parser_type) parser_type))
"struct http_parser *parser = malloc(sizeof(struct http_parser));
http_parser_init(parser, parser_type);
C_return(parser);"))
(define parse
(foreign-safe-lambda* size_t
(((nonnull-c-pointer (struct http_parser)) parser)
((const (nonnull-c-pointer (struct http_parser_settings))) settings)
(c-string buf)
(int len)
(scheme-object req))
"size_t result;
parser->data = CHICKEN_new_gc_root();
CHICKEN_gc_root_set(parser->data, req);
result = http_parser_execute(parser, settings, buf, len);
CHICKEN_delete_gc_root(parser->data);
C_return(result);"))
(define parser-pause
(foreign-lambda void "http_parser_pause"
(nonnull-c-pointer (struct http_parser))
int))
(defstruct connection
parser
buffer
buffer-length
request
port)
(define-record-printer (connection x out)
(fprintf out "#" (connection-port x)))
(define (destroy-connection! conn)
(free (connection-parser conn)))
(define (http-connection port)
(make-connection
port: port
parser: (make-parser HTTP_REQUEST)))
(defstruct request
method
(url "")
path
(query '())
(headers '())
(chunk-position 0)
(chunk-length 0)
chunk-data
headers-complete
complete
keep-alive
port)
(define-record-printer (request x out)
(fprintf out "#" (request-method x) (request-url x)))
(define (request-headers-ref req name)
(alist-ref name (request-headers req)))
(define (read-http-chunk p limit)
(##sys#check-input-port p #t 'read-http-chunk)
(let ((buffer (##sys#make-string limit)))
(let loop ((i 0))
(if (and limit (fx>= i limit))
(values (##sys#substring buffer 0 i) i)
(if (or ((##sys#slot (##sys#slot p 2) 6) p) ; char-ready?
(fx= i 0))
(let ((c (##sys#read-char-0 p)))
(if (eof-object? c)
(if (fx= i 0)
(values c i)
(values (##sys#substring buffer 0 i) i))
(begin
(##core#inline "C_setsubchar" buffer i c)
(loop (fx+ i 1)))))
(values (##sys#substring buffer 0 i) i))))))
(define (connection-read-line conn)
(let ((buf (connection-buffer conn)))
(if buf
(begin
(connection-buffer-set! conn #f)
(values buf (connection-buffer-length conn)))
(read-http-chunk (connection-port conn)
(http-chunk-size)))))
(define (parser-execute conn req str len)
(let* ((parser (connection-parser conn))
(parsed (parse parser SETTINGS str len req)))
(when (< parsed len)
(let ((errno (parser-error parser)))
(if (= PAUSED errno)
(begin
(connection-buffer-set! conn (substring/shared str parsed))
(connection-buffer-length-set! conn (- len parsed))
(parser-pause (connection-parser conn) 0))
(abort (error-description errno)))))))
(define (body-char-ready? req)
(or (request-complete req)
(< (request-chunk-position req)
(request-chunk-length req))))
(define (body-read-char req conn)
(cond
((< (request-chunk-position req) (request-chunk-length req))
(begin0
(string-ref (request-chunk-data req) (request-chunk-position req))
(request-chunk-position-set! req (+ 1 (request-chunk-position req)))))
((request-complete req) #!eof)
(else
(receive (buf len) (connection-read-line conn)
(if (= 0 len)
#!eof
(begin
(parser-execute conn req buf len)
(body-read-char req conn)))))))
(define (make-body-port req conn)
(make-input-port
(cut body-read-char req conn) ;; read-char
(cut body-char-ready? req) ;; char-ready?
(cut close-input-port (connection-port conn)) ;; close
#f ;; peek-char
#f ;; read-string!
#f ;; read-line
))
(define (skip-body req conn)
(until (request-complete req)
(receive (buf len) (connection-read-line conn)
(if (= 0 len)
(abort "Unexpected end of input")
(parser-execute conn req buf len)))))
(define (read-headers req conn)
(until (request-headers-complete req)
(receive (buf len) (connection-read-line conn)
(if (= 0 len)
(abort "Unexpected end of input")
(parser-execute conn req buf len)))))
(define (read-request conn)
(when (connection-request conn)
(skip-body (connection-request conn) conn))
(let ((req (make-request)))
(connection-request-set! conn req)
(read-headers req conn)
(request-port-set! req (make-body-port req conn))
req))
)