Skip to content

Commit

Permalink
Merge pull request #128 from flrdv/master
Browse files Browse the repository at this point in the history
Storing version in a constant, improve simple router and adjust http.Respond behaviour
  • Loading branch information
flrdv authored Jan 29, 2024
2 parents ca6cf36 + a4b3333 commit ba5084d
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 58 deletions.
4 changes: 3 additions & 1 deletion examples/helloworld_norouter/helloworld.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ func MyHandler(request *http.Request) *http.Response {
}

func main() {
r := simple.New(MyHandler, http.Error)
r := simple.New(MyHandler, func(request *http.Request) *http.Response {
return http.Error(request, request.Env.Error)
})
app := indigo.New(addr)
log.Println("Listening on", addr)
log.Fatal(app.Serve(r))
Expand Down
7 changes: 2 additions & 5 deletions http/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,9 @@ func (r *Response) Clear() *Response {
return r
}

// Respond returns a response object of request.
//
// NOTE: the behaviour differs from request.Respond() directly, as it doesn't clear
// the response builder each time
// Respond is a predicate to request.Respond(). May be used as a dummy handler
func Respond(request *Request) *Response {
return request.response
return request.Respond()
}

// Code is a predicate to request.Respond().Code(...)
Expand Down
42 changes: 41 additions & 1 deletion indigo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,19 @@ func TestServer(t *testing.T) {
}
})

t.Run("body reader", func(t *testing.T) {
r := strings.NewReader("Hello, world!")
resp, err := stdhttp.DefaultClient.Post(appURL+"/body-reader", "text/html", r)
require.NoError(t, err)
defer func() {
_ = resp.Body.Close()
}()
require.Equal(t, stdhttp.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "Hello, world!", string(body))
})

t.Run("root head", func(t *testing.T) {
resp, err := stdhttp.DefaultClient.Head(appURL + "/")
require.NoError(t, err)
Expand Down Expand Up @@ -489,13 +502,25 @@ func TestServer(t *testing.T) {
require.NoError(t, conn.SetReadDeadline(time.Now().Add(time.Second)))
data, err := io.ReadAll(conn)
require.NoError(t, err)
fmt.Println("data:", strconv.Quote(string(data)))
n := bytes.Count(data, []byte("\r\n\r\n"))
// pipelinedRequests+1 because we're reading till io.EOF. So by that, the server
// sends us 408 Request Timeout before closing the connection
require.Equal(t, pipelinedRequests+1, n, "got less pipelined responses as expected")
})

t.Run("chunked body", func(t *testing.T) {
request := "POST /body-reader HTTP/1.1\r\n" +
"Connection: close\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"7\r\nMozilla\r\n1\r\n \r\n11\r\nDeveloper Network\r\n0\r\n\r\n"
resp, err := send(addr, []byte(request))
require.NoError(t, err)
repr, err := httptest.Parse(string(resp))
require.NoError(t, err)
require.Equal(t, "Mozilla Developer Network", repr.Body)
})

t.Run("forced stop", func(t *testing.T) {
app.Stop()
chanRead(ch, 5*time.Second)
Expand Down Expand Up @@ -525,6 +550,21 @@ func sendSimpleRequest(addr, path string) (net.Conn, error) {
return conn, err
}

func send(addr string, req []byte) ([]byte, error) {
conn, err := net.Dial("tcp", addr)
defer conn.Close()
if err != nil {
return nil, err
}

_, err = conn.Write(req)
if err != nil {
return nil, err
}

return io.ReadAll(conn)
}

func parseBody(resp *stdhttp.Response) (httptest.Request, error) {
body, err := io.ReadAll(resp.Body)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/server/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ func newComparingRouter(t *testing.T, want headers.Headers) *simple.Router {
return simple.New(func(request *http.Request) *http.Response {
require.True(t, compareHeaders(want, request.Headers))
return http.Respond(request)
}, func(request *http.Request, err error) *http.Response {
require.Failf(t, "unexpected error", "unexpected error: %s", err.Error())
}, func(request *http.Request) *http.Response {
require.Failf(t, "unexpected error", "unexpected error: %s", request.Env.Error.Error())
return nil
})
}
Expand Down
100 changes: 60 additions & 40 deletions internal/transport/http1/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ method:
}

data = data[sp+1:]
p.state = ePath
goto path
}

Expand All @@ -133,6 +132,7 @@ path:
return transport.Error, nil, status.ErrURITooLong
}

p.state = ePath
return transport.Pending, nil, nil
}

Expand Down Expand Up @@ -173,15 +173,13 @@ path:
}

data = data[lf+1:]
p.state = eHeaderKey
goto headerKey
}

return transport.Pending, nil, nil

headerKey:
{
if len(data) == 0 {
p.state = eHeaderKey
return transport.Pending, nil, err
}

Expand All @@ -192,7 +190,6 @@ headerKey:
return transport.HeadersCompleted, data[1:], nil
case '\r':
data = data[1:]
p.state = eHeaderValueCRLFCR
goto headerValueCRLFCR
}

Expand All @@ -202,26 +199,31 @@ headerKey:
return transport.Error, nil, status.ErrHeaderFieldsTooLarge
}

p.state = eHeaderKey
return transport.Pending, nil, nil
}

if !headerKeyBuff.Append(data[:colon]) {
return transport.Error, nil, status.ErrHeaderFieldsTooLarge
}

p.headerKey = uf.B2S(headerKeyBuff.Finish())
key := uf.B2S(headerKeyBuff.Finish())
p.headerKey = key
data = data[colon+1:]

if p.headersNumber++; p.headersNumber > p.headersSettings.Number.Maximal {
return transport.Error, nil, status.ErrTooManyHeaders
}

if strcomp.EqualFold(p.headerKey, "content-length") {
p.state = eContentLength
if len(key) == len("content-length") &&
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,
) && cLength == encodeU64(
key[8]|0x20, key[9]|0x20, key[10]|0x20, key[11]|0x20, key[12]|0x20, key[13]|0x20, 0, 0,
) {
goto contentLength
}

p.state = eHeaderValue
goto headerValue
}

Expand All @@ -239,6 +241,7 @@ contentLength:
p.contentLength = p.contentLength*10 + int(char-'0')
}

p.state = eContentLength
return transport.Pending, nil, nil

contentLengthEnd:
Expand All @@ -249,21 +252,19 @@ contentLengthEnd:
request.ContentLength = p.contentLength

switch data[0] {
case ' ':
case '\r':
data = data[1:]
p.state = eContentLengthCR
goto contentLengthCR
case '\n':
data = data[1:]
p.state = eHeaderKey
goto headerKey
default:
return transport.Error, nil, status.ErrBadRequest
}

contentLengthCR:
if len(data) == 0 {
p.state = eContentLengthCR
return transport.Pending, nil, nil
}

Expand All @@ -272,7 +273,6 @@ contentLengthCR:
}

data = data[1:]
p.state = eHeaderKey
goto headerKey

headerValue:
Expand All @@ -287,6 +287,7 @@ headerValue:
return transport.Error, nil, status.ErrHeaderFieldsTooLarge
}

p.state = eHeaderValue
return transport.Pending, nil, nil
}

Expand All @@ -304,56 +305,55 @@ headerValue:
value = value[:len(value)-1]
}

request.Headers.Add(p.headerKey, value)
key := p.headerKey
request.Headers.Add(key, value)

switch len(p.headerKey) {
switch len(key) {
case 7:
if p.headerKey[0]|0x20 == 'u' && p.headerKey[1]|0x20 == 'p' && p.headerKey[2]|0x20 == 'g' &&
p.headerKey[3]|0x20 == 'r' && p.headerKey[4]|0x20 == 'a' && p.headerKey[5]|0x20 == 'd' &&
p.headerKey[6]|0x20 == 'e' {
request.Upgrade = proto.ChooseUpgrade(value)
}
encoded := encodeU64(
key[0]|0x20, key[1]|0x20, key[2]|0x20, key[3]|0x20, key[4]|0x20, key[5]|0x20, key[6]|0x20, 0,
)

if p.headerKey[0]|0x20 == 't' && p.headerKey[1]|0x20 == 'r' && p.headerKey[2]|0x20 == 'a' &&
p.headerKey[3]|0x20 == 'i' && p.headerKey[4]|0x20 == 'l' && p.headerKey[5]|0x20 == 'e' &&
p.headerKey[6]|0x20 == 'r' {
switch encoded {
case cUpgrade:
request.Upgrade = proto.ChooseUpgrade(value)
case cTrailer:
request.Encoding.HasTrailer = true
}
case 12:
if p.headerKey[0]|0x20 == 'c' && p.headerKey[1]|0x20 == 'o' && p.headerKey[2]|0x20 == 'n' &&
p.headerKey[3]|0x20 == 't' && p.headerKey[4]|0x20 == 'e' && p.headerKey[5]|0x20 == 'n' &&
p.headerKey[6]|0x20 == 't' && p.headerKey[7] == '-' && p.headerKey[8]|0x20 == 't' &&
p.headerKey[9]|0x20 == 'y' && p.headerKey[10]|0x20 == 'p' && p.headerKey[11]|0x20 == 'e' {
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,
) && cType == encodeU32(
key[8]|0x20, key[9]|0x20, key[10]|0x20, key[11]|0x20,
) {
request.ContentType = value
}
case 16:
if p.headerKey[0]|0x20 == 'c' && p.headerKey[1]|0x20 == 'o' && p.headerKey[2]|0x20 == 'n' &&
p.headerKey[3]|0x20 == 't' && p.headerKey[4]|0x20 == 'e' && p.headerKey[5]|0x20 == 'n' &&
p.headerKey[6]|0x20 == 't' && p.headerKey[7] == '-' && p.headerKey[8]|0x20 == 'e' &&
p.headerKey[9]|0x20 == 'n' && p.headerKey[10]|0x20 == 'c' && p.headerKey[11]|0x20 == 'o' &&
p.headerKey[12]|0x20 == 'd' && p.headerKey[13]|0x20 == 'i' && p.headerKey[14]|0x20 == 'n' &&
p.headerKey[15]|0x20 == 'g' {
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,
) && cEncoding == encodeU64(
key[8]|0x20, key[9]|0x20, key[10]|0x20, key[11]|0x20, key[12]|0x20, key[13]|0x20, key[14]|0x20, key[15]|0x20,
) {
request.Encoding.Content, _ = parseEncodingString(p.contEncToksBuff, value, cap(p.contEncToksBuff))
}
case 17:
if p.headerKey[0]|0x20 == 't' && p.headerKey[1]|0x20 == 'r' && p.headerKey[2]|0x20 == 'a' &&
p.headerKey[3]|0x20 == 'n' && p.headerKey[4]|0x20 == 's' && p.headerKey[5]|0x20 == 'f' &&
p.headerKey[6]|0x20 == 'e' && p.headerKey[7]|0x20 == 'r' && p.headerKey[8] == '-' &&
p.headerKey[9]|0x20 == 'e' && p.headerKey[10]|0x20 == 'n' && p.headerKey[11]|0x20 == 'c' &&
p.headerKey[12]|0x20 == 'o' && p.headerKey[13]|0x20 == 'd' && p.headerKey[14]|0x20 == 'i' &&
p.headerKey[15]|0x20 == 'n' && p.headerKey[16]|0x20 == 'g' {
if cTransfer == 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,
) && cEncodin == encodeU64(
key[8]|0x20, key[9]|0x20, key[10]|0x20, key[11]|0x20, key[12]|0x20, key[13]|0x20, key[14]|0x20, key[15]|0x20,
) && key[16]|0x20 == 'g' {
request.Encoding.Transfer, request.Encoding.Chunked = parseEncodingString(
p.encToksBuff, value, cap(p.encToksBuff),
)
}
}

p.state = eHeaderKey
goto headerKey
}

headerValueCRLFCR:
if len(data) == 0 {
p.state = eHeaderValueCRLFCR
return transport.Pending, nil, nil
}

Expand Down Expand Up @@ -415,3 +415,23 @@ func trimPrefixSpaces(b []byte) []byte {

return b[:0]
}

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', '-')
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')
cTransfer = encodeU64('t', 'r', 'a', 'n', 's', 'f', 'e', 'r')
cEncodin = encodeU64('-', 'e', 'n', 'c', 'o', 'd', 'i', 'n')
)

func encodeU64(a, b, c, d, e, f, g, h uint8) uint64 {
return (uint64(h) << 56) | (uint64(g) << 48) | (uint64(f) << 40) | (uint64(e) << 32) |
(uint64(d) << 24) | (uint64(c) << 16) | (uint64(b) << 8) | uint64(a)
}

func encodeU32(a, b, c, d uint8) uint32 {
return (uint32(d) << 24) | (uint32(c) << 16) | (uint32(b) << 8) | uint32(a)
}
21 changes: 21 additions & 0 deletions router/inbuilt/middleware/serverheader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package middleware

import (
"github.com/indigo-web/indigo/http"
"github.com/indigo-web/indigo/router/inbuilt"
"strings"
)

const DefaultServerHeader = "indigo"

func ServerHeader(customHeaders ...string) inbuilt.Middleware {
value := strings.Join(customHeaders, " ")
if len(value) == 0 {
value = DefaultServerHeader
}

return func(next inbuilt.Handler, request *http.Request) *http.Response {
return next(request).
Header("Server", value)
}
}
14 changes: 6 additions & 8 deletions router/simple/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@ import (

var _ router.Router = new(Router)

type (
Handler func(*http.Request) *http.Response
ErrorHandler func(*http.Request, error) *http.Response
)
type Handler func(*http.Request) *http.Response

type Router struct {
handler Handler
errHandler ErrorHandler
handler, errHandler Handler
}

func New(handler Handler, errHandler ErrorHandler) *Router {
func New(handler, errHandler Handler) *Router {
return &Router{
handler: handler,
errHandler: errHandler,
Expand All @@ -33,5 +29,7 @@ func (r Router) OnRequest(request *http.Request) *http.Response {
}

func (r Router) OnError(request *http.Request, err error) *http.Response {
return r.errHandler(request, err)
request.Env.Error = err

return r.errHandler(request)
}
1 change: 0 additions & 1 deletion version

This file was deleted.

3 changes: 3 additions & 0 deletions version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package indigo

const Version = "v0.15.8"

0 comments on commit ba5084d

Please sign in to comment.