Welcome to the CHICKEN Scheme pasting service

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))

)

Your annotation:

Enter a new annotation:

Your nick:
The title of your paste:
Your paste (mandatory) :
Which egg implements a Scheme to JavaScript compiler?
Visually impaired? Let me spell it for you (wav file) download WAV