Skip to content

Commit 3ff27d4

Browse files
authoredDec 31, 2023
Add httpx: low memory, non-allocating http implementation (#13)
* add httpx * add more logic and tests * add RequestHeader type * refactor tests: incorporate TCP buffer size into tests * further reduce heap allocations * finish segmenting RequestHeader and ResponseHeader type logic
1 parent 2083131 commit 3ff27d4

14 files changed

+3012
-50
lines changed
 

‎httpx/args.go

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package httpx
2+
3+
import "unsafe"
4+
5+
const (
6+
argsNoValue = true
7+
argsHasValue = false
8+
)
9+
10+
type argsKV struct {
11+
key []byte
12+
value []byte
13+
noValue bool
14+
}
15+
16+
func visitArgs(args []argsKV, f func(k, v []byte)) {
17+
for i, n := 0, len(args); i < n; i++ {
18+
kv := &args[i]
19+
f(kv.key, kv.value)
20+
}
21+
}
22+
23+
func visitArgsKey(args []argsKV, f func(k []byte)) {
24+
for i, n := 0, len(args); i < n; i++ {
25+
kv := &args[i]
26+
f(kv.key)
27+
}
28+
}
29+
30+
func copyArgs(dst, src []argsKV) []argsKV {
31+
if cap(dst) < len(src) {
32+
tmp := make([]argsKV, len(src))
33+
dstLen := len(dst)
34+
dst = dst[:cap(dst)] // copy all of dst.
35+
copy(tmp, dst)
36+
for i := dstLen; i < len(tmp); i++ {
37+
// Make sure nothing is nil.
38+
tmp[i].key = []byte{}
39+
tmp[i].value = []byte{}
40+
}
41+
dst = tmp
42+
}
43+
n := len(src)
44+
dst = dst[:n]
45+
for i := 0; i < n; i++ {
46+
dstKV := &dst[i]
47+
srcKV := &src[i]
48+
dstKV.key = append(dstKV.key[:0], srcKV.key...)
49+
if srcKV.noValue {
50+
dstKV.value = dstKV.value[:0]
51+
} else {
52+
dstKV.value = append(dstKV.value[:0], srcKV.value...)
53+
}
54+
dstKV.noValue = srcKV.noValue
55+
}
56+
return dst
57+
}
58+
59+
func delAllArgs(args []argsKV, key string) []argsKV {
60+
for i, n := 0, len(args); i < n; i++ {
61+
kv := &args[i]
62+
if key == b2s(kv.key) {
63+
tmp := *kv
64+
copy(args[i:], args[i+1:])
65+
n--
66+
i--
67+
args[n] = tmp
68+
args = args[:n]
69+
}
70+
}
71+
return args
72+
}
73+
74+
func setArg(h []argsKV, key, value string, noValue bool) []argsKV {
75+
n := len(h)
76+
for i := 0; i < n; i++ {
77+
kv := &h[i]
78+
if key == b2s(kv.key) {
79+
if noValue {
80+
kv.value = kv.value[:0]
81+
} else {
82+
kv.value = append(kv.value[:0], value...)
83+
}
84+
kv.noValue = noValue
85+
return h
86+
}
87+
}
88+
return appendArg(h, key, value, noValue)
89+
}
90+
91+
func appendArg(args []argsKV, key, value string, noValue bool) []argsKV {
92+
var kv *argsKV
93+
args, kv = allocArg(args)
94+
kv.key = append(kv.key[:0], key...)
95+
if noValue {
96+
kv.value = kv.value[:0]
97+
} else {
98+
kv.value = append(kv.value[:0], value...)
99+
}
100+
kv.noValue = noValue
101+
return args
102+
}
103+
104+
func allocArg(h []argsKV) ([]argsKV, *argsKV) {
105+
n := len(h)
106+
if cap(h) > n {
107+
h = h[:n+1]
108+
} else {
109+
h = append(h, argsKV{
110+
value: []byte{},
111+
})
112+
}
113+
return h, &h[n]
114+
}
115+
116+
func releaseArg(h []argsKV) []argsKV {
117+
return h[:len(h)-1]
118+
}
119+
120+
func hasArg(h []argsKV, key string) bool {
121+
for i, n := 0, len(h); i < n; i++ {
122+
kv := &h[i]
123+
if key == b2s(kv.key) {
124+
return true
125+
}
126+
}
127+
return false
128+
}
129+
130+
func peekArgStr(h []argsKV, k string) []byte {
131+
for i, n := 0, len(h); i < n; i++ {
132+
kv := &h[i]
133+
if b2s(kv.key) == k {
134+
return kv.value
135+
}
136+
}
137+
return nil
138+
}
139+
140+
// b2s converts byte slice to a string without memory allocation.
141+
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
142+
func b2s(b []byte) string {
143+
return unsafe.String(unsafe.SliceData(b), len(b))
144+
}
145+
146+
// s2b converts string to a byte slice without memory allocation.
147+
func s2b(s string) []byte {
148+
return unsafe.Slice(unsafe.StringData(s), len(s))
149+
}

0 commit comments

Comments
 (0)