Skip to content

Commit 49a3b7a

Browse files
committed
Change faucet middleware API
1 parent 683252e commit 49a3b7a

File tree

9 files changed

+322
-131
lines changed

9 files changed

+322
-131
lines changed

faucet.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,13 @@ func NewFaucet(
9999
// Branch off another route group, so they don't influence
100100
// "standard" routes like health
101101
f.mux.Group(func(r chi.Router) {
102-
// Apply user middlewares
103-
for _, middleware := range f.middlewares {
104-
r.Use(middleware)
105-
}
106-
107-
// Apply standard and custom route handlers
108-
for _, handler := range f.handlers {
109-
r.Post(handler.Pattern, handler.HandlerFunc)
102+
for _, h := range f.handlers {
103+
r.Post(h.Pattern,
104+
wrapJSONRPC(
105+
h.HandlerFunc,
106+
f.middlewares...,
107+
),
108+
)
110109
}
111110
})
112111

faucet_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package faucet
22

33
import (
4-
"net/http"
4+
"context"
55
"testing"
66

77
"github.com/gnolang/faucet/config"
8+
"github.com/gnolang/faucet/spec"
89
"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
910
"github.com/gnolang/gno/tm2/pkg/crypto"
1011
"github.com/gnolang/gno/tm2/pkg/std"
@@ -75,8 +76,8 @@ func TestFaucet_NewFaucet(t *testing.T) {
7576
handlers := []Handler{
7677
{
7778
Pattern: "/hello",
78-
HandlerFunc: func(_ http.ResponseWriter, _ *http.Request) {
79-
// Empty handler
79+
HandlerFunc: func(_ context.Context, _ *spec.BaseJSONRequest) *spec.BaseJSONResponse {
80+
return spec.NewJSONResponse(0, nil, nil) // empty handler
8081
},
8182
},
8283
}

handler.go

Lines changed: 84 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
package faucet
22

33
import (
4+
"context"
5+
"encoding/json"
46
"errors"
57
"fmt"
68
"io"
79
"net/http"
810
"regexp"
911

1012
"github.com/gnolang/faucet/spec"
11-
"github.com/gnolang/faucet/writer"
12-
httpWriter "github.com/gnolang/faucet/writer/http"
1313
"github.com/gnolang/gno/tm2/pkg/crypto"
1414
"github.com/gnolang/gno/tm2/pkg/std"
1515
)
1616

17-
const (
18-
unableToHandleRequest = "unable to handle faucet request"
19-
faucetSuccess = "successfully executed faucet transfer"
20-
)
17+
const faucetSuccess = "successfully executed faucet transfer"
2118

2219
const DefaultDripMethod = "drip" // the default JSON-RPC method for a faucet drip
2320

@@ -27,81 +24,106 @@ var (
2724
errInvalidMethod = errors.New("unknown RPC method call")
2825
)
2926

30-
// drip is a single Faucet transfer request
31-
type drip struct {
32-
amount std.Coins
33-
to crypto.Address
34-
}
27+
// wrapJSONRPC wraps the given handler and middlewares into a JSON-RPC 2.0 pipeline
28+
func wrapJSONRPC(handlerFn HandlerFunc, mws ...Middleware) http.HandlerFunc {
29+
callChain := chainMiddlewares(mws...)(handlerFn)
3530

36-
var amountRegex = regexp.MustCompile(`^\d+ugnot$`)
31+
return func(w http.ResponseWriter, r *http.Request) {
32+
// Grab the request(s)
33+
requests, err := parseRequests(r.Body)
34+
if err != nil {
35+
http.Error(
36+
w,
37+
fmt.Sprintf("unable to read request: %s", err.Error()),
38+
http.StatusBadRequest,
39+
)
3740

38-
// defaultHTTPHandler is the default faucet transfer handler
39-
func (f *Faucet) defaultHTTPHandler(w http.ResponseWriter, r *http.Request) {
40-
// Load the requests
41-
requestBody, readErr := io.ReadAll(r.Body)
42-
if readErr != nil {
43-
http.Error(
44-
w,
45-
"unable to read request",
46-
http.StatusBadRequest,
47-
)
41+
return
42+
}
4843

49-
return
50-
}
44+
var (
45+
ctx = r.Context()
5146

52-
// Extract the requests
53-
requests, err := spec.ExtractBaseRequests(requestBody)
54-
if err != nil {
55-
http.Error(
56-
w,
57-
"invalid request body",
58-
http.StatusBadRequest,
47+
responses = make(spec.BaseJSONResponses, 0)
5948
)
6049

61-
return
62-
}
50+
for _, req := range requests {
51+
// Make sure it's a valid base request
52+
if !spec.IsValidBaseRequest(req) {
53+
responses = append(responses, spec.NewJSONResponse(
54+
req.ID,
55+
nil,
56+
spec.NewJSONError("invalid JSON-RPC 2.0 request", spec.InvalidRequestErrorCode),
57+
))
58+
59+
continue
60+
}
61+
62+
// Parse the request.
63+
// This executes all the middlewares, and
64+
// finally the base handler for the endpoint
65+
resp := callChain(ctx, req)
66+
67+
responses = append(responses, resp)
68+
}
69+
70+
w.Header().Set("Content-Type", JSONMimeType)
71+
72+
// Create the encoder
73+
enc := json.NewEncoder(w)
74+
75+
if len(responses) == 1 {
76+
// Write the JSON response as a single response
77+
_ = enc.Encode(responses[0]) //nolint:errcheck // Fine to leave unchecked
6378

64-
// Handle the requests
65-
w.Header().Set("Content-Type", jsonMimeType)
66-
f.handleRequest(
67-
httpWriter.New(f.logger, w),
68-
requests,
69-
)
79+
return
80+
}
81+
82+
// Write the JSON response as a batch
83+
_ = enc.Encode(responses) //nolint:errcheck // Fine to leave unchecked
84+
}
7085
}
7186

72-
// handleRequest is the common default faucet handler
73-
func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests spec.BaseJSONRequests) {
74-
// Parse all JSON-RPC requests
75-
responses := make(spec.BaseJSONResponses, len(requests))
87+
// chainMiddlewares combines the given middlewares
88+
func chainMiddlewares(mw ...Middleware) Middleware {
89+
return func(final HandlerFunc) HandlerFunc {
90+
h := final
7691

77-
for i, req := range requests {
78-
f.logger.Debug("incoming request", "request", req)
92+
for i := len(mw) - 1; i >= 0; i-- {
93+
h = mw[i](h)
94+
}
7995

80-
responses[i] = f.handleSingleRequest(req)
96+
return h
8197
}
98+
}
8299

83-
if len(responses) == 1 {
84-
// Write the JSON response as a single response
85-
writer.WriteResponse(responses[0])
100+
// parseRequests parses the JSON-RPC requests from the request body
101+
func parseRequests(body io.Reader) (spec.BaseJSONRequests, error) {
102+
// Load the requests
103+
requestBody, readErr := io.ReadAll(body)
104+
if readErr != nil {
105+
return nil, fmt.Errorf("unable to read request: %w", readErr)
106+
}
86107

87-
return
108+
// Extract the requests
109+
requests, err := spec.ExtractBaseRequests(requestBody)
110+
if err != nil {
111+
return nil, fmt.Errorf("invalid request body: %w", err)
88112
}
89113

90-
// Write the JSON response as a batch
91-
writer.WriteResponse(responses)
114+
return requests, nil
92115
}
93116

94-
// handleSingleRequest validates and executes one drip request
95-
func (f *Faucet) handleSingleRequest(req *spec.BaseJSONRequest) *spec.BaseJSONResponse {
96-
// Make sure it's a valid base request
97-
if !spec.IsValidBaseRequest(req) {
98-
return spec.NewJSONResponse(
99-
req.ID,
100-
nil,
101-
spec.NewJSONError("invalid JSON-RPC 2.0 request", spec.InvalidRequestErrorCode),
102-
)
103-
}
117+
// drip is a single Faucet transfer request
118+
type drip struct {
119+
amount std.Coins
120+
to crypto.Address
121+
}
104122

123+
var amountRegex = regexp.MustCompile(`^\d+ugnot$`)
124+
125+
// defaultHTTPHandler is the default faucet transfer handler
126+
func (f *Faucet) defaultHTTPHandler(_ context.Context, req *spec.BaseJSONRequest) *spec.BaseJSONResponse {
105127
// Make sure the method call on "/" is "drip"
106128
if req.Method != DefaultDripMethod {
107129
return spec.NewJSONResponse(

0 commit comments

Comments
 (0)