net,vweb: reduce allocations by ~80%

This commit is contained in:
Alexander Medvednikov 2023-11-11 01:10:23 +03:00
parent b3a9701129
commit e7cad4f55d
12 changed files with 464 additions and 91 deletions

View file

@ -4,14 +4,24 @@
module http
import strings
import arrays
struct HeaderKV {
key string
value string
}
pub const max_headers = 50
// Header represents the key-value pairs in an HTTP header
pub struct Header {
mut:
data map[string][]string
// data map[string][]string
data [max_headers]HeaderKV
cur_pos int
// map of lowercase header keys to their original keys
// in order of appearance
keys map[string][]string
// keys map[string][]string
}
// CommonHeader is an enum of the most common HTTP headers
@ -333,8 +343,8 @@ const common_header_map = {
pub fn (mut h Header) free() {
unsafe {
h.data.free()
h.keys.free()
// h.data.free()
// h.keys.free()
}
}
@ -346,11 +356,13 @@ pub struct HeaderConfig {
// Create a new Header object
pub fn new_header(kvs ...HeaderConfig) Header {
mut h := Header{
data: map[string][]string{}
// data: map[string][]string{}
}
for kv in kvs {
h.add(kv.key, kv.value)
for i, kv in kvs {
h.data[i] = HeaderKV{kv.key.str(), kv.value}
// h.add(kv.key, kv.value)
}
h.cur_pos = kvs.len
return h
}
@ -371,16 +383,20 @@ pub fn new_custom_header_from_map(kvs map[string]string) !Header {
// add appends a value to the header key.
pub fn (mut h Header) add(key CommonHeader, value string) {
k := key.str()
h.data[k] << value
h.add_key(k)
// h.data[k] << value
h.data[h.cur_pos] = HeaderKV{k, value}
h.cur_pos++
// h.add_key(k)
}
// add_custom appends a value to a custom header key. This function will
// return an error if the key contains invalid header characters.
pub fn (mut h Header) add_custom(key string, value string) ! {
is_valid(key)!
h.data[key] << value
h.add_key(key)
// h.data[key] << value
h.data[h.cur_pos] = HeaderKV{key, value}
h.cur_pos++
// h.add_key(key)
}
// add_map appends the value for each header key.
@ -400,9 +416,21 @@ pub fn (mut h Header) add_custom_map(kvs map[string]string) ! {
// set sets the key-value pair. This function will clear any other values
// that exist for the CommonHeader.
pub fn (mut h Header) set(key CommonHeader, value string) {
k := key.str()
h.data[k] = [value]
h.add_key(k)
key_str := key.str()
// for i, kv in h.data {
for i := 0; i < h.cur_pos; i++ {
if h.data[i].key == key_str {
h.data[i] = HeaderKV{key_str, value}
return
}
}
// Not updated, add a new one
h.data[h.cur_pos] = HeaderKV{key_str, value}
h.cur_pos++
// h.data[k] = [value]
// h.add_key(k)
}
// set_custom sets the key-value pair for a custom header key. This
@ -411,8 +439,27 @@ pub fn (mut h Header) set(key CommonHeader, value string) {
// characters.
pub fn (mut h Header) set_custom(key string, value string) ! {
is_valid(key)!
h.data[key] = [value]
h.add_key(key)
mut set := false
for i, kv in h.data {
if kv.key == key {
if !set {
h.data[i] = HeaderKV{key, value}
set = true
} else {
// Remove old duplicates
h.data[i] = HeaderKV{key, ''}
}
// return
}
}
if set {
return
}
// Not updated, add a new one
h.data[h.cur_pos] = HeaderKV{key, value}
h.cur_pos++
// h.data[key] = [value]
// h.add_key(key)
}
// delete deletes all values for a key.
@ -422,13 +469,20 @@ pub fn (mut h Header) delete(key CommonHeader) {
// delete_custom deletes all values for a custom header key.
pub fn (mut h Header) delete_custom(key string) {
h.data.delete(key)
for i, kv in h.data {
if kv.key == key {
h.data[i] = HeaderKV{key, ''}
}
}
// h.data.delete(key)
// remove key from keys metadata
/*
kl := key.to_lower()
if kl in h.keys {
h.keys[kl] = h.keys[kl].filter(it != key)
}
*/
}
[params]
@ -438,8 +492,26 @@ pub struct HeaderCoerceConfig {
// coerce coerces data in the Header by joining keys that match
// case-insensitively into one entry.
//[deprecated: 'no need to call this method anymore, keys are automatically coerced']
pub fn (mut h Header) coerce(flags HeaderCoerceConfig) {
for kl, data_keys in h.keys {
keys := h.keys()
// for k in keys {
// println('${k} => ${h.get_custom(k, exact: true) or { continue }}')
//}
new_keys := arrays.distinct(h.keys().map(it.to_lower()))
if keys.len == new_keys.len {
return
}
mut new_data := [http.max_headers]HeaderKV{}
mut i := 0
for _, key in new_keys {
for _, old_key in keys {
if old_key.to_lower() == key {
new_data[i] = HeaderKV{key, h.get_custom(old_key, exact: true) or { continue }}
i++
}
}
/*
master_key := if flags.canonicalize { canonicalize(kl) } else { data_keys[0] }
// save master data
@ -455,12 +527,25 @@ pub fn (mut h Header) coerce(flags HeaderCoerceConfig) {
h.data.delete(key)
}
h.keys[kl] = [master_key]
*/
}
h.data = new_data
h.cur_pos = i
}
// contains returns whether the header key exists in the map.
pub fn (h Header) contains(key CommonHeader) bool {
return h.contains_custom(key.str())
if h.cur_pos == 0 {
return false
}
key_str := key.str()
for i := 0; i < h.cur_pos; i++ {
if h.data[i].key == key_str {
return true
}
}
return false
// return h.contains_custom(key.str())
}
[params]
@ -471,9 +556,23 @@ pub struct HeaderQueryConfig {
// contains_custom returns whether the custom header key exists in the map.
pub fn (h Header) contains_custom(key string, flags HeaderQueryConfig) bool {
if flags.exact {
return key in h.data
for i := 0; i < h.cur_pos; i++ {
kv := h.data[i]
if kv.key == key {
return true
}
}
return false
} else {
lower_key := key.to_lower()
for i := 0; i < h.cur_pos; i++ {
kv := h.data[i]
if kv.key.to_lower() == lower_key {
return true
}
}
return false
}
return key.to_lower() in h.keys
}
// get gets the first value for the CommonHeader, or none if the key
@ -485,27 +584,34 @@ pub fn (h Header) get(key CommonHeader) !string {
// get_custom gets the first value for the custom header, or none if
// the key does not exist.
pub fn (h Header) get_custom(key string, flags HeaderQueryConfig) !string {
mut data_key := key
if !flags.exact {
// get the first key from key metadata
k := key.to_lower()
if h.keys[k].len == 0 {
return error('none')
if flags.exact {
for i := 0; i < h.cur_pos; i++ {
// for kv in h.data {
kv := h.data[i]
// println('${kv.key} => ${kv.value}')
if kv.key == key {
return kv.value
}
}
} else {
lower_key := key.to_lower()
// for kv in h.data {
for i := 0; i < h.cur_pos; i++ {
kv := h.data[i]
if kv.key.to_lower() == lower_key {
return kv.value
}
}
data_key = h.keys[k][0]
}
if h.data[data_key].len == 0 {
return error('none')
}
return h.data[data_key][0]
return error('none')
}
// 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 {
for k, _ in h.data {
if k.starts_with(key) {
return k
for _, kv in h.data {
if kv.key.starts_with(key) {
return kv.key
}
}
return error('none')
@ -518,20 +624,41 @@ pub fn (h Header) values(key CommonHeader) []string {
// custom_values gets all values for the custom header.
pub fn (h Header) custom_values(key string, flags HeaderQueryConfig) []string {
if h.cur_pos == 0 {
return []
}
mut res := []string{cap: 2}
if flags.exact {
return h.data[key]
for i := 0; i < h.cur_pos; i++ {
kv := h.data[i]
if kv.key == key && kv.value != '' { // empty value means a deleted header
res << kv.value
}
}
return res
} else {
lower_key := key.to_lower()
for i := 0; i < h.cur_pos; i++ {
kv := h.data[i]
if kv.key.to_lower() == lower_key && kv.value != '' { // empty value means a deleted header
res << kv.value
}
}
return res
}
// case insensitive lookup
mut values := []string{cap: 10}
for k in h.keys[key.to_lower()] {
values << h.data[k]
}
return values
}
// keys gets all header keys as strings
pub fn (h Header) keys() []string {
return h.data.keys()
mut res := []string{cap: h.cur_pos}
for i := 0; i < h.cur_pos; i++ {
if h.data[i].value == '' {
continue
}
res << h.data[i].key
}
// Make sure keys are lower case and unique
return arrays.uniq(res)
}
[params]
@ -547,6 +674,16 @@ pub struct HeaderRenderConfig {
pub fn (h Header) render(flags HeaderRenderConfig) string {
// estimate ~48 bytes per header
mut sb := strings.new_builder(h.data.len * 48)
h.render_into_sb(mut sb, flags)
res := sb.str()
unsafe { sb.free() }
return res
}
// render_into_sb works like render, but uses a preallocated string builder instead.
// This method should be used only for performance critical applications.
pub fn (h Header) render_into_sb(mut sb strings.Builder, flags HeaderRenderConfig) {
/*
if flags.coerce {
for kl, data_keys in h.keys {
key := if flags.version == .v2_0 {
@ -566,32 +703,33 @@ pub fn (h Header) render(flags HeaderRenderConfig) string {
}
}
} else {
for k, vs in h.data {
key := if flags.version == .v2_0 {
k.to_lower()
} else if flags.canonicalize {
canonicalize(k.to_lower())
} else {
k
}
for v in vs {
sb.write_string(key)
sb.write_string(': ')
sb.write_string(v)
sb.write_string('\r\n')
}
*/
// for _, kv in h.data {
for i := 0; i < h.cur_pos; i++ {
kv := h.data[i]
key := if flags.version == .v2_0 {
kv.key.to_lower()
} else if flags.canonicalize {
canonicalize(kv.key.to_lower())
} else {
kv.key
}
// XTODO handle []string ? or doesn't matter?
// for v in vs {
sb.write_string(key)
sb.write_string(': ')
sb.write_string(kv.value)
sb.write_string('\r\n')
//}
}
res := sb.str()
unsafe { sb.free() }
return res
//}
}
// 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()
data: h.data // h.data.clone()
cur_pos: h.cur_pos
}
for k in other.keys() {
for v in other.custom_values(k, exact: true) {
@ -608,21 +746,23 @@ pub fn (h Header) join(other Header) Header {
// Common headers are determined by the common_header_map
// 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
fn canonicalize(sl string) string {
fn canonicalize(name string) string {
// check if we have a common header
if sl in http.common_header_map {
return http.common_header_map[sl].str()
if name in http.common_header_map {
return http.common_header_map[name].str()
}
return sl.split('-').map(it.capitalize()).join('-')
return name.split('-').map(it.capitalize()).join('-')
}
// Helper function to add a key to the keys map
/*
fn (mut h Header) add_key(key string) {
kl := key.to_lower()
if !h.keys[kl].contains(key) {
h.keys[kl] << key
}
}
*/
// Custom error struct for invalid header tokens
struct HeaderKeyError {
@ -704,3 +844,8 @@ fn parse_header(s string) !(string, string) {
// TODO: parse quoted text according to the RFC
return words[0], words[1].trim(' \t')
}
fn parse_header_fast(s string) !int {
pos := s.index(':') or { return error('missing colon in header') }
return pos
}