Skip to content

Commit ab3b8c6

Browse files
authored
Merge pull request #12 from go-spring-projects/feat-web-methods
Add server route method `Get/Head/Post/Put/Patch/Delete/Connect/Options/Trace`
2 parents c545a23 + fa9bece commit ab3b8c6

File tree

17 files changed

+166
-60
lines changed

17 files changed

+166
-60
lines changed

web/bind.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@ func (fn RendererFunc) Render(ctx *Context, err error, result interface{}) {
4141
//
4242
// func(ctx context.Context) R
4343
//
44+
// func(ctx context.Context) error
45+
//
4446
// func(ctx context.Context, req T) R
4547
//
48+
// func(ctx context.Context, req T) error
49+
//
4650
// func(ctx context.Context, req T) (R, error)
4751
//
4852
// func(writer http.ResponseWriter, request *http.Request)
@@ -53,18 +57,20 @@ func Bind(fn interface{}, render Renderer) http.HandlerFunc {
5357

5458
switch h := fn.(type) {
5559
case http.HandlerFunc:
56-
return h
60+
return warpHandlerCtx(h)
5761
case http.Handler:
58-
return h.ServeHTTP
62+
return warpHandlerCtx(h.ServeHTTP)
5963
case func(http.ResponseWriter, *http.Request):
60-
return h
64+
return warpHandlerCtx(h)
6165
default:
6266
// valid func
6367
if err := validMappingFunc(fnType); nil != err {
6468
panic(err)
6569
}
6670
}
6771

72+
firstOutIsErrorType := 1 == fnType.NumOut() && utils.IsErrorType(fnType.Out(0))
73+
6874
return func(writer http.ResponseWriter, request *http.Request) {
6975

7076
// param of context
@@ -128,14 +134,15 @@ func Bind(fn interface{}, render Renderer) http.HandlerFunc {
128134
// nothing
129135
return
130136
case 1:
131-
// write response
132-
result = returnValues[0].Interface()
137+
if firstOutIsErrorType {
138+
err, _ = returnValues[0].Interface().(error)
139+
} else {
140+
result = returnValues[0].Interface()
141+
}
133142
case 2:
134143
// check error
135144
result = returnValues[0].Interface()
136-
if e, ok := returnValues[1].Interface().(error); ok && nil != e {
137-
err = e
138-
}
145+
err, _ = returnValues[1].Interface().(error)
139146
default:
140147
panic("unreachable here")
141148
}
@@ -149,7 +156,9 @@ func Bind(fn interface{}, render Renderer) http.HandlerFunc {
149156
func validMappingFunc(fnType reflect.Type) error {
150157
// func(ctx context.Context)
151158
// func(ctx context.Context) R
159+
// func(ctx context.Context) error
152160
// func(ctx context.Context, req T) R
161+
// func(ctx context.Context, req T) error
153162
// func(ctx context.Context, req T) (R, error)
154163
if !utils.IsFuncType(fnType) {
155164
return fmt.Errorf("%s: not a func", fnType.String())
@@ -174,13 +183,30 @@ func validMappingFunc(fnType reflect.Type) error {
174183
}
175184
}
176185

177-
if 0 < fnType.NumOut() && utils.IsErrorType(fnType.Out(0)) {
178-
return fmt.Errorf("%s: first output param type not be error", fnType.String())
179-
}
186+
switch fnType.NumOut() {
187+
case 0: // nothing
188+
case 1: // R | error
189+
case 2: // (R, error)
190+
if utils.IsErrorType(fnType.Out(0)) {
191+
return fmt.Errorf("%s: first output param type not be error", fnType.String())
192+
}
180193

181-
if 1 < fnType.NumOut() && !utils.IsErrorType(fnType.Out(1)) {
182-
return fmt.Errorf("%s: second output type (%s) must a error", fnType.String(), fnType.Out(1).String())
194+
if !utils.IsErrorType(fnType.Out(1)) {
195+
return fmt.Errorf("%s: second output type (%s) must a error", fnType.String(), fnType.Out(1).String())
196+
}
183197
}
184198

185199
return nil
186200
}
201+
202+
func warpHandlerCtx(handler http.HandlerFunc) http.HandlerFunc {
203+
return func(writer http.ResponseWriter, request *http.Request) {
204+
webCtx := &Context{Writer: writer, Request: request}
205+
handler.ServeHTTP(writer, requestWithCtx(request, webCtx))
206+
}
207+
}
208+
209+
func requestWithCtx(r *http.Request, webCtx *Context) *http.Request {
210+
ctx := WithContext(r.Context(), webCtx)
211+
return r.WithContext(ctx)
212+
}

web/context.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string,
186186
// Render writes the response headers and calls render.Render to render data.
187187
func (c *Context) Render(code int, render render.Renderer) error {
188188
if code > 0 {
189+
if len(c.Writer.Header().Get("Content-Type")) <= 0 {
190+
if contentType := render.ContentType(); len(contentType) > 0 {
191+
c.Writer.Header().Set("Content-Type", contentType)
192+
}
193+
}
189194
c.Writer.WriteHeader(code)
190195
}
191196
return render.Render(c.Writer)
@@ -203,7 +208,7 @@ func (c *Context) String(code int, format string, args ...interface{}) error {
203208

204209
// Data writes some data into the body stream and updates the HTTP code.
205210
func (c *Context) Data(code int, contentType string, data []byte) error {
206-
return c.Render(code, render.BinaryRenderer{ContentType: contentType, Data: data})
211+
return c.Render(code, render.BinaryRenderer{DataType: contentType, Data: data})
207212
}
208213

209214
// JSON serializes the given struct as JSON into the response body.

web/examples/greeting/main.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package main
1818

1919
import (
2020
"context"
21-
"fmt"
2221
"log/slog"
2322
"math/rand"
2423
"mime/multipart"
@@ -36,10 +35,10 @@ type Greeting struct {
3635
}
3736

3837
func (g *Greeting) OnInit(ctx context.Context) error {
39-
g.Server.Bind("/greeting", g.Greeting)
40-
g.Server.Bind("/health", g.Health)
41-
g.Server.Bind("/user/register/{username}/{password}", g.Register)
42-
g.Server.Bind("/user/password", g.UpdatePassword)
38+
g.Server.Get("/greeting", g.Greeting)
39+
g.Server.Get("/health", g.Health)
40+
g.Server.Post("/user/register/{username}/{password}", g.Register)
41+
g.Server.Post("/user/password", g.UpdatePassword)
4342

4443
g.Server.Use(func(handler http.Handler) http.Handler {
4544

@@ -61,11 +60,11 @@ func (g *Greeting) Greeting(ctx context.Context) string {
6160
return "greeting!!!"
6261
}
6362

64-
func (g *Greeting) Health(ctx context.Context) (string, error) {
63+
func (g *Greeting) Health(ctx context.Context) error {
6564
if 0 == rand.Int()%2 {
66-
return "", fmt.Errorf("health check failed")
65+
return web.Error(400, "health check failed")
6766
}
68-
return time.Now().String(), nil
67+
return nil
6968
}
7069

7170
func (g *Greeting) Register(

web/render/binary.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@ import (
2121
)
2222

2323
type BinaryRenderer struct {
24-
ContentType string
25-
Data []byte
24+
DataType string // Content-Type
25+
Data []byte
2626
}
2727

28-
func (b BinaryRenderer) Render(writer http.ResponseWriter) error {
29-
if header := writer.Header(); len(header.Get("Content-Type")) == 0 {
30-
contentType := "application/octet-stream"
31-
if len(b.ContentType) > 0 {
32-
contentType = b.ContentType
33-
}
34-
header.Set("Content-Type", contentType)
28+
func (b BinaryRenderer) ContentType() string {
29+
contentType := "application/octet-stream"
30+
if len(b.DataType) > 0 {
31+
contentType = b.DataType
3532
}
33+
return contentType
34+
}
35+
36+
func (b BinaryRenderer) Render(writer http.ResponseWriter) error {
3637
_, err := writer.Write(b.Data)
3738
return err
3839
}

web/render/binary_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ func TestBinaryRenderer(t *testing.T) {
3333

3434
w := httptest.NewRecorder()
3535

36-
err := BinaryRenderer{ContentType: "application/octet-stream", Data: data}.Render(w)
36+
render := BinaryRenderer{DataType: "application/octet-stream", Data: data}
37+
err := render.Render(w)
3738
assert.Nil(t, err)
3839

39-
assert.Equal(t, w.Header().Get("Content-Type"), "application/octet-stream")
40+
assert.Equal(t, render.ContentType(), "application/octet-stream")
4041
assert.Equal(t, w.Body.Bytes(), data)
4142
}

web/render/html.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ type HTMLRenderer struct {
2727
Data interface{}
2828
}
2929

30+
func (h HTMLRenderer) ContentType() string {
31+
return "text/html; charset=utf-8"
32+
}
33+
3034
func (h HTMLRenderer) Render(writer http.ResponseWriter) error {
31-
if header := writer.Header(); len(header.Get("Content-Type")) == 0 {
32-
header.Set("Content-Type", "text/html; charset=utf-8")
33-
}
3435
if len(h.Name) > 0 {
3536
return h.Template.ExecuteTemplate(writer, h.Name, h.Data)
3637
}

web/render/html_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ func TestHTMLRenderer(t *testing.T) {
3333
err := htmlRender.Render(w)
3434

3535
assert.Nil(t, err)
36-
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
36+
assert.Equal(t, htmlRender.ContentType(), "text/html; charset=utf-8")
3737
assert.Equal(t, w.Body.String(), "Hello asdklajhdasdd")
3838
}

web/render/json.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ type JsonRenderer struct {
2727
Data interface{}
2828
}
2929

30+
func (j JsonRenderer) ContentType() string {
31+
return "application/json; charset=utf-8"
32+
}
33+
3034
func (j JsonRenderer) Render(writer http.ResponseWriter) error {
31-
if header := writer.Header(); len(header.Get("Content-Type")) == 0 {
32-
header.Set("Content-Type", "application/json; charset=utf-8")
33-
}
3435
encoder := json.NewEncoder(writer)
3536
if len(j.Prefix) > 0 || len(j.Indent) > 0 {
3637
encoder.SetIndent(j.Prefix, j.Indent)

web/render/json_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ func TestJSONRenderer(t *testing.T) {
3131

3232
w := httptest.NewRecorder()
3333

34-
err := JsonRenderer{Data: data}.Render(w)
34+
render := JsonRenderer{Data: data}
35+
err := render.Render(w)
3536
assert.Nil(t, err)
3637

37-
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
38+
assert.Equal(t, render.ContentType(), "application/json; charset=utf-8")
3839
assert.Equal(t, w.Body.String(), "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n")
3940
}

web/render/redirect.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ type RedirectRenderer struct {
2727
Location string
2828
}
2929

30+
func (r RedirectRenderer) ContentType() string {
31+
return ""
32+
}
33+
3034
func (r RedirectRenderer) Render(writer http.ResponseWriter) error {
3135
if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
3236
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))

web/render/redirect_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func TestRedirectRenderer(t *testing.T) {
3737
w := httptest.NewRecorder()
3838
err = data1.Render(w)
3939
assert.Nil(t, err)
40+
assert.Equal(t, data1.ContentType(), "")
4041

4142
data2 := RedirectRenderer{
4243
Code: http.StatusOK,

web/render/renderer.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616

1717
package render
1818

19-
import "net/http"
19+
import (
20+
"net/http"
21+
)
2022

2123
// Renderer writes data with custom ContentType and headers.
2224
type Renderer interface {
25+
ContentType() string
2326
Render(writer http.ResponseWriter) error
2427
}

web/render/text.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ type TextRenderer struct {
2828
Args []interface{}
2929
}
3030

31+
func (t TextRenderer) ContentType() string {
32+
return "text/plain; charset=utf-8"
33+
}
34+
3135
func (t TextRenderer) Render(writer http.ResponseWriter) error {
32-
if header := writer.Header(); len(header.Get("Content-Type")) == 0 {
33-
header.Set("Content-Type", "text/plain; charset=utf-8")
34-
}
3536
_, err := io.Copy(writer, strings.NewReader(fmt.Sprintf(t.Format, t.Args...)))
3637
return err
3738
}

web/render/text_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ import (
2626
func TestTextRenderer(t *testing.T) {
2727
w := httptest.NewRecorder()
2828

29-
err := (TextRenderer{
29+
render := TextRenderer{
3030
Format: "hello %s %d",
3131
Args: []any{"bob", 2},
32-
}).Render(w)
32+
}
33+
34+
err := render.Render(w)
3335

3436
assert.Nil(t, err)
35-
assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
37+
assert.Equal(t, render.ContentType(), "text/plain; charset=utf-8")
3638
assert.Equal(t, w.Body.String(), "hello bob 2")
3739
}

web/render/xml.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ type XmlRenderer struct {
2727
Data interface{}
2828
}
2929

30-
func (x XmlRenderer) Render(writer http.ResponseWriter) error {
31-
if header := writer.Header(); len(header.Get("Content-Type")) == 0 {
32-
header.Set("Content-Type", "application/xml; charset=utf-8")
33-
}
30+
func (x XmlRenderer) ContentType() string {
31+
return "application/xml; charset=utf-8"
32+
}
3433

34+
func (x XmlRenderer) Render(writer http.ResponseWriter) error {
3535
encoder := xml.NewEncoder(writer)
3636
if len(x.Prefix) > 0 || len(x.Indent) > 0 {
3737
encoder.Indent(x.Prefix, x.Indent)

web/render/xml_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ func TestXmlRenderer(t *testing.T) {
5454
"foo": "bar",
5555
}
5656

57-
err := (XmlRenderer{Data: data}).Render(w)
57+
render := (XmlRenderer{Data: data})
58+
err := render.Render(w)
5859

5960
assert.Nil(t, err)
60-
assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8")
61+
assert.Equal(t, render.ContentType(), "application/xml; charset=utf-8")
6162
assert.Equal(t, w.Body.String(), "<map><foo>bar</foo></map>")
6263
}

0 commit comments

Comments
 (0)