-
Notifications
You must be signed in to change notification settings - Fork 2
/
response.scm
168 lines (153 loc) · 5.02 KB
/
response.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
(defstruct response
port
(code 200)
reason
(headers `((content-type . "text/html; charset=utf-8")
(date . ,(rfc1123-time->string (seconds->utc-time)))
;; TODO: set this based on the keep-alive value of the request obj
(connection . "keep-alive")
(x-content-type-options . "nosniff")))
body)
(define-record-printer (response x out)
(fprintf out "#<response ~S ~S ~S ~S>"
(response-code x)
(response-reason x)
(response-headers x)
(response-body x)))
(define (response-keep-alive res)
(and-let* ((value (response-headers-ref res 'connection)))
(string=? "keep-alive" value)))
(define status-codes
(alist->hash-table
'((100 . "Continue")
(101 . "Switching Protocols")
(200 . "OK")
(201 . "Created")
(202 . "Accepted")
(203 . "Non-Authoritative Information")
(204 . "No Content")
(205 . "Reset Content")
(206 . "Partial Content")
(300 . "Multiple Choices")
(301 . "Moved Permanently")
(302 . "Moved Temporarily")
(303 . "See Other")
(304 . "Not Modified")
(305 . "Use Proxy")
(400 . "Bad Request")
(401 . "Unauthorized")
(402 . "Payment Required")
(403 . "Forbidden")
(404 . "Not Found")
(405 . "Method Not Allowed")
(406 . "Not Acceptable")
(407 . "Proxy Authentication Required")
(408 . "Request Time-out")
(409 . "Conflict")
(410 . "Gone")
(411 . "Length Required")
(412 . "Precondition Failed")
(413 . "Request Entity Too Large")
(414 . "Request-URI Too Large")
(415 . "Unsupported Media Type")
(500 . "Internal Server Error")
(501 . "Not Implemented")
(502 . "Bad Gateway")
(503 . "Service Unavailable")
(504 . "Gateway Time-out")
(505 . "HTTP Version not supported"))
size: 37
hash: number-hash
test: =))
(define (header-name->string name)
(let ((upper-next #t))
(string-map!
(lambda (ch)
(if upper-next
(begin
(set! upper-next #f)
(char-upcase ch))
(begin
(when (char=? ch #\-)
(set! upper-next #t))
ch)))
(symbol->string name))))
(define (code->reason code)
(hash-table-ref/default status-codes code "unknown"))
(define (prepare-response-head res)
(let ((reason (or (response-reason res)
(code->reason (response-code res))))
(headers (response-headers res)))
(if (not (any (lambda (h)
(or (eq? 'content-length h)
(eq? 'transfer-encoding h)))
headers))
(update-response res
reason: reason
headers: (cons '(transfer-encoding . "chunked") headers))
(update-response res reason: reason))))
(define (response-write-head res)
(let ((res (prepare-response-head res)))
(with-output-to-port (response-port res)
(lambda ()
(let* ((code (response-code res))
(reason (response-reason res)))
(write-string
(string-append/shared
"HTTP/1.1 " (number->string code) " " reason "\r\n"))
(for-each (lambda (h)
(write-string
(string-append/shared
(header-name->string (car h)) ": " (cdr h) "\r\n")))
(response-headers res))
(write-string "\r\n")
res)))))
(define (write-chunk str port)
(write-string
(string-append/shared
(number->string (string-length str) 16) "\r\n"
str "\r\n")
#f
port))
(define (make-chunked-body-port port)
(make-output-port
(lambda (str) (write-chunk str port))
(lambda () (write-string "0\r\n\r\n" #f port))
(lambda () (flush-output port))))
(define (make-body-port port len)
(if (not len)
(make-chunked-body-port port)
(make-output-port
(lambda (str) (write-string str #f port))
(lambda () #f)
(lambda () (flush-output port)))))
(define (response-write-body res)
(let ((body (response-body res))
(len (response-headers-ref res 'content-length))
(port (response-port res)))
(cond
((procedure? body)
(let ((out (make-body-port port len)))
(body out)
(close-output-port out)))
((not len)
(when body (write-chunk body port))
(write-string "0\r\n\r\n" #f port))
(else
(when body (write-string body #f port))))
res))
(define (response-write res)
(response-write-body (response-write-head res)))
(define (merge-headers new old #!optional (pending new))
(if (null? old)
new
(let* ((x (car old))
(k (car x)))
(if (assq k pending)
(merge-headers new (cdr old) pending)
(merge-headers (cons x new) (cdr old) pending)))))
(define (response-headers-merge res new)
(let ((old (response-headers res)))
(update-response res headers: (merge-headers new old))))
(define (response-headers-ref res name)
(alist-ref name (response-headers res)))