Skip to content

Commit ff60dfd

Browse files
authored
Merge pull request #140 from flrdv/master
v0.17.1: breaking changes, new radix tree, introduction of streams as opposed to previously attachments, bug fixes and overall performance speed-ups
2 parents b4c8dad + cae3183 commit ff60dfd

40 files changed

+1174
-1394
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,6 @@ fabric.properties
9898

9999
linesofcode.py
100100
oldestline.sh
101+
cloc.sh
101102

102103
bin/

config/config.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ type (
3535
DefaultContentType mime.MIME
3636
}
3737

38+
HTTPResponseBuffer struct {
39+
Default, Maximal int
40+
}
41+
3842
URIRequestLineSize struct {
3943
Default, Maximal int
4044
}
@@ -90,11 +94,16 @@ type (
9094
}
9195

9296
HTTP struct {
93-
// ResponseBuffSize is responsible for a response buffer that is being allocated when
94-
// client connects and is used for rendering the response into it
95-
ResponseBuffSize int
96-
// FileBuffSize defines the size of the read buffer when reading a file
97-
FileBuffSize int
97+
// ResponseBuffer is used to store the byte-representation of a response, ready to be sent
98+
// over the network.
99+
//
100+
// Response buffer growth rules:
101+
// 1) If the stream is sized (1) and its size overflows current buffer length (2),
102+
// grow it to contain the whole stream at once, but limit the size to at most
103+
// `HTTP.ResponseBuffer.Maximal`
104+
// 2) If the stream is unsized (1) and the previous write used more than ~98.44% of its total
105+
// capacity (2), the capacity doubles.
106+
ResponseBuffer HTTPResponseBuffer
98107
}
99108

100109
NET struct {
@@ -171,8 +180,10 @@ func Default() *Config {
171180
},
172181
},
173182
HTTP: HTTP{
174-
ResponseBuffSize: 1024,
175-
FileBuffSize: 64 * 1024, // 64kb read buffer for files is pretty much sufficient
183+
ResponseBuffer: HTTPResponseBuffer{
184+
Default: 1024,
185+
Maximal: 64 * 1024,
186+
},
176187
},
177188
NET: NET{
178189
ReadBufferSize: 4 * 1024, // 4kb is more than enough for ordinary requests.

examples/connhijack/connhijack.go

Lines changed: 0 additions & 46 deletions
This file was deleted.

examples/dynroute/dynroute.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"fmt"
45
"log"
56

67
"github.com/indigo-web/indigo/http"
@@ -11,21 +12,27 @@ import (
1112

1213
const addr = ":8080"
1314

14-
func MyDynamicHandler(request *http.Request) *http.Response {
15-
worldName := request.Params.Value("world-name")
16-
17-
return request.Respond().String("your world-name is " + worldName)
18-
}
19-
2015
func main() {
2116
r := inbuilt.New()
2217

23-
r.Get("/hello/{world-name}", MyDynamicHandler)
24-
25-
app := indigo.New(addr).
26-
OnBind(func(addr string) {
27-
log.Printf("running on %s\n", addr)
18+
r.
19+
Get("/api/v:version/user/:id", func(request *http.Request) *http.Response {
20+
majorVersion := request.Vars.Value("version")
21+
id := request.Vars.Value("id")
22+
23+
return http.String(
24+
request, fmt.Sprintf("user %s requested the API v%s", id, majorVersion),
25+
)
26+
}).
27+
Get("/api/v1/user/0", func(request *http.Request) *http.Response {
28+
return http.String(request, "welcome back, legend!")
2829
})
2930

30-
log.Fatal(app.Serve(r))
31+
log.Fatal(
32+
indigo.New(addr).
33+
OnBind(func(addr string) {
34+
log.Printf("running on %s\n", addr)
35+
}).
36+
Serve(r),
37+
)
3138
}

http/response.go

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/indigo-web/indigo/http/status"
88
"github.com/indigo-web/indigo/internal/response"
99
"github.com/indigo-web/indigo/internal/strutil"
10-
"github.com/indigo-web/indigo/internal/types"
1110
"github.com/indigo-web/indigo/kv"
1211
json "github.com/json-iterator/go"
1312
"io"
@@ -25,7 +24,7 @@ const (
2524
)
2625

2726
type Response struct {
28-
fields *response.Fields
27+
fields response.Fields
2928
}
3029

3130
// NewResponse returns a new instance of the Response object with status code set to 200 OK,
@@ -34,10 +33,11 @@ type Response struct {
3433
// clear reason otherwise
3534
func NewResponse() *Response {
3635
return &Response{
37-
&response.Fields{
36+
response.Fields{
3837
Code: status.OK,
3938
Headers: make([]kv.Pair, 0, preallocRespHeaders),
4039
ContentType: response.DefaultContentType,
40+
StreamSize: -1,
4141
},
4242
}
4343
}
@@ -112,13 +112,13 @@ func (r *Response) String(body string) *Response {
112112
// Bytes sets the response's body to passed slice WITHOUT COPYING. Changing
113113
// the passed slice later will affect the response by itself
114114
func (r *Response) Bytes(body []byte) *Response {
115-
r.fields.Body = body
115+
r.fields.BufferedBody = body
116116
return r
117117
}
118118

119119
// Write implements io.Reader interface. It always returns n=len(b) and err=nil
120120
func (r *Response) Write(b []byte) (n int, err error) {
121-
r.fields.Body = append(r.fields.Body, b...)
121+
r.fields.BufferedBody = append(r.fields.BufferedBody, b...)
122122
return len(b), nil
123123
}
124124

@@ -132,7 +132,6 @@ func (r *Response) TryFile(path string) (*Response, error) {
132132

133133
stat, err := fd.Stat()
134134
if err != nil {
135-
// ...and if we can't get stats on it, it exists, however something in system went wrong
136135
return r, status.ErrInternalServerError
137136
}
138137
if stat.IsDir() {
@@ -144,7 +143,7 @@ func (r *Response) TryFile(path string) (*Response, error) {
144143
r.fields.ContentType = defaultFileMIME
145144
}
146145

147-
return r.Attachment(fd, int(stat.Size())), nil
146+
return r.SizedStream(fd, stat.Size()), nil
148147
}
149148

150149
// File opens a file for reading and returns a new Response with attachment, set to the file
@@ -158,10 +157,21 @@ func (r *Response) File(path string) *Response {
158157
return resp
159158
}
160159

161-
// Attachment sets a Response's attachment. In this case Response body will be ignored.
162-
// If size <= 0, then Transfer-Encoding: chunked will be used
163-
func (r *Response) Attachment(reader io.Reader, size int) *Response {
164-
r.fields.Attachment = types.NewAttachment(reader, size)
160+
// Stream sets a reader to be the source of the response's body.
161+
func (r *Response) Stream(reader io.Reader) *Response {
162+
// TODO: we can check whether the reader implements Len() int interface and in that
163+
// TODO: case elide the chunked transfer encoding
164+
r.fields.Stream = reader
165+
r.fields.StreamSize = -1
166+
return r
167+
}
168+
169+
// SizedStream receives a hint of the stream's future size. This helps, for example, uploading files,
170+
// as in this case we can rely on io.WriterTo interface, which might use more effective kernel mechanisms
171+
// available, e.g. sendfile(2) for Linux.
172+
func (r *Response) SizedStream(reader io.Reader, size int64) *Response {
173+
r.fields.Stream = reader
174+
r.fields.StreamSize = size
165175
return r
166176
}
167177

@@ -174,7 +184,7 @@ func (r *Response) Cookie(cookies ...cookie.Cookie) *Response {
174184
// TryJSON receives a model (must be a pointer to the structure) and returns a new Response
175185
// object and an error
176186
func (r *Response) TryJSON(model any) (*Response, error) {
177-
r.fields.Body = r.fields.Body[:0]
187+
r.fields.BufferedBody = r.fields.BufferedBody[:0]
178188
stream := json.ConfigDefault.BorrowStream(r)
179189
stream.WriteVal(model)
180190
err := stream.Flush()
@@ -220,7 +230,7 @@ func (r *Response) Error(err error, code ...status.Code) *Response {
220230

221231
// Reveal returns a struct with values, filled by builder. Used mostly in internal purposes
222232
func (r *Response) Reveal() *response.Fields {
223-
return r.fields
233+
return &r.fields
224234
}
225235

226236
// Clear discards everything was done with Response object before
@@ -229,42 +239,52 @@ func (r *Response) Clear() *Response {
229239
return r
230240
}
231241

232-
// Respond is a predicate to request.Respond(). May be used as a dummy handler
242+
// Respond is a shorthand for request.Respond(). May be used as a dummy handler
233243
func Respond(request *Request) *Response {
234244
return request.Respond()
235245
}
236246

237-
// Code is a predicate to request.Respond().Code(...)
247+
// Code is a shorthand for request.Respond().Code(...)
238248
func Code(request *Request, code status.Code) *Response {
239249
return request.Respond().Code(code)
240250
}
241251

242-
// String is a predicate to request.Respond().String(...)
252+
// String is a shorthand for request.Respond().String(...)
243253
func String(request *Request, str string) *Response {
244254
return request.Respond().String(str)
245255
}
246256

247-
// Bytes is a predicate to request.Respond().Bytes(...)
257+
// Bytes is a shorthand for request.Respond().Bytes(...)
248258
func Bytes(request *Request, b []byte) *Response {
249259
return request.Respond().Bytes(b)
250260
}
251261

252-
// File is a predicate to request.Respond().File(...)
262+
// File is a shorthand for request.Respond().File(...)
253263
func File(request *Request, path string) *Response {
254264
return request.Respond().File(path)
255265
}
256266

257-
// JSON is a predicate to request.Respond().JSON(...)
267+
// Stream is a shorthand for request.Respond().Stream(...)
268+
func Stream(request *Request, reader io.Reader) *Response {
269+
return request.Respond().Stream(reader)
270+
}
271+
272+
// SizedStream is a shorthand for request.Respond().SizedStream(...)
273+
func SizedStream(request *Request, reader io.Reader, size int64) *Response {
274+
return request.Respond().SizedStream(reader, size)
275+
}
276+
277+
// JSON is a shorthand for request.Respond().JSON(...)
258278
func JSON(request *Request, model any) *Response {
259279
return request.Respond().JSON(model)
260280
}
261281

262-
// Error is a predicate to request.Respond().Error(...)
282+
// Error is a shorthand for request.Respond().Error(...)
263283
//
264-
// Error returns a response builder with an error set. If passed err is nil, nothing will happen.
265-
// If an instance of status.HTTPError is passed, error code will be automatically set. Custom
266-
// codes can be passed, however only first will be used. By default, the error is
267-
// status.ErrInternalServerError
284+
// Error returns the response builder with an error set. If passed err is nil, nothing will happen.
285+
// If an instance of status.HTTPError is passed, its status code is automatically set. Otherwise,
286+
// status.ErrInternalServerError is used. A custom code can be set. Passing multiple status codes
287+
// will discard all except the first one.
268288
func Error(request *Request, err error, code ...status.Code) *Response {
269289
return request.Respond().Error(err, code...)
270290
}

http/response_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func TestResponse(t *testing.T) {
1313
m := []int{1, 2, 3}
1414
resp, err := response.TryJSON(m)
1515
require.NoError(t, err)
16-
require.Equal(t, "[1,2,3]", string(resp.Reveal().Body))
16+
require.Equal(t, "[1,2,3]", string(resp.Reveal().BufferedBody))
1717
require.Equal(t, "application/json", resp.Reveal().ContentType)
1818
})
1919
}

indi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/indigo-web/indigo/transport"
1010
)
1111

12-
const Version = "0.17.0"
12+
const Version = "0.17.1"
1313

1414
// App is just a struct with addr and shutdown channel that is currently
1515
// not used. Planning to replace it with context.WithCancel()

0 commit comments

Comments
 (0)