Skip to content

Commit 3d0078a

Browse files
committed
use ifnotnil/x/http
1 parent 019df33 commit 3d0078a

File tree

11 files changed

+28
-186
lines changed

11 files changed

+28
-186
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ env:
5858
checks: vet staticcheck gofumpt goimports golangci-lint
5959

6060
.PHONY: ci-gen-n-format
61-
ci-gen-n-format: mockery goimports gofumpt golangci-lint-fmt
61+
ci-gen-n-format: mockery golangci-lint-fmt
6262
./scripts/git-check-dirty
6363

6464
.PHONY: ci-mod

api/sample.http

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ GET http://localhost:8888/about
22
Accept: application/json
33
Accept-Encoding: gzip, deflate, br
44

5+
###
6+
7+
@username = Yoda
8+
@password = _Named must your fear be before banish it you can_
9+
510
GET http://localhost:8888/echo
611
Accept: application/json
712
Accept-Encoding: gzip, deflate, br
813
Content-Type: application/json
9-
X-Auth-Key: _Named must your fear be before banish it you can_
14+
Authorization: Basic {{username}}:{{password}}
1015
{
1116
"glossary": {
1217
"title": "example glossary",

cmd/goboilerplate/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func main() {
5151
)
5252

5353
httpConf := httpx.ParseConfig(cnf)
54-
router := httpx.NewDefaultRouter(ctx, httpConf)
54+
router := httpx.NewDefaultRouter(ctx, httpConf, logger)
5555

5656
// init services / application
5757
server := httpx.StartListenAndServe(

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.24.2
44

55
require (
66
github.com/go-chi/chi/v5 v5.2.1
7+
github.com/ifnotnil/x/http v0.0.2
78
github.com/knadh/koanf/parsers/dotenv v1.1.0
89
github.com/knadh/koanf/parsers/yaml v1.0.0
910
github.com/knadh/koanf/providers/confmap v1.0.0

go.sum

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
66
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
77
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
88
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
9+
github.com/ifnotnil/x/http v0.0.2 h1:b4D76UAXWpWgB718Ai4NAkwvxuSnEW5hXRSp1APm1n8=
10+
github.com/ifnotnil/x/http v0.0.2/go.mod h1:ypXtxLtlMet+XudeN1YlIYWAT3vwfIuUfSALBnwvP8s=
911
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
1012
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
1113
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
@@ -22,8 +24,8 @@ github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmY
2224
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
2325
github.com/knadh/koanf/v2 v2.2.0 h1:FZFwd9bUjpb8DyCWARUBy5ovuhDs1lI87dOEn2K8UVU=
2426
github.com/knadh/koanf/v2 v2.2.0/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
25-
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
26-
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
27+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
28+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
2729
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2830
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
2931
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
@@ -32,14 +34,16 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
3234
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
3335
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3436
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
37+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
38+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
3539
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
3640
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
3741
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
3842
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
3943
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
4044
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
4145
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
42-
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
43-
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
46+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
47+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
4448
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4549
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/httpx/handlers.go

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,11 @@
11
package httpx
22

33
import (
4-
"encoding/json"
5-
"io"
64
"net/http"
7-
"strings"
85

96
"github.com/moukoublen/goboilerplate/build"
10-
"github.com/moukoublen/goboilerplate/internal/logx"
117
)
128

139
func AboutHandler(w http.ResponseWriter, r *http.Request) {
1410
RespondJSON(r.Context(), w, http.StatusOK, build.GetInfo())
1511
}
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: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,11 @@ package httpx
33
import (
44
"context"
55
"encoding/json"
6-
"errors"
7-
"io"
86
"net/http"
97

108
"github.com/moukoublen/goboilerplate/internal/logx"
119
)
1210

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-
27-
func ReadJSONRequest(r *http.Request, decodeTo any) (e error) {
28-
if r == nil || r.Body == nil || r.Body == http.NoBody {
29-
return nil
30-
}
31-
32-
defer DrainAndCloseRequest(r, &e)
33-
34-
if err := json.NewDecoder(r.Body).Decode(decodeTo); err != nil {
35-
return err
36-
}
37-
38-
return
39-
}
40-
4111
// RespondJSON renders a json response using a json encoder directly over the ResponseWriter.
4212
// That's why in most cases will end up sending chunked (`transfer-encoding: chunked`) response.
4313
func RespondJSON(ctx context.Context, w http.ResponseWriter, statusCode int, body any) {

internal/httpx/mock_http_client_test.go

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/httpx/outbound.go

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,14 @@
11
package httpx
22

33
import (
4-
"compress/flate"
5-
"compress/gzip"
6-
"encoding/json"
7-
"errors"
84
"fmt"
9-
"io"
10-
"net"
115
"net/http"
12-
"time"
136
)
147

15-
// 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, errOut *error) {
17-
if res == nil || res.Body == nil || res.Body == http.NoBody {
18-
return
19-
}
20-
21-
_, discardErr := io.Copy(io.Discard, res.Body)
22-
closeErr := res.Body.Close()
23-
24-
if discardErr != nil || closeErr != nil {
25-
*errOut = errors.Join(*errOut, discardErr, closeErr)
26-
}
27-
}
28-
298
type HTTPClient interface {
309
Do(req *http.Request) (*http.Response, error)
3110
}
3211

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) {
35-
res, err := c.Do(req)
36-
if err != nil {
37-
return err
38-
}
39-
defer DrainAndCloseResponse(res, &er)
40-
41-
if res.StatusCode >= http.StatusBadRequest {
42-
return NewStatusCodeError(res.StatusCode)
43-
}
44-
45-
reader := res.Body
46-
47-
var cer error
48-
switch res.Header.Get("Content-Encoding") {
49-
// case "br": // TODO
50-
case "gzip":
51-
reader, cer = gzip.NewReader(res.Body)
52-
case "deflate":
53-
reader = flate.NewReader(res.Body)
54-
}
55-
if cer != nil {
56-
return cer
57-
}
58-
59-
return json.NewDecoder(reader).Decode(output)
60-
}
61-
6212
func NewStatusCodeError(statusCode int) *StatusCodeError {
6313
return &StatusCodeError{statusCode: statusCode}
6414
}
@@ -83,22 +33,3 @@ func (s *StatusCodeError) Is(target error) bool {
8333

8434
return false
8535
}
86-
87-
// NewHTTPClient returns a new default http client.
88-
func NewHTTPClient(globalTimeout time.Duration) *http.Client {
89-
return &http.Client{
90-
Timeout: globalTimeout,
91-
Transport: &http.Transport{
92-
Proxy: http.ProxyFromEnvironment,
93-
DialContext: (&net.Dialer{
94-
Timeout: 30 * time.Second, //nolint:mnd,gomnd
95-
KeepAlive: 30 * time.Second, //nolint:mnd,gomnd
96-
}).DialContext,
97-
ForceAttemptHTTP2: true,
98-
MaxIdleConns: 100, //nolint:mnd,gomnd
99-
IdleConnTimeout: 90 * time.Second, //nolint:mnd,gomnd
100-
TLSHandshakeTimeout: 10 * time.Second, //nolint:mnd,gomnd
101-
ExpectContinueTimeout: 1 * time.Second, //nolint:mnd,gomnd
102-
},
103-
}
104-
}

internal/httpx/router.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/go-chi/chi/v5"
1212
"github.com/go-chi/chi/v5/middleware"
13+
xhttp "github.com/ifnotnil/x/http"
1314
"github.com/knadh/koanf/v2"
1415
"github.com/moukoublen/goboilerplate/internal/logx"
1516
)
@@ -38,7 +39,7 @@ func ParseConfig(cnf *koanf.Koanf) Config {
3839
}
3940

4041
// NewDefaultRouter returns a *chi.Mux with a default set of middlewares and an "/about" route.
41-
func NewDefaultRouter(ctx context.Context, c Config) *chi.Mux {
42+
func NewDefaultRouter(ctx context.Context, c Config, logger *slog.Logger) *chi.Mux {
4243
router := chi.NewRouter()
4344

4445
router.Use(middleware.Heartbeat("/ping"))
@@ -51,8 +52,14 @@ func NewDefaultRouter(ctx context.Context, c Config) *chi.Mux {
5152
router.Use(middleware.Timeout(c.GlobalInboundTimeout))
5253
}
5354

55+
router.Group(func(r chi.Router) {
56+
r.Use(middleware.BasicAuth("", map[string]string{
57+
"Yoda": "_Named must your fear be before banish it you can_",
58+
}))
59+
r.Get("/echo", xhttp.EchoHandler(logger))
60+
})
61+
5462
router.Get("/about", AboutHandler)
55-
router.Get("/echo", EchoHandler)
5663

5764
// for test purposes
5865
// router.Get("/panic", func(_ http.ResponseWriter, _ *http.Request) { panic("test panic") })

0 commit comments

Comments
 (0)