Skip to content

Commit

Permalink
Using new HTTP router in 1.22 and removing httprouter package (#684)
Browse files Browse the repository at this point in the history
<!--
Thanks for taking precious time for making a PR.

Before creating a pull request, please make sure:
- Your PR solves one problem for which an issue exist and a solution has
been discussed
- You have read the guide for contributing
- See
https://github.com/beatlabs/patron/blob/master/README.md#how-to-contribute
- You signed all your commits (otherwise we won't be able to merge the
PR)
  - See https://github.com/beatlabs/patron/blob/master/SIGNYOURWORK.md
- You added unit tests for the new functionality
- You mention in the PR description which issue it is addressing, e.g.
"Resolves #123"
-->

## Which problem is this PR solving?

Resolves #672.

## Short description of the changes

- Upgraded Go to 1.22
- removed httprouter
- introduced new router
- Changed to codecov for code coverage
  • Loading branch information
mantzas authored Apr 4, 2024
1 parent a13fc62 commit 409ecf2
Show file tree
Hide file tree
Showing 43 changed files with 131 additions and 1,889 deletions.
32 changes: 13 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,32 @@ jobs:
run: make lint

build:
name: CI on Go ${{ matrix.go-image-version}}
strategy:
matrix:
go-image-version: ["1.21"]
name: CI on Go
runs-on: self-hosted
steps:
- name: Set up Go ${{ matrix.go-image-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-image-version }}

- name: Check out source code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"

- name: Start dependencies
run: make deps-start

- name: Running CI
run: |
sleep 30
make ci
- name: Convert coverage file to lcov
run: |
GO111MODULE=off go get -u github.com/jandelgado/gcov2lcov
$(go env GOPATH)/bin/gcov2lcov -infile=coverage.txt -outfile=coverage.lcov
- name: Coveralls
uses: coverallsapp/github-action@master
- name: Codecov
uses: codecov/codecov-action@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./coverage.lcov
fail_ci_if_error: true # optional (default = false)
files: ./coverage.txt
name: codecov-umbrella # optional
token: ${{ secrets.CODECOV_TOKEN }} # required

- name: e2e
run: |
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ fmtcheck:
@sh -c "'$(CURDIR)/script/gofmtcheck.sh'"

lint: fmtcheck
$(DOCKER) run --env=GOFLAGS=-mod=vendor --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.55.2 golangci-lint -v run
$(DOCKER) run --env=GOFLAGS=-mod=vendor --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.57.2 golangci-lint -v run

deeplint: fmtcheck
$(DOCKER) run --env=GOFLAGS=-mod=vendor --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.55.2 golangci-lint run --exclude-use-default=false --enable-all -D dupl --build-tags integration
$(DOCKER) run --env=GOFLAGS=-mod=vendor --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.57.2 golangci-lint run --exclude-use-default=false --enable-all -D dupl --build-tags integration

modsync: fmtcheck
go mod tidy && go mod vendor
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# patron ![CI](https://github.com/beatlabs/patron/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/beatlabs/patron/badge.svg?branch=master)](https://coveralls.io/github/beatlabs/patron?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/beatlabs/patron)](https://goreportcard.com/report/github.com/beatlabs/patron) [![GoDoc](https://godoc.org/github.com/beatlabs/patron?status.svg)](https://godoc.org/github.com/beatlabs/patron) ![GitHub release](https://img.shields.io/github/release/beatlabs/patron.svg)[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbeatlabs%2Fpatron.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbeatlabs%2Fpatron?ref=badge_shield&issueType=license)
# patron ![CI](https://github.com/beatlabs/patron/workflows/CI/badge.svg) [![codecov](https://codecov.io/gh/beatlabs/patron/graph/badge.svg?token=sxY15rXW1X)](https://codecov.io/gh/beatlabs/patron) [![Go Report Card](https://goreportcard.com/badge/github.com/beatlabs/patron)](https://goreportcard.com/report/github.com/beatlabs/patron) [![GoDoc](https://godoc.org/github.com/beatlabs/patron?status.svg)](https://godoc.org/github.com/beatlabs/patron) ![GitHub release](https://img.shields.io/github/release/beatlabs/patron.svg)[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbeatlabs%2Fpatron.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbeatlabs%2Fpatron?ref=badge_shield&issueType=license)

Patron is a framework for creating microservices, originally created by Sotiris Mantzaris (<https://github.com/mantzas>). This fork is maintained by Beat Engineering (<https://thebeat.co>)

Expand All @@ -17,7 +17,11 @@ The entry point of the framework is the `Service`. The `Service` uses `Component
- logging

`Patron` provides the same defaults for making the usage as simple as possible.
`Patron` needs Go 1.21 as a minimum.
`Patron` needs Go 1.22 as a minimum.

## Code coverage

![Code coverage](https://codecov.io/gh/beatlabs/patron/graphs/icicle.svg?token=sxY15rXW1X)

## Table of Contents

Expand Down
2 changes: 1 addition & 1 deletion client/es/elasticsearch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func TestEsQuery(t *testing.T) {

responseMsg := `[{"acknowledged": true, "shards_acknowledged": true, "index": "test"}]`
ctx, indexName := context.Background(), "test_index"
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write([]byte(responseMsg))
assert.NoError(t, err)
}))
Expand Down
10 changes: 5 additions & 5 deletions client/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func TestTracedClient_Do_Redirect(t *testing.T) {
http.Redirect(w, r, "https://google.com", http.StatusSeeOther)
}))
defer ts.Close()
c, err := New(WithCheckRedirect(func(req *http.Request, via []*http.Request) error {
c, err := New(WithCheckRedirect(func(_ *http.Request, _ []*http.Request) error {
return errors.New("stop redirects")
}))
assert.NoError(t, err)
Expand All @@ -116,7 +116,7 @@ func TestNew(t *testing.T) {
WithTimeout(time.Second),
WithCircuitBreaker("test", circuitbreaker.Setting{}),
WithTransport(&http.Transport{}),
WithCheckRedirect(func(req *http.Request, via []*http.Request) error { return nil }),
WithCheckRedirect(func(_ *http.Request, _ []*http.Request) error { return nil }),
}}, wantErr: false},
{name: "failure, invalid timeout", args: args{oo: []OptionFunc{WithTimeout(0 * time.Second)}}, wantErr: true},
{name: "failure, invalid circuit breaker", args: args{[]OptionFunc{WithCircuitBreaker("", circuitbreaker.Setting{})}}, wantErr: true},
Expand All @@ -139,12 +139,12 @@ func TestNew(t *testing.T) {

func TestDecompress(t *testing.T) {
const msg = "hello, client!"
ts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = fmt.Fprint(w, msg)
}))
defer ts1.Close()

ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
var b bytes.Buffer
cw := gzip.NewWriter(&b)
_, err := cw.Write([]byte(msg))
Expand All @@ -162,7 +162,7 @@ func TestDecompress(t *testing.T) {
}))
defer ts2.Close()

ts3 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ts3 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
var b bytes.Buffer
cw, _ := flate.NewWriter(&b, 8)
_, err := cw.Write([]byte(msg))
Expand Down
2 changes: 1 addition & 1 deletion client/http/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestCheckRedirect_Nil(t *testing.T) {
}

func TestCheckRedirect(t *testing.T) {
cr := func(req *http.Request, via []*http.Request) error {
cr := func(_ *http.Request, _ []*http.Request) error {
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion client/sns/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ type SNSAPI interface {
}

func createConfig(awsServiceID, awsRegion, awsEndpoint string) (aws.Config, error) {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, _ ...interface{}) (aws.Endpoint, error) {
if service == awsServiceID && region == awsRegion {
return aws.Endpoint{
URL: awsEndpoint,
Expand Down
2 changes: 1 addition & 1 deletion client/sns/publisher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (s *stubSNSAPI) Publish(_ context.Context, _ *sns.PublishInput, _ ...func(*

func ExamplePublisher() {
// Create the SNS API with the required config, credentials, etc.
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, _ ...interface{}) (aws.Endpoint, error) {
if service == sns.ServiceID && region == "eu-west-1" {
return aws.Endpoint{
URL: "http://localhost:4575",
Expand Down
2 changes: 1 addition & 1 deletion client/sqs/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func createSQSAPI(region, endpoint string) (*sqs.Client, error) {
}

func createConfig(awsServiceID, awsRegion, awsEndpoint string) (aws.Config, error) {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, _ ...interface{}) (aws.Endpoint, error) {
if service == awsServiceID && region == awsRegion {
return aws.Endpoint{
URL: awsEndpoint,
Expand Down
2 changes: 1 addition & 1 deletion client/sqs/publisher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func NewMockTracerInjector() MockTracerInjector {
}

func ExamplePublisher() {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, _ ...interface{}) (aws.Endpoint, error) {
if service == sqs.ServiceID && region == "eu-west-1" {
return aws.Endpoint{
URL: "http://localhost:4576",
Expand Down
6 changes: 6 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
coverage:
status:
project:
default:
target: 80% # the required coverage value
threshold: 2% # the leniency in hitting the target
4 changes: 2 additions & 2 deletions component/http/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func extractRequestHeaders(header string, minAge, maxFresh int64) *control {
if adjusted {
wrn = append(wrn, fmt.Sprintf("max-age=%d", minAge))
}
cfg.validators = append(cfg.validators, func(age, maxAge int64) (bool, validationContext) {
cfg.validators = append(cfg.validators, func(age, _ int64) (bool, validationContext) {
return age <= value, maxAgeValidation
})
case controlMinFresh:
Expand Down Expand Up @@ -286,7 +286,7 @@ func extractRequestHeaders(header string, minAge, maxFresh int64) *control {
no storage whatsoever
*/
wrn = append(wrn, fmt.Sprintf("max-age=%d", minAge))
cfg.validators = append(cfg.validators, func(age, maxAge int64) (bool, validationContext) {
cfg.validators = append(cfg.validators, func(age, _ int64) (bool, validationContext) {
return age <= minAge, maxAgeValidation
})
case controlOnlyIfCached:
Expand Down
6 changes: 3 additions & 3 deletions component/http/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ func TestCache_WithHandlerErrorWithoutHeaders(t *testing.T) {
requestParams: newRequestAt(11),
routeConfig: routeConfig{
path: rc.path,
hnd: func(now int64, key string) *response {
hnd: func(_ int64, _ string) *response {
return &response{
Err: hndErr,
}
Expand Down Expand Up @@ -1010,7 +1010,7 @@ func TestCache_WithHandlerErr(t *testing.T) {
rc := routeConfig{
path: "/",
age: Age{Min: 10 * time.Second, Max: 10 * time.Second},
hnd: func(now int64, key string) *response {
hnd: func(_ int64, _ string) *response {
return &response{
Err: hndErr,
}
Expand Down Expand Up @@ -1587,7 +1587,7 @@ func assertCache(t *testing.T, args [][]testArgs) {
// create a test request handler
// that returns the current time instant times '10' multiplied by the VALUE parameter in the request
exec := func(request requestParams) func(now int64, key string) *response {
return func(now int64, key string) *response {
return func(now int64, _ string) *response {
i, err := strconv.Atoi(strings.Split(request.query, "=")[1])
if err != nil {
return &response{
Expand Down
8 changes: 4 additions & 4 deletions component/http/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const (
NotReady ReadyStatus = 2

// AlivePath of the component.
AlivePath = "/alive"
AlivePath = "GET /alive"
// ReadyPath of the component.
ReadyPath = "/ready"
ReadyPath = "GET /ready"
)

// ReadyCheckFunc defines a function type for implementing a readiness check.
Expand All @@ -51,7 +51,7 @@ func LivenessCheckRoute(acf LivenessCheckFunc) (*Route, error) {
}
}

return NewRoute(http.MethodGet, AlivePath, f)
return NewRoute(AlivePath, f)
}

// ReadyCheckRoute returns a route for ready checks.
Expand All @@ -69,5 +69,5 @@ func ReadyCheckRoute(rcf ReadyCheckFunc) (*Route, error) {
}
}

return NewRoute(http.MethodGet, ReadyPath, f)
return NewRoute(ReadyPath, f)
}
6 changes: 2 additions & 4 deletions component/http/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ func Test_aliveCheckRoute(t *testing.T) {
t.Parallel()
route, err := LivenessCheckRoute(tt.acf)
assert.NoError(t, err)
assert.Equal(t, http.MethodGet, route.method)
assert.Equal(t, "/alive", route.path)
assert.Equal(t, "GET /alive", route.path)

resp := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodGet, "/alive", nil)
Expand Down Expand Up @@ -55,8 +54,7 @@ func Test_readyCheckRoute(t *testing.T) {
t.Parallel()
route, err := ReadyCheckRoute(tt.rcf)
assert.NoError(t, err)
assert.Equal(t, http.MethodGet, route.method)
assert.Equal(t, "/ready", route.path)
assert.Equal(t, "GET /ready", route.path)

resp := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodGet, "/ready", nil)
Expand Down
14 changes: 5 additions & 9 deletions component/http/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func initHTTPServerMetrics() {
Name: "handled_total",
Help: "Total number of HTTP responses served by the server.",
},
[]string{"method", "path", "status_code"},
[]string{"path", "status_code"},
)
prometheus.MustRegister(httpStatusTracingHandledMetric)
httpStatusTracingLatencyMetric = prometheus.NewHistogramVec(
Expand All @@ -236,18 +236,14 @@ func initHTTPServerMetrics() {
Name: "handled_seconds",
Help: "Latency of a completed HTTP response served by the server.",
},
[]string{"method", "path", "status_code"})
[]string{"path", "status_code"})
prometheus.MustRegister(httpStatusTracingLatencyMetric)
}

// NewRequestObserver creates a Func that captures status code and duration metrics about the responses returned;
// metrics are exposed via Prometheus.
// This middleware is enabled by default.
func NewRequestObserver(method, path string) (Func, error) {
if method == "" {
return nil, errors.New("method cannot be empty")
}

func NewRequestObserver(path string) (Func, error) {
if path == "" {
return nil, errors.New("path cannot be empty")
}
Expand All @@ -265,12 +261,12 @@ func NewRequestObserver(method, path string) (Func, error) {
status := strconv.Itoa(lw.Status())

httpStatusCounter := trace.Counter{
Counter: httpStatusTracingHandledMetric.WithLabelValues(method, path, status),
Counter: httpStatusTracingHandledMetric.WithLabelValues(path, status),
}
httpStatusCounter.Inc(r.Context())

httpLatencyMetricObserver := trace.Histogram{
Observer: httpStatusTracingLatencyMetric.WithLabelValues(method, path, status),
Observer: httpStatusTracingLatencyMetric.WithLabelValues(path, status),
}
httpLatencyMetricObserver.Observe(r.Context(), time.Since(now).Seconds())
})
Expand Down
Loading

0 comments on commit 409ecf2

Please sign in to comment.