mirror of https://github.com/antonmedv/fx
Merge eabbebbdb2
into 6793ff4a0e
commit
99bacb72a0
@ -0,0 +1,139 @@
|
||||
package http_response
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type HttpResponseHeader interface {
|
||||
Name() []byte
|
||||
Value() []byte
|
||||
NormalizedValue() []byte
|
||||
}
|
||||
|
||||
type HttpResponse interface {
|
||||
Status() []byte
|
||||
Headers() []HttpResponseHeader
|
||||
Body() []byte
|
||||
}
|
||||
|
||||
type httpResponseHeader struct {
|
||||
name []byte
|
||||
value []byte
|
||||
normalizedValue []byte
|
||||
}
|
||||
|
||||
func (h httpResponseHeader) Name() []byte {
|
||||
return h.name
|
||||
}
|
||||
|
||||
func (h httpResponseHeader) Value() []byte {
|
||||
return h.value
|
||||
}
|
||||
|
||||
func (h httpResponseHeader) NormalizedValue() []byte {
|
||||
if h.normalizedValue == nil {
|
||||
h.normalizedValue = normalizeHeaderValue(h.value)
|
||||
}
|
||||
return h.normalizedValue
|
||||
}
|
||||
|
||||
var normalizeValueRe *regexp.Regexp
|
||||
|
||||
func normalizeHeaderValue(value []byte) []byte {
|
||||
if normalizeValueRe == nil {
|
||||
str := `\s*\n\s+`
|
||||
normalizeValueRe = regexp.MustCompile(str)
|
||||
}
|
||||
|
||||
return normalizeValueRe.ReplaceAllLiteral(bytes.TrimSpace(value), []byte{' '})
|
||||
}
|
||||
|
||||
type FailedParsingHttpResponseError struct {
|
||||
error string
|
||||
}
|
||||
|
||||
func (e FailedParsingHttpResponseError) Error() string {
|
||||
return e.error
|
||||
}
|
||||
|
||||
var splitHttpResponseRe *regexp.Regexp
|
||||
|
||||
func splitHttpResponse(text []byte) (status []byte, headers []byte, body []byte, ok bool) {
|
||||
if splitHttpResponseRe == nil {
|
||||
str := `(?ms)\A(?P<status>(?:HTTP/\d\.\d \d{3} \w+|HTTP/2 [^\n]+))\n(?P<headers>.*?\n)\n(?P<body>.*)\z`
|
||||
splitHttpResponseRe = regexp.MustCompile(str)
|
||||
}
|
||||
|
||||
match := splitHttpResponseRe.FindSubmatch(text)
|
||||
if match == nil {
|
||||
return
|
||||
}
|
||||
|
||||
status = match[1]
|
||||
headers = match[2]
|
||||
body = match[3]
|
||||
ok = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var splitHeadersRe *regexp.Regexp
|
||||
|
||||
func splitHeaders(text []byte) (headers []HttpResponseHeader, ok bool) {
|
||||
if splitHeadersRe == nil {
|
||||
str := `(?m)\A(?:^(?P<field>[^:\s]+):\s+(?P<value>.*$\n(?:^\s+.*$\n)*))`
|
||||
splitHeadersRe = regexp.MustCompile(str)
|
||||
}
|
||||
|
||||
for p := 0; p < len(text); {
|
||||
match := splitHeadersRe.FindSubmatch(text[p:])
|
||||
if match == nil {
|
||||
return nil, false
|
||||
}
|
||||
name := match[1]
|
||||
value := bytes.TrimRight(match[2], "\n")
|
||||
headers = append(headers, httpResponseHeader{name, value, nil})
|
||||
p += len(match[0])
|
||||
}
|
||||
|
||||
ok = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func normalizeEols(text []byte) []byte {
|
||||
return bytes.ReplaceAll(text, []byte{'\r', '\n'}, []byte{'\n'})
|
||||
}
|
||||
|
||||
type httpResponse struct {
|
||||
status []byte
|
||||
headers []HttpResponseHeader
|
||||
body []byte
|
||||
}
|
||||
|
||||
func NewHttpResponseFromText(text []byte) (HttpResponse, error) {
|
||||
status, combinedHeaders, body, ok := splitHttpResponse(normalizeEols(text))
|
||||
if !ok {
|
||||
return nil, FailedParsingHttpResponseError{"failed extracting status, headers or body"}
|
||||
}
|
||||
|
||||
headers, ok := splitHeaders(combinedHeaders)
|
||||
if !ok {
|
||||
return nil, FailedParsingHttpResponseError{"failed parsing headers"}
|
||||
}
|
||||
|
||||
return httpResponse{status, headers, body}, nil
|
||||
}
|
||||
|
||||
func (r httpResponse) Status() []byte {
|
||||
return r.status
|
||||
}
|
||||
|
||||
func (r httpResponse) Headers() []HttpResponseHeader {
|
||||
return r.headers
|
||||
}
|
||||
|
||||
func (r httpResponse) Body() []byte {
|
||||
return r.body
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package http_response
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_normalizeHeaderValue(t *testing.T) {
|
||||
require.Equal(t, "a b", string(normalizeHeaderValue([]byte("a b"))))
|
||||
require.Equal(t, "a b", string(normalizeHeaderValue([]byte("a b "))))
|
||||
require.Equal(t, "a b", string(normalizeHeaderValue([]byte(" a b"))))
|
||||
require.Equal(t, "a b", string(normalizeHeaderValue([]byte(" a \n b "))))
|
||||
}
|
||||
|
||||
func Test_splitHttpResponse(t *testing.T) {
|
||||
_, _, _, ok := splitHttpResponse([]byte("whatever"))
|
||||
require.False(t, ok)
|
||||
|
||||
_, _, _, ok = splitHttpResponse([]byte("HTTP/1.1 200 OK\n\nbody"))
|
||||
require.False(t, ok)
|
||||
|
||||
status, header, body, ok := splitHttpResponse([]byte("HTTP/1.1 200 OK\nheader1: value\nheader2: value\n with\n continuation\n\nbody"))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "HTTP/1.1 200 OK", string(status))
|
||||
require.Equal(t, "header1: value\nheader2: value\n with\n continuation\n", string(header))
|
||||
require.Equal(t, "body", string(body))
|
||||
|
||||
status, header, body, ok = splitHttpResponse([]byte("HTTP/2 200 \ndate: Sun, 17 Mar 2024 21:58:03 GMT\n\n{}"))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "HTTP/2 200 ", string(status))
|
||||
require.Equal(t, "date: Sun, 17 Mar 2024 21:58:03 GMT\n", string(header))
|
||||
require.Equal(t, "{}", string(body))
|
||||
}
|
||||
|
||||
func Test_splitHeaders(t *testing.T) {
|
||||
_, ok := splitHeaders([]byte("whatever"))
|
||||
require.False(t, ok)
|
||||
|
||||
_, ok = splitHeaders([]byte("a: b\nwhatever\n"))
|
||||
require.False(t, ok)
|
||||
|
||||
res, ok := splitHeaders([]byte("a: b\n"))
|
||||
require.True(t, ok)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "a", string(res[0].Name()))
|
||||
require.Equal(t, "b", string(res[0].Value()))
|
||||
require.Equal(t, "b", string(res[0].NormalizedValue()))
|
||||
|
||||
res, ok = splitHeaders([]byte("a: b1\n b2\n b3\n"))
|
||||
require.True(t, ok)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "a", string(res[0].Name()))
|
||||
require.Equal(t, "b1\n b2\n b3", string(res[0].Value()))
|
||||
require.Equal(t, "b1 b2 b3", string(res[0].NormalizedValue()))
|
||||
|
||||
res, ok = splitHeaders([]byte("a: b\nc: d\n"))
|
||||
require.True(t, ok)
|
||||
require.Len(t, res, 2)
|
||||
require.Equal(t, "a", string(res[0].Name()))
|
||||
require.Equal(t, "b", string(res[0].Value()))
|
||||
require.Equal(t, "c", string(res[1].Name()))
|
||||
require.Equal(t, "d", string(res[1].Value()))
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
HTTP/1.1 200 OK
|
||||
date: Sun, 17 Mar 2024 21:58:03 GMT
|
||||
content-type: application/json; charset=utf-8
|
||||
content-length: 35
|
||||
age: 32
|
||||
x-multiline: a
|
||||
b
|
||||
c
|
||||
x-multivalue: value1
|
||||
x-multivalue: value2
|
||||
|
||||
|
||||
{"key1": "value1","key2": "value2"}
|
Loading…
Reference in New Issue