Skip to content

Commit

Permalink
Merge pull request #5 from jeko2000/feature/add-max-bytes-to-request-…
Browse files Browse the repository at this point in the history
…body-middleware

Fix #4 Add max-bytes parameter for wrap-request-body middleware
  • Loading branch information
jeko2000 authored Apr 3, 2022
2 parents 952c8bc + 2c5b095 commit aea7f27
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 19 deletions.
59 changes: 43 additions & 16 deletions src/middleware/request-body.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,58 @@
(:use :cl)
(:import-from :tiny-routes.request
#:raw-body
#:content-length
#:request-append)
(:import-from :tiny-routes.middleware.builder
#:wrap-request-mapper)
(:import-from :tiny-routes.response
#:payload-too-large)
(:export #:read-stream-to-string
#:input-stream-mapper
#:payload-too-large-handler
#:wrap-request-body))

(in-package :tiny-routes.middleware.request-body)

;; TODO: Add support for a max number of bytes the server is willing
;; to read before sending back a Payload Too Large response.
(defun read-stream-to-string (input-stream)
"Read INPUT-STREAM entirely and return the contents as a string."
(when input-stream)
(defun read-stream-to-string (input-stream content-length)
"Read INPUT-STREAM return contents as a string.
If CONTENT-LENGTH is non-nil, it must be a positive integer
representing the maximum number of bytes to read from INPUT-STREAM."
(with-output-to-string (output-stream)
(let ((buf (make-array 4096 :element-type (stream-element-type input-stream))))
(loop for pos = (read-sequence buf input-stream)
while (plusp pos)
do (write-sequence buf output-stream :end pos)))))
(if content-length
(let* ((buffer (make-array content-length :element-type (stream-element-type input-stream)))
(position (read-sequence buffer input-stream)))
(write-sequence buffer output-stream :end position))
(let ((buf (make-array 4096 :element-type (stream-element-type input-stream))))
(loop for pos = (read-sequence buf input-stream)
while (plusp pos)
do (write-sequence buf output-stream :end pos))))))

(defun payload-too-large-handler (request)
"Return a response representing a payload too large to be handled."
(declare (ignore request))
(payload-too-large))

(defun wrap-request-body (handler &key (input-stream-mapper #'read-stream-to-string))
(defun wrap-request-body (handler &key max-bytes
(input-stream-mapper #'read-stream-to-string)
(payload-too-large-handler #'payload-too-large-handler))
"Wrap HANDLER such that the result of applying INPUT-STREAM-MAPPER to
the request body is made available to the request via `:request-body'."
(wrap-request-mapper
handler
(lambda (request)
(let ((body (funcall input-stream-mapper (raw-body request))))
(request-append request :request-body body)))))
the request body is made available to the request via `:request-body'.
If MAX-BYTES is non-nil, it must be a positive integer representing the
maximum number of bytes to read from INPUT-STREAM.
PAYLOAD-TOO-LARGE-HANDLER is a `handler' called if MAX-BYTES is non nil
and the content-length of string is greater than MAX-BYTES."
(lambda (request)
(let ((content-length (content-length request)))
(cond ((null content-length)
(funcall handler request))
;; handle if request is too large
((and (typep max-bytes '(integer 1))
(> content-length max-bytes))
(funcall payload-too-large-handler request))
(t
(let ((body (funcall input-stream-mapper (raw-body request) content-length)))
(funcall handler (request-append request :request-body body))))))))
11 changes: 8 additions & 3 deletions t/tiny-routes-test.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,19 @@

(test middleware-test1
(with-input-from-string (in "Sample request body stream")
(let* ((request (make-request :request-method :get :request-uri "/users/jeko" :raw-body in)))
(let* ((request (make-request :request-method :get :request-uri "/users/jeko" :raw-body in
:content-length 26)))
(is (eq in (raw-body request)))
(is (string= "Sample request body stream"
(request-get (response-body (funcall (wrap-request-body #'echo-handler) request))
:request-body)))
(is (equalp '(413 nil nil)
(funcall (wrap-request-body #'echo-handler :max-bytes 2) request)))
(is (equalp '(:user-id "jeko")
(request-get (response-body (funcall (wrap-request-matches-path-template #'echo-handler "/users/:user-id")
request))
(request-get (response-body
(funcall
(wrap-request-matches-path-template #'echo-handler "/users/:user-id")
request))
:path-parameters))))))

(def-suite* routes :in tiny-routes)
Expand Down

0 comments on commit aea7f27

Please sign in to comment.