diff --git a/vlib/net/http/response.v b/vlib/net/http/response.v index 5f4b794165..31517827c8 100644 --- a/vlib/net/http/response.v +++ b/vlib/net/http/response.v @@ -22,10 +22,15 @@ fn (mut resp Response) free() { // Formats resp to bytes suitable for HTTP response transmission pub fn (resp Response) bytes() []byte { // TODO: build []byte directly; this uses two allocations + return resp.bytestr().bytes() +} + +// Formats resp to a string suitable for HTTP response transmission +pub fn (resp Response) bytestr() string { // TODO: cookies return ('$resp.version $resp.status_code ${status_from_int(resp.status_code).str()}\r\n' + '${resp.header.render( version: resp.version - )}\r\n' + '$resp.text').bytes() + )}\r\n' + '$resp.text') } // Parse a raw HTTP response into a Response object diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index a653cb25d9..293b1682f8 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -12,15 +12,59 @@ import strings import time pub const ( - methods_with_form = [http.Method.post, .put, .patch] - header_server = 'Server: VWeb\r\n' - header_connection_close = 'Connection: close\r\n' - headers_close = '$header_server$header_connection_close\r\n' - // TODO: use http.response structs - http_400 = 'HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n${headers_close}400 Bad Request' - http_404 = 'HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n${headers_close}404 Not Found' - http_500 = 'HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\n${headers_close}500 Internal Server Error' - mime_types = map{ + methods_with_form = [http.Method.post, .put, .patch] + http_400 = http.Response{ + version: .v1_1 + status_code: 400 + text: '400 Bad Request' + header: fn () http.Header { + mut h := http.new_header() + h.add(.content_type, 'text/plain') + h.add(.content_length, '15') + close := headers_close() + for k in close.keys() { + for v in close.custom_values(k) { + h.add_custom(k, v) or {} + } + } + return h + }() + } + http_404 = http.Response{ + version: .v1_1 + status_code: 404 + text: '404 Not Found' + header: fn () http.Header { + mut h := http.new_header() + h.add(.content_type, 'text/plain') + h.add(.content_length, '13') + close := headers_close() + for k in close.keys() { + for v in close.custom_values(k) { + h.add_custom(k, v) or {} + } + } + return h + }() + } + http_500 = http.Response{ + version: .v1_1 + status_code: 500 + text: '500 Internal Server Error' + header: fn () http.Header { + mut h := http.new_header() + h.add(.content_type, 'text/plain') + h.add(.content_length, '25') + close := headers_close() + for k in close.keys() { + for v in close.custom_values(k) { + h.add_custom(k, v) or {} + } + } + return h + }() + } + mime_types = map{ '.css': 'text/css; charset=utf-8' '.gif': 'image/gif' '.htm': 'text/html; charset=utf-8' @@ -117,7 +161,8 @@ pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bo } sb.write_string(ctx.headers) sb.write_string('\r\n') - sb.write_string(vweb.headers_close) + sb.write_string(headers_close().str()) + sb.write_string('\r\n') if ctx.chunked_transfer { mut i := 0 mut len := res.len @@ -181,7 +226,7 @@ pub fn (mut ctx Context) server_error(ecode int) Result { if ctx.done { return Result{} } - send_string(mut ctx.conn, vweb.http_500) or {} + send_string(mut ctx.conn, vweb.http_500.bytestr()) or {} return Result{} } @@ -191,7 +236,7 @@ pub fn (mut ctx Context) redirect(url string) Result { return Result{} } ctx.done = true - send_string(mut ctx.conn, 'HTTP/1.1 302 Found\r\nLocation: $url$ctx.headers\r\n$vweb.headers_close') or { + send_string(mut ctx.conn, 'HTTP/1.1 302 Found\r\nLocation: $url$ctx.headers\r\n$headers_close().str()\r\n') or { return Result{} } return Result{} @@ -203,7 +248,7 @@ pub fn (mut ctx Context) not_found() Result { return Result{} } ctx.done = true - send_string(mut ctx.conn, vweb.http_404) or {} + send_string(mut ctx.conn, vweb.http_404.bytestr()) or {} return Result{} } @@ -378,7 +423,7 @@ fn handle_conn(mut conn net.TcpConn, mut app T) { if 'multipart/form-data' in ct { boundary := ct.filter(it.starts_with('boundary=')) if boundary.len != 1 { - send_string(mut conn, vweb.http_400) or {} + send_string(mut conn, vweb.http_400.bytestr()) or {} return } form, files := parse_multipart_form(req.data, boundary[0][9..]) @@ -457,7 +502,7 @@ fn handle_conn(mut conn net.TcpConn, mut app T) { } } // site not found - send_string(mut conn, vweb.http_404) or {} + send_string(mut conn, vweb.http_404.bytestr()) or {} } fn route_matches(url_words []string, route_words []string) ?[]string { @@ -553,7 +598,7 @@ fn serve_if_static(mut app T, url urllib.URL) bool { return false } data := os.read_file(static_file) or { - send_string(mut app.conn, vweb.http_404) or {} + send_string(mut app.conn, vweb.http_404.bytestr()) or {} return true } app.send_response_to_client(mime_type, data) @@ -662,3 +707,9 @@ pub type RawHtml = string fn send_string(mut conn net.TcpConn, s string) ? { conn.write(s.bytes()) ? } + +fn headers_close() http.Header { + mut h := http.new_header(key: .connection, value: 'close') + h.add_custom('Server', 'VWeb') or {} + return h +}