diff --git a/vlib/veb/consts.v b/vlib/veb/consts.v new file mode 100644 index 0000000000..5b002a53a5 --- /dev/null +++ b/vlib/veb/consts.v @@ -0,0 +1,149 @@ +module veb + +import net.http + +// max read and write limits in bytes +const max_read = 8096 +const max_write = 8096 * 2 + +pub const max_http_post_size = 1024 * 1024 +pub const default_port = 8080 +pub const methods_with_form = [http.Method.post, .put, .patch] + +pub const headers_close = http.new_custom_header_from_map({ + 'Server': 'veb' +}) or { panic('should never fail') } + +pub const http_302 = http.new_response( + status: .found + body: '302 Found' + header: headers_close +) + +pub const http_400 = http.new_response( + status: .bad_request + body: '400 Bad Request' + header: http.new_header( + key: .content_type + value: 'text/plain' + ).join(headers_close) +) + +pub const http_404 = http.new_response( + status: .not_found + body: '404 Not Found' + header: http.new_header( + key: .content_type + value: 'text/plain' + ).join(headers_close) +) + +pub const http_408 = http.new_response( + status: .request_timeout + body: '408 Request Timeout' + header: http.new_header( + key: .content_type + value: 'text/plain' + ).join(headers_close) +) + +pub const http_413 = http.new_response( + status: .request_entity_too_large + body: '413 Request entity is too large' + header: http.new_header( + key: .content_type + value: 'text/plain' + ).join(headers_close) +) + +pub const http_500 = http.new_response( + status: .internal_server_error + body: '500 Internal Server Error' + header: http.new_header( + key: .content_type + value: 'text/plain' + ).join(headers_close) +) + +pub const mime_types = { + '.aac': 'audio/aac' + '.abw': 'application/x-abiword' + '.arc': 'application/x-freearc' + '.avi': 'video/x-msvideo' + '.azw': 'application/vnd.amazon.ebook' + '.bin': 'application/octet-stream' + '.bmp': 'image/bmp' + '.bz': 'application/x-bzip' + '.bz2': 'application/x-bzip2' + '.cda': 'application/x-cdf' + '.csh': 'application/x-csh' + '.css': 'text/css' + '.csv': 'text/csv' + '.doc': 'application/msword' + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + '.eot': 'application/vnd.ms-fontobject' + '.epub': 'application/epub+zip' + '.gz': 'application/gzip' + '.gif': 'image/gif' + '.htm': 'text/html' + '.html': 'text/html' + '.ico': 'image/vnd.microsoft.icon' + '.ics': 'text/calendar' + '.jar': 'application/java-archive' + '.jpeg': 'image/jpeg' + '.jpg': 'image/jpeg' + '.js': 'text/javascript' + '.json': 'application/json' + '.jsonld': 'application/ld+json' + '.mid': 'audio/midi audio/x-midi' + '.midi': 'audio/midi audio/x-midi' + '.mjs': 'text/javascript' + '.mp3': 'audio/mpeg' + '.mp4': 'video/mp4' + '.mpeg': 'video/mpeg' + '.mpkg': 'application/vnd.apple.installer+xml' + '.odp': 'application/vnd.oasis.opendocument.presentation' + '.ods': 'application/vnd.oasis.opendocument.spreadsheet' + '.odt': 'application/vnd.oasis.opendocument.text' + '.oga': 'audio/ogg' + '.ogv': 'video/ogg' + '.ogx': 'application/ogg' + '.opus': 'audio/opus' + '.otf': 'font/otf' + '.png': 'image/png' + '.pdf': 'application/pdf' + '.php': 'application/x-httpd-php' + '.ppt': 'application/vnd.ms-powerpoint' + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation' + '.rar': 'application/vnd.rar' + '.rtf': 'application/rtf' + '.sh': 'application/x-sh' + '.svg': 'image/svg+xml' + '.swf': 'application/x-shockwave-flash' + '.tar': 'application/x-tar' + '.tif': 'image/tiff' + '.tiff': 'image/tiff' + '.ts': 'video/mp2t' + '.ttf': 'font/ttf' + '.txt': 'text/plain' + '.vsd': 'application/vnd.visio' + '.wasm': 'application/wasm' + '.wav': 'audio/wav' + '.weba': 'audio/webm' + '.webm': 'video/webm' + '.webp': 'image/webp' + '.woff': 'font/woff' + '.woff2': 'font/woff2' + '.xhtml': 'application/xhtml+xml' + '.xls': 'application/vnd.ms-excel' + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + '.xml': 'application/xml' + '.xul': 'application/vnd.mozilla.xul+xml' + '.zip': 'application/zip' + '.3gp': 'video/3gpp' + '.3g2': 'video/3gpp2' + '.7z': 'application/x-7z-compressed' + '.m3u8': 'application/vnd.apple.mpegurl' + '.vsh': 'text/x-vlang' + '.v': 'text/x-vlang' +} diff --git a/vlib/veb/veb.v b/vlib/veb/veb.v index c6bb5d2b64..41be14c2f7 100644 --- a/vlib/veb/veb.v +++ b/vlib/veb/veb.v @@ -9,10 +9,6 @@ import time import strings import picoev -// max read and write limits in bytes -const max_read = 8096 -const max_write = 8096 * 2 - // A type which doesn't get filtered inside templates pub type RawHtml = string @@ -26,150 +22,6 @@ pub fn no_result() Result { return Result{} } -pub const methods_with_form = [http.Method.post, .put, .patch] - -pub const headers_close = http.new_custom_header_from_map({ - 'Server': 'veb' -}) or { panic('should never fail') } - -pub const http_302 = http.new_response( - status: .found - body: '302 Found' - header: headers_close -) - -pub const http_400 = http.new_response( - status: .bad_request - body: '400 Bad Request' - header: http.new_header( - key: .content_type - value: 'text/plain' - ).join(headers_close) -) - -pub const http_404 = http.new_response( - status: .not_found - body: '404 Not Found' - header: http.new_header( - key: .content_type - value: 'text/plain' - ).join(headers_close) -) - -pub const http_408 = http.new_response( - status: .request_timeout - body: '408 Request Timeout' - header: http.new_header( - key: .content_type - value: 'text/plain' - ).join(headers_close) -) - -pub const http_413 = http.new_response( - status: .request_entity_too_large - body: '413 Request entity is too large' - header: http.new_header( - key: .content_type - value: 'text/plain' - ).join(headers_close) -) - -pub const http_500 = http.new_response( - status: .internal_server_error - body: '500 Internal Server Error' - header: http.new_header( - key: .content_type - value: 'text/plain' - ).join(headers_close) -) - -pub const mime_types = { - '.aac': 'audio/aac' - '.abw': 'application/x-abiword' - '.arc': 'application/x-freearc' - '.avi': 'video/x-msvideo' - '.azw': 'application/vnd.amazon.ebook' - '.bin': 'application/octet-stream' - '.bmp': 'image/bmp' - '.bz': 'application/x-bzip' - '.bz2': 'application/x-bzip2' - '.cda': 'application/x-cdf' - '.csh': 'application/x-csh' - '.css': 'text/css' - '.csv': 'text/csv' - '.doc': 'application/msword' - '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - '.eot': 'application/vnd.ms-fontobject' - '.epub': 'application/epub+zip' - '.gz': 'application/gzip' - '.gif': 'image/gif' - '.htm': 'text/html' - '.html': 'text/html' - '.ico': 'image/vnd.microsoft.icon' - '.ics': 'text/calendar' - '.jar': 'application/java-archive' - '.jpeg': 'image/jpeg' - '.jpg': 'image/jpeg' - '.js': 'text/javascript' - '.json': 'application/json' - '.jsonld': 'application/ld+json' - '.mid': 'audio/midi audio/x-midi' - '.midi': 'audio/midi audio/x-midi' - '.mjs': 'text/javascript' - '.mp3': 'audio/mpeg' - '.mp4': 'video/mp4' - '.mpeg': 'video/mpeg' - '.mpkg': 'application/vnd.apple.installer+xml' - '.odp': 'application/vnd.oasis.opendocument.presentation' - '.ods': 'application/vnd.oasis.opendocument.spreadsheet' - '.odt': 'application/vnd.oasis.opendocument.text' - '.oga': 'audio/ogg' - '.ogv': 'video/ogg' - '.ogx': 'application/ogg' - '.opus': 'audio/opus' - '.otf': 'font/otf' - '.png': 'image/png' - '.pdf': 'application/pdf' - '.php': 'application/x-httpd-php' - '.ppt': 'application/vnd.ms-powerpoint' - '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation' - '.rar': 'application/vnd.rar' - '.rtf': 'application/rtf' - '.sh': 'application/x-sh' - '.svg': 'image/svg+xml' - '.swf': 'application/x-shockwave-flash' - '.tar': 'application/x-tar' - '.tif': 'image/tiff' - '.tiff': 'image/tiff' - '.ts': 'video/mp2t' - '.ttf': 'font/ttf' - '.txt': 'text/plain' - '.vsd': 'application/vnd.visio' - '.wasm': 'application/wasm' - '.wav': 'audio/wav' - '.weba': 'audio/webm' - '.webm': 'video/webm' - '.webp': 'image/webp' - '.woff': 'font/woff' - '.woff2': 'font/woff2' - '.xhtml': 'application/xhtml+xml' - '.xls': 'application/vnd.ms-excel' - '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - '.xml': 'application/xml' - '.xul': 'application/vnd.mozilla.xul+xml' - '.zip': 'application/zip' - '.3gp': 'video/3gpp' - '.3g2': 'video/3gpp2' - '.7z': 'application/x-7z-compressed' - '.m3u8': 'application/vnd.apple.mpegurl' - '.vsh': 'text/x-vlang' - '.v': 'text/x-vlang' -} - -pub const max_http_post_size = 1024 * 1024 - -pub const default_port = 8080 - struct Route { methods []http.Method path string @@ -312,9 +164,11 @@ pub fn run_at[A, X](mut global_app A, params RunParams) ! { pico_context.idx = []int{len: picoev.max_fds} // reserve space for read and write buffers - pico_context.buf = unsafe { malloc_noscan(picoev.max_fds * veb.max_read + 1) } + pico_context.buf = unsafe { malloc_noscan(picoev.max_fds * max_read + 1) } defer { - unsafe { free(pico_context.buf) } + unsafe { + free(pico_context.buf) + } } pico_context.incomplete_requests = []http.Request{len: picoev.max_fds} pico_context.file_responses = []FileResponse{len: picoev.max_fds} @@ -383,7 +237,7 @@ fn handle_timeout(mut pv picoev.Picoev, mut params RequestParams, fd int) { is_blocking: false } - fast_send_resp(mut conn, veb.http_408) or {} + fast_send_resp(mut conn, http_408) or {} pv.close_conn(fd) params.request_done(fd) @@ -398,8 +252,8 @@ fn handle_write_file(mut pv picoev.Picoev, mut params RequestParams, fd int) { bytes_written := sendfile(fd, params.file_responses[fd].file.fd, bytes_to_write) params.file_responses[fd].pos += bytes_written } $else { - if bytes_to_write > veb.max_write { - bytes_to_write = veb.max_write + if bytes_to_write > max_write { + bytes_to_write = max_write } data := unsafe { malloc(bytes_to_write) } @@ -439,8 +293,8 @@ fn handle_write_file(mut pv picoev.Picoev, mut params RequestParams, fd int) { fn handle_write_string(mut pv picoev.Picoev, mut params RequestParams, fd int) { mut bytes_to_write := int(params.string_responses[fd].str.len - params.string_responses[fd].pos) - if bytes_to_write > veb.max_write { - bytes_to_write = veb.max_write + if bytes_to_write > max_write { + bytes_to_write = max_write } mut conn := &net.TcpConn{ @@ -480,7 +334,7 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { } // cap the max_read to 8KB - mut reader := io.new_buffered_reader(reader: conn, cap: veb.max_read) + mut reader := io.new_buffered_reader(reader: conn, cap: max_read) defer { unsafe { reader.free() @@ -509,11 +363,11 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { params.incomplete_requests[fd] = http.Request{} return } - if reader.total_read >= veb.max_read { + if reader.total_read >= max_read { // throw an error when the request header is larger than 8KB // same limit that apache handles eprintln('[veb] error parsing request: too large') - fast_send_resp(mut conn, veb.http_413) or {} + fast_send_resp(mut conn, http_413) or {} pv.close_conn(fd) params.incomplete_requests[fd] = http.Request{} @@ -524,16 +378,16 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { // check if the request has a body content_length := req.header.get(.content_length) or { '0' } if content_length.int() > 0 { - mut max_bytes_to_read := veb.max_read - reader.total_read + mut max_bytes_to_read := max_read - reader.total_read mut bytes_to_read := content_length.int() - params.idx[fd] // cap the bytes to read to 8KB for the body, including the request headers if any - if bytes_to_read > veb.max_read - reader.total_read { - bytes_to_read = veb.max_read - reader.total_read + if bytes_to_read > max_read - reader.total_read { + bytes_to_read = max_read - reader.total_read } mut buf_ptr := params.buf unsafe { - buf_ptr += fd * veb.max_read // pointer magic + buf_ptr += fd * max_read // pointer magic } // convert to []u8 for BufferedReader mut buf := unsafe { buf_ptr.vbytes(max_bytes_to_read) } @@ -556,7 +410,7 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { header: http.new_header( key: .content_type value: 'text/plain' - ).join(veb.headers_close) + ).join(headers_close) )) or {} pv.close_conn(fd) @@ -601,7 +455,7 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { // small optimization: if the response is small write it immediately // the socket is most likely able to write all the data without blocking. // See Context.send_file for why we use max_read instead of max_write. - if completed_context.res.body.len < veb.max_read { + if completed_context.res.body.len < max_read { fast_send_resp(mut conn, completed_context.res) or {} handle_complete_request(completed_context.client_wants_to_close, mut pv, fd) @@ -614,7 +468,7 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { if res == -1 { // should not happen params.string_responses[fd].done() - fast_send_resp(mut conn, veb.http_500) or {} + fast_send_resp(mut conn, http_500) or {} handle_complete_request(completed_context.client_wants_to_close, mut pv, fd) return @@ -626,13 +480,13 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { .file { // save file information length := completed_context.res.header.get(.content_length) or { - fast_send_resp(mut conn, veb.http_500) or {} + fast_send_resp(mut conn, http_500) or {} return } params.file_responses[fd].total = length.i64() params.file_responses[fd].file = os.open(completed_context.return_file) or { // Context checks if the file is valid, so this should never happen - fast_send_resp(mut conn, veb.http_500) or {} + fast_send_resp(mut conn, http_500) or {} params.file_responses[fd].done() pv.close_conn(fd) return @@ -643,7 +497,7 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { // picoev error if res == -1 { // should not happen - fast_send_resp(mut conn, veb.http_500) or {} + fast_send_resp(mut conn, http_500) or {} params.file_responses[fd].done() pv.close_conn(fd) return @@ -689,7 +543,7 @@ fn handle_request[A, X](mut conn net.TcpConn, req http.Request, params &RequestP form, files := parse_form_from_request(req) or { // Bad request eprintln('[veb] error parsing form: ${err.msg()}') - conn.write(veb.http_400.bytes()) or {} + conn.write(http_400.bytes()) or {} return none } @@ -981,7 +835,7 @@ fn serve_if_static[A, X](app &A, mut user_context X, url urllib.URL, host string // StaticHandler ensures that the mime type exists on either the App or in veb ext := os.file_ext(static_file) - mut mime_type := app.static_mime_types[ext] or { veb.mime_types[ext] } + mut mime_type := app.static_mime_types[ext] or { mime_types[ext] } static_host := app.static_hosts[asked_path] or { '' } if static_file == '' || mime_type == '' {