feat: update environment variables for portal configuration and improve HTTP request handling
rabbitprincess committed
Jun 5, 2026 at 00:21 UTC
2cb88a34759435ad9f199d6e2a02be958c7de891
2 files changed
+126
-127
portal/x402/handler.go
+122
-122
@@ -1,122 +1,122 @@
1
-package x402
2
-
3
-import (
4
- "context"
5
- "encoding/json"
6
- "errors"
7
- "fmt"
8
- "net/http"
9
- "strings"
10
-
11
- "github.com/gosuda/portal-tunnel/v2/types"
12
- "github.com/gosuda/portal-tunnel/v2/utils"
13
-)
14
-
15
-// USDCPaymentHandler serves both the wallet prepare endpoint and one protected resource.
16
-type USDCPaymentHandler struct {
17
- payment *Payment
18
- protectedPath string
19
- method string
20
- handler types.X402PaymentHandlerFunc
21
-}
22
-
23
-// NewUSDCPaymentHandler returns a complete HTTP handler for one Sui USDC x402 payment flow.
24
-func NewUSDCPaymentHandler(payment types.X402Payment, protectedPath, protectedMethod string, handler types.X402PaymentHandlerFunc) (*USDCPaymentHandler, error) {
25
- if handler == nil {
26
- return nil, errors.New("USDC payment handler is required")
27
- }
28
- paid, err := NewUSDCPayment(payment)
29
- if err != nil {
30
- return nil, err
31
- }
32
- payment = paid.payment
33
-
34
- protectedPath = strings.TrimSpace(protectedPath)
35
- if protectedPath == "" {
36
- protectedPath = payment.ResourcePath
37
- }
38
- if protectedPath == "" {
39
- return nil, errors.New("USDC payment protected path is required")
40
- }
41
- if !strings.HasPrefix(protectedPath, "/") {
42
- return nil, fmt.Errorf("USDC payment protected path %q must start with /", protectedPath)
43
- }
44
- protectedPath = utils.NormalizeURLPath(protectedPath)
45
- if protectedPath == types.X402PreparePath {
46
- return nil, fmt.Errorf("USDC payment protected path cannot be %s", types.X402PreparePath)
47
- }
48
-
49
- paid.payment.ResourcePath = protectedPath
50
- return &USDCPaymentHandler{
51
- payment: paid,
52
- protectedPath: protectedPath,
53
- method: strings.TrimSpace(protectedMethod),
54
- handler: handler,
55
- }, nil
56
-}
57
-
58
-// Payment returns the normalized payment contract used by this handler.
59
-func (h *USDCPaymentHandler) Payment() types.X402Payment {
60
- if h == nil || h.payment == nil {
61
- return types.X402Payment{}
62
- }
63
- return h.payment.payment
64
-}
65
-
66
-func (h *USDCPaymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
67
- if h == nil || h.payment == nil {
68
- http.Error(w, "payment is not configured", http.StatusInternalServerError)
69
- return
70
- }
71
-
72
- path := "/"
73
- if r.URL != nil {
74
- path = r.URL.Path
75
- }
76
- switch path {
77
- case types.X402PreparePath:
78
- if !utils.RequireMethod(w, r, http.MethodPost) {
79
- return
80
- }
81
- var req types.X402PreparePaymentRequest
82
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
83
- http.Error(w, "invalid payment prepare request", http.StatusBadRequest)
84
- return
85
- }
86
- h.payment.WritePrepare(w, r, req.Sender, h.protectedPath)
87
- case h.protectedPath:
88
- if h.method != "" && !utils.RequireMethod(w, r, h.method) {
89
- return
90
- }
91
- if h.handler == nil {
92
- http.NotFound(w, r)
93
- return
94
- }
95
-
96
- ctx := r.Context()
97
- cancel := func() {}
98
- payment := h.payment.payment
99
- if payment.RequestTimeout > 0 {
100
- ctx, cancel = context.WithTimeout(ctx, payment.RequestTimeout)
101
- }
102
- defer cancel()
103
-
104
- paymentPayload, ok := h.payment.Verify(ctx, w, r)
105
- if !ok {
106
- return
107
- }
108
- settled, ok := h.payment.Settle(ctx, w, r, paymentPayload)
109
- if !ok {
110
- return
111
- }
112
- utils.SetPaymentResponseHeaders(w.Header(), settled)
113
-
114
- h.handler(w, r, types.X402PaymentResult{
115
- TransactionID: strings.TrimSpace(settled.Transaction),
116
- Network: string(settled.Network),
117
- Payer: strings.TrimSpace(settled.Payer),
118
- })
119
- default:
120
- http.NotFound(w, r)
121
- }
122
-}
1
+package x402
2
+
3
+import (
4
+ "context"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "net/http"
9
+ "strings"
10
+
11
+ "github.com/gosuda/portal-tunnel/v2/types"
12
+ "github.com/gosuda/portal-tunnel/v2/utils"
13
+)
14
+
15
+// USDCPaymentHandler serves both the wallet prepare endpoint and one protected resource.
16
+type USDCPaymentHandler struct {
17
+ payment *Payment
18
+ protectedPath string
19
+ method string
20
+ handler types.X402PaymentHandlerFunc
21
+}
22
+
23
+// NewUSDCPaymentHandler returns a complete HTTP handler for one Sui USDC x402 payment flow.
24
+func NewUSDCPaymentHandler(payment types.X402Payment, protectedPath, protectedMethod string, handler types.X402PaymentHandlerFunc) (*USDCPaymentHandler, error) {
25
+ if handler == nil {
26
+ return nil, errors.New("USDC payment handler is required")
27
+ }
28
+ paid, err := NewUSDCPayment(payment)
29
+ if err != nil {
30
+ return nil, err
31
+ }
32
+ payment = paid.payment
33
+
34
+ protectedPath = strings.TrimSpace(protectedPath)
35
+ if protectedPath == "" {
36
+ protectedPath = payment.ResourcePath
37
+ }
38
+ if protectedPath == "" {
39
+ return nil, errors.New("USDC payment protected path is required")
40
+ }
41
+ if !strings.HasPrefix(protectedPath, "/") {
42
+ return nil, fmt.Errorf("USDC payment protected path %q must start with /", protectedPath)
43
+ }
44
+ protectedPath = utils.NormalizeURLPath(protectedPath)
45
+ if protectedPath == types.X402PreparePath {
46
+ return nil, fmt.Errorf("USDC payment protected path cannot be %s", types.X402PreparePath)
47
+ }
48
+
49
+ paid.payment.ResourcePath = protectedPath
50
+ return &USDCPaymentHandler{
51
+ payment: paid,
52
+ protectedPath: protectedPath,
53
+ method: strings.TrimSpace(protectedMethod),
54
+ handler: handler,
55
+ }, nil
56
+}
57
+
58
+// Payment returns the normalized payment contract used by this handler.
59
+func (h *USDCPaymentHandler) Payment() types.X402Payment {
60
+ if h == nil || h.payment == nil {
61
+ return types.X402Payment{}
62
+ }
63
+ return h.payment.payment
64
+}
65
+
66
+func (h *USDCPaymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
67
+ if h == nil || h.payment == nil {
68
+ http.Error(w, "payment is not configured", http.StatusInternalServerError)
69
+ return
70
+ }
71
+
72
+ path := "/"
73
+ if r.URL != nil {
74
+ path = r.URL.Path
75
+ }
76
+ switch path {
77
+ case types.X402PreparePath:
78
+ if !utils.RequireMethod(w, r, http.MethodPost) {
79
+ return
80
+ }
81
+ var req types.X402PreparePaymentRequest
82
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
83
+ http.Error(w, "invalid payment prepare request", http.StatusBadRequest)
84
+ return
85
+ }
86
+ h.payment.WritePrepare(w, r, req.Sender, h.protectedPath)
87
+ case h.protectedPath:
88
+ if h.method != "" && !utils.RequireMethod(w, r, h.method) {
89
+ return
90
+ }
91
+ if h.handler == nil {
92
+ http.NotFound(w, r)
93
+ return
94
+ }
95
+
96
+ ctx := r.Context()
97
+ cancel := func() {}
98
+ payment := h.payment.payment
99
+ if payment.RequestTimeout > 0 {
100
+ ctx, cancel = context.WithTimeout(ctx, payment.RequestTimeout)
101
+ }
102
+ defer cancel()
103
+
104
+ paymentPayload, ok := h.payment.Verify(ctx, w, r)
105
+ if !ok {
106
+ return
107
+ }
108
+ settled, ok := h.payment.Settle(ctx, w, r, paymentPayload)
109
+ if !ok {
110
+ return
111
+ }
112
+ utils.SetPaymentResponseHeaders(w.Header(), settled)
113
+
114
+ h.handler(w, r, types.X402PaymentResult{
115
+ TransactionID: strings.TrimSpace(settled.Transaction),
116
+ Network: string(settled.Network),
117
+ Payer: strings.TrimSpace(settled.Payer),
118
+ })
119
+ default:
120
+ http.NotFound(w, r)
121
+ }
122
+}
sdk/http.go
+4
-5
@@ -346,8 +346,8 @@ func (r *httpRoute) rewriteProxyRequest(pr *httputil.ProxyRequest) {
346
path := utils.NormalizeURLPath(pr.In.URL.Path)
347
rawPath := pr.In.URL.RawPath
348
if r.prefix != "/" {
349
- switch {
350
- case path == r.prefix:
349
+ switch path {
350
+ case r.prefix:
351
path = "/"
352
default:
353
path = strings.TrimPrefix(path, r.prefix)
@@ -357,10 +357,9 @@ func (r *httpRoute) rewriteProxyRequest(pr *httputil.ProxyRequest) {
357
}
358
359
if rawPath != "" {
360
- switch {
361
- case rawPath == r.prefix:
360
+ if rawPath == r.prefix {
361
rawPath = "/"
363
- case strings.HasPrefix(rawPath, r.prefix+"/"):
362
+ } else if strings.HasPrefix(rawPath, r.prefix+"/") {
363
rawPath = strings.TrimPrefix(rawPath, r.prefix)
364
}
365
}