Skip to content

Commit

Permalink
Merge pull request indigo-web#137 from flrdv/master
Browse files Browse the repository at this point in the history
Optimized serializer, updated stringer for proto.Proto, added Connection header value as the request's attribute
  • Loading branch information
flrdv authored Apr 26, 2024
2 parents 155f693 + cd49408 commit e20ef64
Show file tree
Hide file tree
Showing 17 changed files with 112 additions and 120 deletions.
8 changes: 4 additions & 4 deletions http/headers/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ type (
Headers = *keyvalue.Storage
)

func NewPrealloc(n int) Headers {
return keyvalue.NewPreAlloc(n)
}

func New() Headers {
return NewPrealloc(0)
}

func NewPrealloc(n int) Headers {
return keyvalue.NewPreAlloc(n)
}

func NewFromMap(m map[string][]string) Headers {
return keyvalue.NewFromMap(m)
}
27 changes: 9 additions & 18 deletions http/proto/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package proto

import "github.com/indigo-web/utils/uf"

//go:generate stringer -type=Proto
type Proto uint8

const (
Expand All @@ -11,15 +10,18 @@ const (
HTTP11
HTTP2

WebSocket

HTTP1 = HTTP10 | HTTP11
)

var (
http10 = []byte("HTTP/1.0 ")
http11 = []byte("HTTP/1.1 ")
)
// String returns protocol as a string WITH A TRAILING SPACE
func (p Proto) String() string {
lut := [...]string{HTTP10: "HTTP/1.0 ", HTTP11: "HTTP/1.1 ", HTTP2: "HTTP/2 "}
if int(p) >= len(lut) {
return ""
}

return lut[p]
}

const (
protoTokenLength = len("HTTP/x.x")
Expand Down Expand Up @@ -48,14 +50,3 @@ func Parse(major, minor uint8) Proto {

return majorMinorVersionLUT[major][minor]
}

func ToBytes(proto Proto) []byte {
switch proto {
case HTTP10:
return http10
case HTTP11:
return http11
default:
return nil
}
}
41 changes: 0 additions & 41 deletions http/proto/proto_string.go

This file was deleted.

2 changes: 0 additions & 2 deletions http/proto/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ func parseUpgradeToken(token string) Proto {
return HTTP11
case "h2c", "H2C":
return HTTP2
case "websocket", "WebSocket", "WEBSOCKET":
return WebSocket
}

return Unknown
Expand Down
4 changes: 4 additions & 0 deletions http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ type Request struct {
ContentLength int
// ContentType obtains Content-Type header value
ContentType string
// Connection holds the Connection header value. It isn't normalized, so can be anything
// and in any case. So in order to compare it, highly recommended to do it case-insensibly
Connection string
// Upgrade is the protocol token, which is set by default to proto.Unknown. In
// case it is anything else, then Upgrade header was received
Upgrade proto.Proto
Expand Down Expand Up @@ -174,6 +177,7 @@ func (r *Request) Clear() (err error) {
r.ContentLength = 0
r.Encoding = Encoding{}
r.ContentType = ""
r.Connection = ""
r.Upgrade = proto.Unknown
r.Ctx = zeroContext
r.Env = Environment{}
Expand Down
12 changes: 6 additions & 6 deletions http/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (
type ResponseWriter func(b []byte) error

const (
// why 7? I don't know. There's no theory behind the number.fields. It can be adjusted
// to 10 as well, but why you would ever need to do this?
// why 7? I don't know. There's no theory behind this number nor researches.
// It can be adjusted to 10 as well, but why you would ever need to do this?
defaultHeadersNumber = 7
defaultFileMIME = mime.OctetStream
)

type Response struct {
fields response.Fields
fields *response.Fields
}

// NewResponse returns a new instance of the Response object with status code set to 200 OK,
Expand All @@ -34,7 +34,7 @@ type Response struct {
// clear reason otherwise
func NewResponse() *Response {
return &Response{
response.Fields{
&response.Fields{
Code: status.OK,
Headers: make([]headers.Header, 0, defaultHeadersNumber),
ContentType: response.DefaultContentType,
Expand Down Expand Up @@ -219,13 +219,13 @@ func (r *Response) Error(err error, code ...status.Code) *Response {
}

// Reveal returns a struct with values, filled by builder. Used mostly in internal purposes
func (r *Response) Reveal() response.Fields {
func (r *Response) Reveal() *response.Fields {
return r.fields
}

// Clear discards everything was done with Response object before
func (r *Response) Clear() *Response {
r.fields = r.fields.Clear()
r.fields.Clear()
return r
}

Expand Down
3 changes: 1 addition & 2 deletions internal/httptest/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package httptest
import (
"github.com/indigo-web/indigo/http"
"github.com/indigo-web/indigo/http/headers"
"github.com/indigo-web/indigo/http/proto"
"strconv"
)

Expand Down Expand Up @@ -32,7 +31,7 @@ func Dump(request *http.Request) (string, error) {
}

buff = space(buff)
protocol := proto.ToBytes(request.Proto)
protocol := request.Proto.String()
protocol = protocol[:len(protocol)-1]
buff = append(buff, protocol...)
buff = crlf(buff)
Expand Down
22 changes: 11 additions & 11 deletions internal/keyvalue/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ type Storage struct {
valuesBuff []string
}

func New() *Storage {
return NewPreAlloc(0)
}

// NewPreAlloc returns an instance of Storage with pre-allocated underlying storage
func NewPreAlloc(n int) *Storage {
return &Storage{
pairs: make([]Pair, 0, n),
}
}

// NewFromMap returns a new instance with already inserted values from given map.
// Note: as maps are unordered, resulting underlying structure will also contain unordered
// pairs
Expand All @@ -37,17 +48,6 @@ func NewFromMap(m map[string][]string) *Storage {
return kv
}

// NewPreAlloc returns an instance of Storage with pre-allocated underlying storage
func NewPreAlloc(n int) *Storage {
return &Storage{
pairs: make([]Pair, 0, n),
}
}

func New() *Storage {
return NewPreAlloc(0)
}

// Add adds a new pair of key and value
func (s *Storage) Add(key, value string) *Storage {
s.pairs = append(s.pairs, Pair{
Expand Down
5 changes: 3 additions & 2 deletions internal/protocol/http1/body.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ func (c *chunkedBodyReader) init(request *http.Request) {
}

func (c *chunkedBodyReader) read() (body []byte, err error) {
data, err := c.client.Read()
client := c.client
data, err := client.Read()
if err != nil {
return nil, err
}
Expand All @@ -221,7 +222,7 @@ func (c *chunkedBodyReader) read() (body []byte, err error) {
}

c.received = received
c.client.Unread(extra)
client.Unread(extra)

return chunk, err
}
Expand Down
12 changes: 12 additions & 0 deletions internal/protocol/http1/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ headerValue:
case cTrailer:
request.Encoding.HasTrailer = true
}
case 10:
if cConnecti == encodeU64(
key[0]|0x20, key[1]|0x20, key[2]|0x20, key[3]|0x20, key[4]|0x20, key[5]|0x20, key[6]|0x20, key[7]|0x20,
) && cOn == encodeU16(key[8]|0x20, key[9]|0x20) {
request.Connection = value
}
case 12:
if cContent == encodeU64(
key[0]|0x20, key[1]|0x20, key[2]|0x20, key[3]|0x20, key[4]|0x20, key[5]|0x20, key[6]|0x20, key[7]|0x20,
Expand Down Expand Up @@ -427,6 +433,8 @@ var (
cUpgrade = encodeU64('u', 'p', 'g', 'r', 'a', 'd', 'e', 0)
cTrailer = encodeU64('t', 'r', 'a', 'i', 'l', 'e', 'r', 0)
cContent = encodeU64('c', 'o', 'n', 't', 'e', 'n', 't', '-')
cConnecti = encodeU64('c', 'o', 'n', 'n', 'e', 'c', 't', 'i')
cOn = encodeU16('o', 'n')
cLength = encodeU64('l', 'e', 'n', 'g', 't', 'h', 0, 0)
cType = encodeU32('t', 'y', 'p', 'e')
cEncoding = encodeU64('e', 'n', 'c', 'o', 'd', 'i', 'n', 'g')
Expand All @@ -442,3 +450,7 @@ func encodeU64(a, b, c, d, e, f, g, h uint8) uint64 {
func encodeU32(a, b, c, d uint8) uint32 {
return (uint32(d) << 24) | (uint32(c) << 16) | (uint32(b) << 8) | uint32(a)
}

func encodeU16(a, b uint8) uint16 {
return (uint16(b) << 8) | uint16(a)
}
10 changes: 10 additions & 0 deletions internal/protocol/http1/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ func TestHttpRequestsParser_Parse_GET(t *testing.T) {
require.Equal(t, "ha-ha", request.Headers.Value("hi-hi"))
require.NoError(t, request.Clear())
})

t.Run("connection", func(t *testing.T) {
raw := "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n"
state, extra, err := parser.Parse([]byte(raw))
require.NoError(t, err)
require.Equal(t, HeadersCompleted, state)
require.Empty(t, string(extra))
require.Equal(t, "Keep-Alive", request.Connection)
require.NoError(t, request.Clear())
})
}

func TestHttpRequestsParser_POST(t *testing.T) {
Expand Down
20 changes: 9 additions & 11 deletions internal/protocol/http1/serializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (d *Serializer) Write(
return err
}

func (d *Serializer) renderResponseLine(fields response.Fields) {
func (d *Serializer) renderResponseLine(fields *response.Fields) {
statusLine := status.Line(fields.Code)

if fields.Status == "" && statusLine != "" {
Expand All @@ -136,7 +136,7 @@ func (d *Serializer) renderResponseLine(fields response.Fields) {
d.crlf()
}

func (d *Serializer) renderHeaders(fields response.Fields) {
func (d *Serializer) renderHeaders(fields *response.Fields) {
responseHeaders := fields.Headers

for _, header := range responseHeaders {
Expand Down Expand Up @@ -206,7 +206,7 @@ func (d *Serializer) sendAttachment(
}

func (d *Serializer) writePlainBody(r io.Reader, writer Writer) error {
// TODO: implement checking whether r implements io.ReaderAt interfacd. In case it does
// TODO: implement checking whether r implements io.ReaderAt interface. In case it does
// body may be transferred more efficiently. This requires implementing io.Writer
// *http.ResponseWriter

Expand Down Expand Up @@ -338,7 +338,7 @@ func (d *Serializer) renderKnownHeader(key, value string) {
}

func (d *Serializer) renderProtocol(protocol proto.Proto) {
d.buff = append(d.buff, proto.ToBytes(protocol)...)
d.buff = append(d.buff, protocol.String()...)
}

func (d *Serializer) sp() {
Expand All @@ -361,16 +361,14 @@ func (d *Serializer) clear() {
func isKeepAlive(protocol proto.Proto, req *http.Request) bool {
switch protocol {
case proto.HTTP10:
return strcomp.EqualFold(req.Headers.Value("connection"), "keep-alive")
return strcomp.EqualFold(req.Connection, "keep-alive")
case proto.HTTP11:
// in case of HTTP/1.1, keep-alive may be only disabled
return !strcomp.EqualFold(req.Headers.Value("connection"), "close")
case proto.HTTP2:
// TODO: are there cases when HTTP/2 connection may not be keep-alived?
return true
return !strcomp.EqualFold(req.Connection, "close")
default:
// don't know what this is, but act like everything is okay
return true
// as the protocol is unknown and the code was probably caused by some sort
// of bug, consider closing it
return false
}
}

Expand Down
Loading

0 comments on commit e20ef64

Please sign in to comment.