Skip to content

Commit deacb88

Browse files
committed
extract handlers
1 parent 083c90d commit deacb88

File tree

5 files changed

+115
-39
lines changed

5 files changed

+115
-39
lines changed

api/sample.http

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Accept-Encoding: gzip, deflate, br
55
GET http://localhost:8888/echo
66
Accept: application/json
77
Accept-Encoding: gzip, deflate, br
8+
Content-Type: application/json
9+
X-Auth-Key: _Named must your fear be before banish it you can_
810
{
911
"glossary": {
1012
"title": "example glossary",

internal/httpx/handlers.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package httpx
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"strings"
8+
9+
"github.com/moukoublen/goboilerplate/build"
10+
"github.com/moukoublen/goboilerplate/internal/logx"
11+
)
12+
13+
func AboutHandler(w http.ResponseWriter, r *http.Request) {
14+
RespondJSON(r.Context(), w, http.StatusOK, build.GetInfo())
15+
}
16+
17+
func EchoHandler(w http.ResponseWriter, r *http.Request) {
18+
var cErr error
19+
defer DrainAndCloseRequest(r, &cErr)
20+
21+
logger := logx.GetFromContext(r.Context())
22+
23+
if h := r.Header.Get("X-Auth-Key"); h != `_Named must your fear be before banish it you can_` {
24+
w.WriteHeader(http.StatusUnauthorized)
25+
return
26+
}
27+
28+
b := map[string]any{}
29+
{
30+
b["method"] = r.Method
31+
b["proto"] = r.Proto
32+
b["protoMajor"] = r.ProtoMajor
33+
b["protoMinor"] = r.ProtoMinor
34+
35+
if r.URL != nil {
36+
u := map[string]any{}
37+
u["scheme"] = r.URL.Scheme
38+
u["opaque"] = r.URL.Opaque
39+
u["user"] = r.URL.User
40+
u["host"] = r.URL.Host
41+
u["path"] = r.URL.Path
42+
u["rawPath"] = r.URL.RawPath
43+
u["omitHost"] = r.URL.OmitHost
44+
u["forceQuery"] = r.URL.ForceQuery
45+
u["rawQuery"] = r.URL.RawQuery
46+
u["fragment"] = r.URL.Fragment
47+
u["rawFragment"] = r.URL.RawFragment
48+
}
49+
50+
headers := map[string]string{}
51+
for k := range r.Header {
52+
headers[k] = r.Header.Get(k)
53+
}
54+
b["headers"] = headers
55+
56+
if ct := r.Header.Get("Content-Type"); strings.Contains(ct, "application/json") {
57+
body := map[string]any{}
58+
_ = json.NewDecoder(r.Body).Decode(&body)
59+
b["body"] = body
60+
} else {
61+
body, _ := io.ReadAll(r.Body)
62+
b["body"] = body
63+
}
64+
65+
b["contentLength"] = r.ContentLength
66+
b["transferEncoding"] = r.TransferEncoding
67+
b["host"] = r.Host
68+
69+
trailer := map[string]string{}
70+
for k := range r.Trailer {
71+
trailer[k] = r.Header.Get(k)
72+
}
73+
b["trailer"] = trailer
74+
75+
b["remoteAddr"] = r.RemoteAddr
76+
b["requestURI"] = r.RequestURI
77+
}
78+
79+
w.Header().Add("Content-Type", "application/json")
80+
w.WriteHeader(http.StatusOK)
81+
err := json.NewEncoder(w).Encode(b)
82+
if err != nil {
83+
logger.Error("error during json encoding", logx.Error(err))
84+
}
85+
}

internal/httpx/inbound.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,26 @@ import (
1010
"github.com/moukoublen/goboilerplate/internal/logx"
1111
)
1212

13+
// DrainAndCloseRequest can be used (most probably with defer) from the server side to ensure that the http request body is fully consumed and closed.
14+
func DrainAndCloseRequest(r *http.Request, errOut *error) {
15+
if r == nil || r.Body == nil || r.Body == http.NoBody {
16+
return
17+
}
18+
19+
_, discardErr := io.Copy(io.Discard, r.Body)
20+
closeErr := r.Body.Close()
21+
22+
if discardErr != nil || closeErr != nil {
23+
*errOut = errors.Join(*errOut, discardErr, closeErr)
24+
}
25+
}
26+
1327
func ReadJSONRequest(r *http.Request, decodeTo any) (e error) {
1428
if r == nil || r.Body == nil || r.Body == http.NoBody {
1529
return nil
1630
}
1731

18-
defer func() {
19-
_, discardErr := io.Copy(io.Discard, r.Body)
20-
closeErr := r.Body.Close()
21-
e = errors.Join(
22-
e,
23-
discardErr,
24-
closeErr,
25-
)
26-
}()
32+
defer DrainAndCloseRequest(r, &e)
2733

2834
if err := json.NewDecoder(r.Body).Decode(decodeTo); err != nil {
2935
return err

internal/httpx/outbound.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package httpx
33
import (
44
"compress/flate"
55
"compress/gzip"
6-
"context"
76
"encoding/json"
7+
"errors"
88
"fmt"
99
"io"
1010
"net"
@@ -13,31 +13,30 @@ import (
1313
)
1414

1515
// DrainAndCloseResponse can be used (most probably with defer) from the client side to ensure that the http response body is consumed til the end and closed.
16-
func DrainAndCloseResponse(res *http.Response) {
17-
if res == nil || res.Body == nil {
16+
func DrainAndCloseResponse(res *http.Response, errOut *error) {
17+
if res == nil || res.Body == nil || res.Body == http.NoBody {
1818
return
1919
}
2020

21-
_, _ = io.Copy(io.Discard, res.Body)
22-
_ = res.Body.Close()
23-
}
21+
_, discardErr := io.Copy(io.Discard, res.Body)
22+
closeErr := res.Body.Close()
2423

25-
type InnerClient interface {
26-
Do(req *http.Request) (*http.Response, error)
24+
if discardErr != nil || closeErr != nil {
25+
*errOut = errors.Join(*errOut, discardErr, closeErr)
26+
}
2727
}
2828

29-
// Client wraps an http.Client and offers `DoAndDecode` as an extra function.
30-
type Client struct {
31-
InnerClient
29+
type HTTPClient interface {
30+
Do(req *http.Request) (*http.Response, error)
3231
}
3332

34-
// DoAndDecode performs the request (req) and tries to json decodes the response to output, it handles gzip and flate compression and also logs in debug level the http transaction (request/response).
35-
func (c *Client) DoAndDecode(_ context.Context, req *http.Request, output any) error {
33+
// DoAndDecode performs the request (req) and tries to json decodes the response to output, it handles gzip and flate compression.
34+
func DoAndDecode(c HTTPClient, req *http.Request, output any) (er error) {
3635
res, err := c.Do(req)
3736
if err != nil {
3837
return err
3938
}
40-
defer DrainAndCloseResponse(res)
39+
defer DrainAndCloseResponse(res, &er)
4140

4241
if res.StatusCode >= http.StatusBadRequest {
4342
return NewStatusCodeError(res.StatusCode)

internal/httpx/router.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/go-chi/chi/v5"
1212
"github.com/go-chi/chi/v5/middleware"
1313
"github.com/knadh/koanf/v2"
14-
"github.com/moukoublen/goboilerplate/build"
1514
"github.com/moukoublen/goboilerplate/internal/httpx/httplog"
1615
"github.com/moukoublen/goboilerplate/internal/logx"
1716
)
@@ -121,21 +120,6 @@ func LogRoutes(ctx context.Context, r *chi.Mux) {
121120
}
122121
}
123122

124-
func AboutHandler(w http.ResponseWriter, r *http.Request) {
125-
RespondJSON(r.Context(), w, http.StatusOK, build.GetInfo())
126-
}
127-
128-
func EchoHandler(w http.ResponseWriter, r *http.Request) {
129-
logger := logx.GetFromContext(r.Context())
130-
131-
req := map[string]any{}
132-
if err := ReadJSONRequest(r, &req); err != nil {
133-
logger.Error("error reading request", logx.Error(err))
134-
}
135-
136-
RespondJSON(r.Context(), w, http.StatusOK, req)
137-
}
138-
139123
// StartListenAndServe creates and runs server.ListenAndServe in a separate go routine.
140124
// Any error produced by ListenAndServe will be sent to fatalErrCh.
141125
// It returns the server struct.

0 commit comments

Comments
 (0)