net.http: add header_from_map and join functions (#10747)
parent
581280e6fc
commit
3039092b89
|
@ -353,22 +353,50 @@ pub fn new_header(kvs ...HeaderConfig) Header {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append a value to the header key.
|
// new_header_from_map creates a Header from key value pairs
|
||||||
|
pub fn new_header_from_map(kvs map[CommonHeader]string) Header {
|
||||||
|
mut h := new_header()
|
||||||
|
h.add_map(kvs)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// new_custom_header_from_map creates a Header from string key value pairs
|
||||||
|
pub fn new_custom_header_from_map(kvs map[string]string) ?Header {
|
||||||
|
mut h := new_header()
|
||||||
|
h.add_custom_map(kvs) ?
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// add appends a value to the header key.
|
||||||
pub fn (mut h Header) add(key CommonHeader, value string) {
|
pub fn (mut h Header) add(key CommonHeader, value string) {
|
||||||
k := key.str()
|
k := key.str()
|
||||||
h.data[k] << value
|
h.data[k] << value
|
||||||
h.add_key(k)
|
h.add_key(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append a value to a custom header key. This function will return an error
|
// add_custom appends a value to a custom header key. This function will
|
||||||
// if the key contains invalid header characters.
|
// return an error if the key contains invalid header characters.
|
||||||
pub fn (mut h Header) add_custom(key string, value string) ? {
|
pub fn (mut h Header) add_custom(key string, value string) ? {
|
||||||
is_valid(key) ?
|
is_valid(key) ?
|
||||||
h.data[key] << value
|
h.data[key] << value
|
||||||
h.add_key(key)
|
h.add_key(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the key-value pair. This function will clear any other values
|
// add_map appends the value for each header key.
|
||||||
|
pub fn (mut h Header) add_map(kvs map[CommonHeader]string) {
|
||||||
|
for k, v in kvs {
|
||||||
|
h.add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add_custom_map appends the value for each custom header key.
|
||||||
|
pub fn (mut h Header) add_custom_map(kvs map[string]string) ? {
|
||||||
|
for k, v in kvs {
|
||||||
|
h.add_custom(k, v) ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sets the key-value pair. This function will clear any other values
|
||||||
// that exist for the CommonHeader.
|
// that exist for the CommonHeader.
|
||||||
pub fn (mut h Header) set(key CommonHeader, value string) {
|
pub fn (mut h Header) set(key CommonHeader, value string) {
|
||||||
k := key.str()
|
k := key.str()
|
||||||
|
@ -376,21 +404,22 @@ pub fn (mut h Header) set(key CommonHeader, value string) {
|
||||||
h.add_key(k)
|
h.add_key(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the key-value pair for a custom header key. This function will
|
// set_custom sets the key-value pair for a custom header key. This
|
||||||
// clear any other values that exist for the header. This function will
|
// function will clear any other values that exist for the header. This
|
||||||
// return an error if the key contains invalid header characters.
|
// function will return an error if the key contains invalid header
|
||||||
|
// characters.
|
||||||
pub fn (mut h Header) set_custom(key string, value string) ? {
|
pub fn (mut h Header) set_custom(key string, value string) ? {
|
||||||
is_valid(key) ?
|
is_valid(key) ?
|
||||||
h.data[key] = [value]
|
h.data[key] = [value]
|
||||||
h.add_key(key)
|
h.add_key(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all values for a key.
|
// delete deletes all values for a key.
|
||||||
pub fn (mut h Header) delete(key CommonHeader) {
|
pub fn (mut h Header) delete(key CommonHeader) {
|
||||||
h.delete_custom(key.str())
|
h.delete_custom(key.str())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all values for a custom header key.
|
// delete_custom deletes all values for a custom header key.
|
||||||
pub fn (mut h Header) delete_custom(key string) {
|
pub fn (mut h Header) delete_custom(key string) {
|
||||||
h.data.delete(key)
|
h.data.delete(key)
|
||||||
|
|
||||||
|
@ -405,7 +434,8 @@ pub struct HeaderCoerceConfig {
|
||||||
canonicalize bool
|
canonicalize bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coerce data by joining keys that match case-insensitively into one entry
|
// coerce coerces data in the Header by joining keys that match
|
||||||
|
// case-insensitively into one entry.
|
||||||
pub fn (mut h Header) coerce(flags ...HeaderCoerceConfig) {
|
pub fn (mut h Header) coerce(flags ...HeaderCoerceConfig) {
|
||||||
canon := flags.any(it.canonicalize)
|
canon := flags.any(it.canonicalize)
|
||||||
|
|
||||||
|
@ -428,7 +458,7 @@ pub fn (mut h Header) coerce(flags ...HeaderCoerceConfig) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether the header key exists in the map.
|
// contains returns whether the header key exists in the map.
|
||||||
pub fn (h Header) contains(key CommonHeader) bool {
|
pub fn (h Header) contains(key CommonHeader) bool {
|
||||||
return h.contains_custom(key.str())
|
return h.contains_custom(key.str())
|
||||||
}
|
}
|
||||||
|
@ -437,7 +467,7 @@ pub struct HeaderQueryConfig {
|
||||||
exact bool
|
exact bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether the custom header key exists in the map.
|
// contains_custom returns whether the custom header key exists in the map.
|
||||||
pub fn (h Header) contains_custom(key string, flags ...HeaderQueryConfig) bool {
|
pub fn (h Header) contains_custom(key string, flags ...HeaderQueryConfig) bool {
|
||||||
if flags.any(it.exact) {
|
if flags.any(it.exact) {
|
||||||
return key in h.data
|
return key in h.data
|
||||||
|
@ -445,14 +475,14 @@ pub fn (h Header) contains_custom(key string, flags ...HeaderQueryConfig) bool {
|
||||||
return key.to_lower() in h.keys
|
return key.to_lower() in h.keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the first value for the CommonHeader, or none if the key does
|
// get gets the first value for the CommonHeader, or none if the key
|
||||||
// not exist.
|
// does not exist.
|
||||||
pub fn (h Header) get(key CommonHeader) ?string {
|
pub fn (h Header) get(key CommonHeader) ?string {
|
||||||
return h.get_custom(key.str())
|
return h.get_custom(key.str())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the first value for the custom header, or none if the key does
|
// get_custom gets the first value for the custom header, or none if
|
||||||
// not exist.
|
// the key does not exist.
|
||||||
pub fn (h Header) get_custom(key string, flags ...HeaderQueryConfig) ?string {
|
pub fn (h Header) get_custom(key string, flags ...HeaderQueryConfig) ?string {
|
||||||
mut data_key := key
|
mut data_key := key
|
||||||
if !flags.any(it.exact) {
|
if !flags.any(it.exact) {
|
||||||
|
@ -469,7 +499,8 @@ pub fn (h Header) get_custom(key string, flags ...HeaderQueryConfig) ?string {
|
||||||
return h.data[data_key][0]
|
return h.data[data_key][0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the first value of the header starting with key, or none if the key does not exist.
|
// starting_with gets the first header starting with key, or none if
|
||||||
|
// the key does not exist.
|
||||||
pub fn (h Header) starting_with(key string) ?string {
|
pub fn (h Header) starting_with(key string) ?string {
|
||||||
for k, _ in h.data {
|
for k, _ in h.data {
|
||||||
if k.starts_with(key) {
|
if k.starts_with(key) {
|
||||||
|
@ -479,12 +510,12 @@ pub fn (h Header) starting_with(key string) ?string {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets all values for the CommonHeader.
|
// values gets all values for the CommonHeader.
|
||||||
pub fn (h Header) values(key CommonHeader) []string {
|
pub fn (h Header) values(key CommonHeader) []string {
|
||||||
return h.custom_values(key.str())
|
return h.custom_values(key.str())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets all values for the custom header.
|
// custom_values gets all values for the custom header.
|
||||||
pub fn (h Header) custom_values(key string, flags ...HeaderQueryConfig) []string {
|
pub fn (h Header) custom_values(key string, flags ...HeaderQueryConfig) []string {
|
||||||
if flags.any(it.exact) {
|
if flags.any(it.exact) {
|
||||||
return h.data[key]
|
return h.data[key]
|
||||||
|
@ -497,7 +528,7 @@ pub fn (h Header) custom_values(key string, flags ...HeaderQueryConfig) []string
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets all header keys as strings
|
// keys gets all header keys as strings
|
||||||
pub fn (h Header) keys() []string {
|
pub fn (h Header) keys() []string {
|
||||||
return h.data.keys()
|
return h.data.keys()
|
||||||
}
|
}
|
||||||
|
@ -508,8 +539,8 @@ pub struct HeaderRenderConfig {
|
||||||
canonicalize bool
|
canonicalize bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renders the Header into a string for use in sending
|
// render renders the Header into a string for use in sending HTTP
|
||||||
// HTTP requests. All header lines will end in `\r\n`
|
// requests. All header lines will end in `\r\n`
|
||||||
[manualfree]
|
[manualfree]
|
||||||
pub fn (h Header) render(flags HeaderRenderConfig) string {
|
pub fn (h Header) render(flags HeaderRenderConfig) string {
|
||||||
// estimate ~48 bytes per header
|
// estimate ~48 bytes per header
|
||||||
|
@ -556,7 +587,24 @@ pub fn (h Header) render(flags HeaderRenderConfig) string {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// Canonicalize an HTTP header key
|
// join combines two Header structs into a new Header struct
|
||||||
|
pub fn (h Header) join(other Header) Header {
|
||||||
|
mut combined := Header{
|
||||||
|
data: h.data.clone()
|
||||||
|
keys: h.keys.clone()
|
||||||
|
}
|
||||||
|
for k in other.keys() {
|
||||||
|
for v in other.custom_values(k, exact: true) {
|
||||||
|
combined.add_custom(k, v) or {
|
||||||
|
// panic because this should never fail
|
||||||
|
panic('unexpected error: $err')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
// canonicalize canonicalizes an HTTP header key
|
||||||
// Common headers are determined by the common_header_map
|
// Common headers are determined by the common_header_map
|
||||||
// Custom headers are capitalized on the first letter and any letter after a '-'
|
// Custom headers are capitalized on the first letter and any letter after a '-'
|
||||||
// NOTE: Assumes sl is lowercase, since the caller usually already has the lowercase key
|
// NOTE: Assumes sl is lowercase, since the caller usually already has the lowercase key
|
||||||
|
@ -584,7 +632,7 @@ struct HeaderKeyError {
|
||||||
invalid_char byte
|
invalid_char byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the header token is valid
|
// is_valid checks if the header token contains all valid bytes
|
||||||
fn is_valid(header string) ? {
|
fn is_valid(header string) ? {
|
||||||
for _, c in header {
|
for _, c in header {
|
||||||
if int(c) >= 128 || !is_token(c) {
|
if int(c) >= 128 || !is_token(c) {
|
||||||
|
@ -598,7 +646,7 @@ fn is_valid(header string) ? {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the byte is valid for a header token
|
// is_token checks if the byte is valid for a header token
|
||||||
fn is_token(b byte) bool {
|
fn is_token(b byte) bool {
|
||||||
return match b {
|
return match b {
|
||||||
33, 35...39, 42, 43, 45, 46, 48...57, 65...90, 94...122, 124, 126 { true }
|
33, 35...39, 42, 43, 45, 46, 48...57, 65...90, 94...122, 124, 126 { true }
|
||||||
|
@ -606,8 +654,8 @@ fn is_token(b byte) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the headers string as seen in HTTP/1.1 requests
|
// str returns the headers string as seen in HTTP/1.1 requests.
|
||||||
// Key order is not guaranteed
|
// Key order is not guaranteed.
|
||||||
pub fn (h Header) str() string {
|
pub fn (h Header) str() string {
|
||||||
return h.render(version: .v1_1)
|
return h.render(version: .v1_1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,3 +274,52 @@ fn test_str() ? {
|
||||||
assert h.str() == 'Accept: text/html,image/jpeg\r\nX-custom: Hello\r\n'
|
assert h.str() == 'Accept: text/html,image/jpeg\r\nX-custom: Hello\r\n'
|
||||||
|| h.str() == 'X-custom: Hello\r\nAccept:text/html,image/jpeg\r\n'
|
|| h.str() == 'X-custom: Hello\r\nAccept:text/html,image/jpeg\r\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_header_from_map() ? {
|
||||||
|
h := new_header_from_map(map{
|
||||||
|
CommonHeader.accept: 'nothing'
|
||||||
|
CommonHeader.expires: 'yesterday'
|
||||||
|
})
|
||||||
|
assert h.contains(.accept)
|
||||||
|
assert h.contains(.expires)
|
||||||
|
assert h.get(.accept) or { '' } == 'nothing'
|
||||||
|
assert h.get(.expires) or { '' } == 'yesterday'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_custom_header_from_map() ? {
|
||||||
|
h := new_custom_header_from_map(map{
|
||||||
|
'Server': 'VWeb'
|
||||||
|
'foo': 'bar'
|
||||||
|
}) ?
|
||||||
|
assert h.contains_custom('server')
|
||||||
|
assert h.contains_custom('foo')
|
||||||
|
assert h.get_custom('server') or { '' } == 'VWeb'
|
||||||
|
assert h.get_custom('foo') or { '' } == 'bar'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_header_join() ? {
|
||||||
|
h1 := new_header_from_map(map{
|
||||||
|
CommonHeader.accept: 'nothing'
|
||||||
|
CommonHeader.expires: 'yesterday'
|
||||||
|
})
|
||||||
|
h2 := new_custom_header_from_map(map{
|
||||||
|
'Server': 'VWeb'
|
||||||
|
'foo': 'bar'
|
||||||
|
}) ?
|
||||||
|
h3 := h1.join(h2)
|
||||||
|
// h1 is unchanged
|
||||||
|
assert h1.contains(.accept)
|
||||||
|
assert h1.contains(.expires)
|
||||||
|
assert !h1.contains_custom('Server')
|
||||||
|
assert !h1.contains_custom('foo')
|
||||||
|
// h2 is unchanged
|
||||||
|
assert !h2.contains(.accept)
|
||||||
|
assert !h2.contains(.expires)
|
||||||
|
assert h2.contains_custom('Server')
|
||||||
|
assert h2.contains_custom('foo')
|
||||||
|
// h3 has all four headers
|
||||||
|
assert h3.contains(.accept)
|
||||||
|
assert h3.contains(.expires)
|
||||||
|
assert h3.contains_custom('Server')
|
||||||
|
assert h3.contains_custom('foo')
|
||||||
|
}
|
||||||
|
|
|
@ -13,56 +13,37 @@ import time
|
||||||
|
|
||||||
pub const (
|
pub const (
|
||||||
methods_with_form = [http.Method.post, .put, .patch]
|
methods_with_form = [http.Method.post, .put, .patch]
|
||||||
|
headers_close = http.new_custom_header_from_map(map{
|
||||||
|
'Server': 'VWeb'
|
||||||
|
http.CommonHeader.connection.str(): 'close'
|
||||||
|
}) or { panic('should never fail') }
|
||||||
|
|
||||||
http_400 = http.Response{
|
http_400 = http.Response{
|
||||||
version: .v1_1
|
version: .v1_1
|
||||||
status_code: 400
|
status_code: 400
|
||||||
text: '400 Bad Request'
|
text: '400 Bad Request'
|
||||||
header: fn () http.Header {
|
header: http.new_header_from_map(map{
|
||||||
mut h := http.new_header()
|
http.CommonHeader.content_type: 'text/plain'
|
||||||
h.add(.content_type, 'text/plain')
|
http.CommonHeader.content_length: '15'
|
||||||
h.add(.content_length, '15')
|
}).join(headers_close)
|
||||||
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{
|
http_404 = http.Response{
|
||||||
version: .v1_1
|
version: .v1_1
|
||||||
status_code: 404
|
status_code: 404
|
||||||
text: '404 Not Found'
|
text: '404 Not Found'
|
||||||
header: fn () http.Header {
|
header: http.new_header_from_map(map{
|
||||||
mut h := http.new_header()
|
http.CommonHeader.content_type: 'text/plain'
|
||||||
h.add(.content_type, 'text/plain')
|
http.CommonHeader.content_length: '13'
|
||||||
h.add(.content_length, '13')
|
}).join(headers_close)
|
||||||
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{
|
http_500 = http.Response{
|
||||||
version: .v1_1
|
version: .v1_1
|
||||||
status_code: 500
|
status_code: 500
|
||||||
text: '500 Internal Server Error'
|
text: '500 Internal Server Error'
|
||||||
header: fn () http.Header {
|
header: http.new_header_from_map(map{
|
||||||
mut h := http.new_header()
|
http.CommonHeader.content_type: 'text/plain'
|
||||||
h.add(.content_type, 'text/plain')
|
http.CommonHeader.content_length: '25'
|
||||||
h.add(.content_length, '25')
|
}).join(headers_close)
|
||||||
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{
|
mime_types = map{
|
||||||
'.css': 'text/css; charset=utf-8'
|
'.css': 'text/css; charset=utf-8'
|
||||||
|
@ -161,7 +142,7 @@ pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bo
|
||||||
}
|
}
|
||||||
sb.write_string(ctx.headers)
|
sb.write_string(ctx.headers)
|
||||||
sb.write_string('\r\n')
|
sb.write_string('\r\n')
|
||||||
sb.write_string(headers_close().str())
|
sb.write_string(vweb.headers_close.str())
|
||||||
sb.write_string('\r\n')
|
sb.write_string('\r\n')
|
||||||
if ctx.chunked_transfer {
|
if ctx.chunked_transfer {
|
||||||
mut i := 0
|
mut i := 0
|
||||||
|
@ -236,7 +217,7 @@ pub fn (mut ctx Context) redirect(url string) Result {
|
||||||
return Result{}
|
return Result{}
|
||||||
}
|
}
|
||||||
ctx.done = true
|
ctx.done = true
|
||||||
send_string(mut ctx.conn, 'HTTP/1.1 302 Found\r\nLocation: $url$ctx.headers\r\n$headers_close().str()\r\n') or {
|
send_string(mut ctx.conn, 'HTTP/1.1 302 Found\r\nLocation: $url$ctx.headers\r\n$vweb.headers_close\r\n') or {
|
||||||
return Result{}
|
return Result{}
|
||||||
}
|
}
|
||||||
return Result{}
|
return Result{}
|
||||||
|
@ -707,9 +688,3 @@ pub type RawHtml = string
|
||||||
fn send_string(mut conn net.TcpConn, s string) ? {
|
fn send_string(mut conn net.TcpConn, s string) ? {
|
||||||
conn.write(s.bytes()) ?
|
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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue