Skip to content

Commit 7f78179

Browse files
committed
feat: cleanup, optimizations, panic handling
1 parent b86d55b commit 7f78179

File tree

8 files changed

+269
-196
lines changed

8 files changed

+269
-196
lines changed

cmd/mirror/main.go cmd/go-proxy/main.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"log"
66
"runtime"
77

8-
"github.com/smrz2001/go-mirror/common/config"
9-
"github.com/smrz2001/go-mirror/common/container"
10-
"github.com/smrz2001/go-mirror/common/logging"
11-
"github.com/smrz2001/go-mirror/server"
8+
"github.com/3box/go-proxy/common/config"
9+
"github.com/3box/go-proxy/common/container"
10+
"github.com/3box/go-proxy/common/logging"
11+
"github.com/3box/go-proxy/server"
1212
)
1313

1414
func main() {

common/config/config.go

+30-7
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,38 @@ package config
22

33
import (
44
"strings"
5+
"time"
56

67
"github.com/spf13/viper"
78

8-
"github.com/smrz2001/go-mirror/common/logging"
9+
"github.com/3box/go-proxy/common/logging"
10+
)
11+
12+
const (
13+
defaultProxyListenPort = "8080"
14+
defaultMetricsListenPort = "9464"
15+
defaultDialTimeout = 30 * time.Second
16+
defaultIdleTimeout = 90 * time.Second
17+
defaultMirrorTimeout = 30 * time.Second
918
)
1019

1120
type Config struct {
12-
Proxy ProxyConfig
21+
Proxy ProxyConfig
22+
Metrics MetricsConfig
1323
}
1424

1525
type ProxyConfig struct {
16-
TargetURL string
17-
MirrorURL string
18-
ListenAddr string
19-
TLSEnabled bool
26+
TargetURL string
27+
MirrorURL string
28+
ListenPort string
29+
DialTimeout time.Duration
30+
IdleTimeout time.Duration
31+
MirrorTimeout time.Duration
32+
}
33+
34+
type MetricsConfig struct {
35+
Enabled bool
36+
ListenPort string
2037
}
2138

2239
func LoadConfig(logger logging.Logger) (*Config, error) {
@@ -26,9 +43,15 @@ func LoadConfig(logger logging.Logger) (*Config, error) {
2643
// This was necessary to get viper to recognize the nested struct fields
2744
viper.EnvKeyReplacer(strings.NewReplacer(".", "_")),
2845
)
29-
v.SetEnvPrefix("GO_MIRROR")
46+
v.SetEnvPrefix("GO_PROXY")
3047
v.AutomaticEnv()
3148

49+
v.SetDefault("Proxy.ListenPort", defaultProxyListenPort)
50+
v.SetDefault("Proxy.DialTimeout", defaultDialTimeout)
51+
v.SetDefault("Proxy.IdleTimeout", defaultIdleTimeout)
52+
v.SetDefault("Proxy.MirrorTimeout", defaultMirrorTimeout)
53+
v.SetDefault("Metrics.ListenPort", defaultMetricsListenPort)
54+
3255
// Unmarshal environment variables into the config struct
3356
var cfg Config
3457
if err := v.Unmarshal(&cfg); err != nil {

common/container/container.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import (
55

66
"go.uber.org/dig"
77

8-
"github.com/smrz2001/go-mirror/common/config"
9-
"github.com/smrz2001/go-mirror/common/logging"
10-
"github.com/smrz2001/go-mirror/common/metric"
11-
"github.com/smrz2001/go-mirror/controllers"
12-
"github.com/smrz2001/go-mirror/server"
8+
"github.com/3box/go-proxy/common/config"
9+
"github.com/3box/go-proxy/common/logging"
10+
"github.com/3box/go-proxy/common/metric"
11+
"github.com/3box/go-proxy/controllers"
12+
"github.com/3box/go-proxy/server"
1313
)
1414

1515
func BuildContainer(ctx context.Context) (*dig.Container, error) {

common/metric/metric.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type MetricService interface {
1616
}
1717

1818
const (
19-
MetricProxyRequest = "proxy_request"
20-
MetricMirrorRequest = "mirror_request"
19+
MetricProxyRequest = "proxy_request"
20+
MetricMirrorRequest = "mirror_request"
21+
MetricPanicRecovered = "panic_recovered"
2122
)

common/metric/otel.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
"github.com/gin-gonic/gin"
1717

18-
"github.com/smrz2001/go-mirror/common/logging"
18+
"github.com/3box/go-proxy/common/logging"
1919
)
2020

2121
const serviceName = "go-mirror"

controllers/proxy_controller.go

+99-125
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414

1515
"github.com/gin-gonic/gin"
1616

17-
"github.com/smrz2001/go-mirror/common/config"
18-
"github.com/smrz2001/go-mirror/common/logging"
19-
"github.com/smrz2001/go-mirror/common/metric"
17+
"github.com/3box/go-proxy/common/config"
18+
"github.com/3box/go-proxy/common/logging"
19+
"github.com/3box/go-proxy/common/metric"
2020
)
2121

2222
type ProxyController interface {
@@ -36,6 +36,23 @@ type proxyController struct {
3636
transport *http.Transport
3737
}
3838

39+
type requestType string
40+
41+
const (
42+
proxyRequest requestType = "proxy"
43+
mirrorRequest = "mirror"
44+
)
45+
46+
// Create a struct to hold request context
47+
type requestContext struct {
48+
reqType requestType
49+
ginContext *gin.Context
50+
request *http.Request
51+
bodyBytes []byte
52+
startTime time.Time
53+
targetURL *url.URL
54+
}
55+
3956
func NewProxyController(
4057
ctx context.Context,
4158
cfg *config.Config,
@@ -57,15 +74,10 @@ func NewProxyController(
5774
transport := &http.Transport{
5875
Proxy: http.ProxyFromEnvironment,
5976
DialContext: (&net.Dialer{
60-
Timeout: 30 * time.Second,
61-
KeepAlive: 30 * time.Second,
77+
Timeout: cfg.Proxy.DialTimeout,
6278
}).DialContext,
63-
ForceAttemptHTTP2: true,
64-
MaxIdleConns: 100,
65-
MaxIdleConnsPerHost: 10,
66-
IdleConnTimeout: 90 * time.Second,
67-
TLSHandshakeTimeout: 10 * time.Second,
68-
ExpectContinueTimeout: 1 * time.Second,
79+
ForceAttemptHTTP2: true,
80+
IdleConnTimeout: cfg.Proxy.IdleTimeout,
6981
}
7082

7183
return &proxyController{
@@ -87,149 +99,111 @@ func (_this proxyController) proxyRequest(c *gin.Context) {
8799
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read request"})
88100
return
89101
}
102+
// Ignore error since we are closing the body anyway
103+
_ = c.Request.Body.Close()
90104

91-
// Close the original body
92-
c.Request.Body.Close()
105+
// Handle proxy request
106+
_this.handleRequest(requestContext{
107+
reqType: proxyRequest,
108+
ginContext: c,
109+
request: c.Request,
110+
bodyBytes: bodyBytes,
111+
startTime: time.Now(),
112+
targetURL: _this.target,
113+
})
93114

94-
start := time.Now()
115+
// Handle mirror request if configured
116+
if _this.mirror != nil {
117+
go func() {
118+
ctx, cancel := context.WithTimeout(_this.ctx, _this.cfg.Proxy.MirrorTimeout)
119+
defer cancel()
120+
121+
_this.handleRequest(requestContext{
122+
reqType: mirrorRequest,
123+
request: c.Request.Clone(ctx),
124+
bodyBytes: bodyBytes,
125+
startTime: time.Now(),
126+
targetURL: _this.mirror,
127+
})
128+
}()
129+
}
130+
}
95131

96-
// Create the proxy request
97-
proxyReq := c.Request.Clone(c.Request.Context())
98-
proxyReq.URL.Scheme = _this.target.Scheme
99-
proxyReq.URL.Host = _this.target.Host
100-
proxyReq.Host = _this.target.Host
101-
proxyReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
132+
func (_this proxyController) handleRequest(reqCtx requestContext) {
133+
// Prepare the request
134+
req := reqCtx.request.Clone(reqCtx.request.Context())
135+
req.URL.Scheme = reqCtx.targetURL.Scheme
136+
req.URL.Host = reqCtx.targetURL.Host
137+
req.Host = reqCtx.targetURL.Host
138+
req.Body = io.NopCloser(bytes.NewBuffer(reqCtx.bodyBytes))
102139

103140
// Log outbound request
104-
_this.logger.Debugw("outbound request",
105-
"method", proxyReq.Method,
106-
"url", proxyReq.URL.String(),
107-
"headers", proxyReq.Header,
141+
_this.logger.Debugw(fmt.Sprintf("%s request", reqCtx.reqType),
142+
"method", req.Method,
143+
"url", req.URL.String(),
144+
"headers", req.Header,
108145
)
109146

110-
// Record request with normalized path
111-
err = _this.metrics.RecordRequest(_this.ctx, metric.MetricProxyRequest, proxyReq.Method, proxyReq.URL.Path)
112-
if err != nil {
113-
_this.logger.Errorw("failed to record proxy request metric", "error", err)
147+
// Record metrics
148+
metricName := metric.MetricProxyRequest
149+
if reqCtx.reqType == mirrorRequest {
150+
metricName = metric.MetricMirrorRequest
114151
}
115152

116-
// Make the proxy request
117-
resp, err := _this.transport.RoundTrip(proxyReq)
153+
if err := _this.metrics.RecordRequest(_this.ctx, metricName, req.Method, req.URL.Path); err != nil {
154+
_this.logger.Errorw("failed to record request metric", "error", err)
155+
}
156+
157+
// Make the request
158+
resp, err := _this.transport.RoundTrip(req)
118159
if err != nil {
119-
_this.logger.Errorw("proxy error", "error", err)
120-
c.JSON(http.StatusBadGateway, gin.H{"error": "proxy error"})
160+
_this.logger.Errorw(fmt.Sprintf("%s error", reqCtx.reqType), "error", err)
161+
if reqCtx.reqType == proxyRequest {
162+
reqCtx.ginContext.JSON(http.StatusBadGateway, gin.H{"error": "proxy error"})
163+
}
121164
return
122165
}
123-
defer resp.Body.Close()
166+
// Ignore error since we are closing the body anyway
167+
defer func() { _ = resp.Body.Close() }()
124168

125-
// Read the response body
169+
// Process response
126170
respBody, err := io.ReadAll(resp.Body)
127171
if err != nil {
128-
_this.logger.Errorw("failed to read proxy response body", "error", err)
129-
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read response"})
172+
_this.logger.Errorw(fmt.Sprintf("failed to read %s response", reqCtx.reqType), "error", err)
173+
if reqCtx.reqType == proxyRequest {
174+
reqCtx.ginContext.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read response"})
175+
}
130176
return
131177
}
132178

133-
// Log response details
134-
_this.logger.Debugw("received proxy response",
179+
// Log response
180+
_this.logger.Debugw(fmt.Sprintf("%s response", reqCtx.reqType),
135181
"status", resp.StatusCode,
136-
"content_length", len(respBody),
137-
"content_type", resp.Header.Get("Content-Type"),
182+
"content length", len(respBody),
183+
"headers", resp.Header,
184+
"latency", time.Since(reqCtx.startTime),
138185
)
139186

187+
// Record metrics
140188
attrs := []attribute.KeyValue{
141-
attribute.String("method", proxyReq.Method),
142-
attribute.String("path", proxyReq.URL.Path),
189+
attribute.String("method", req.Method),
190+
attribute.String("path", req.URL.Path),
143191
attribute.Int("status_code", resp.StatusCode),
144-
// Group status codes into categories
145192
attribute.String("status_class", fmt.Sprintf("%dxx", resp.StatusCode/100)),
146193
}
147194

148-
err = _this.metrics.RecordDuration(_this.ctx, metric.MetricProxyRequest, time.Since(start), attrs...)
149-
if err != nil {
150-
_this.logger.Errorw("failed to record proxy request duration metric", "error", err)
151-
}
152-
153-
// Send mirror request if configured
154-
if _this.mirror != nil {
155-
// Create a new context for the mirror request
156-
mirrorReq := c.Request.Clone(_this.ctx)
157-
go _this.mirrorRequest(mirrorReq, bodyBytes)
195+
if err := _this.metrics.RecordDuration(_this.ctx, metricName, time.Since(reqCtx.startTime), attrs...); err != nil {
196+
_this.logger.Errorw("failed to record duration metric", "error", err)
158197
}
159198

160-
// Copy response headers
161-
for k, vv := range resp.Header {
162-
for _, v := range vv {
163-
c.Header(k, v)
199+
// Write response for proxy requests only
200+
if reqCtx.reqType == proxyRequest {
201+
for k, vv := range resp.Header {
202+
for _, v := range vv {
203+
reqCtx.ginContext.Header(k, v)
204+
}
164205
}
165-
}
166-
167-
// Write the response
168-
c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody)
169-
}
170-
171-
func (_this proxyController) mirrorRequest(mirrorReq *http.Request, bodyBytes []byte) {
172-
// Create a context with timeout for the mirror request
173-
ctx, cancel := context.WithTimeout(_this.ctx, 30*time.Second)
174-
defer cancel()
175-
176-
// Use the new context
177-
mirrorReq = mirrorReq.WithContext(ctx)
178-
179-
start := time.Now()
180-
181-
// Create the mirror request
182-
mirrorReq.URL.Scheme = _this.mirror.Scheme
183-
mirrorReq.URL.Host = _this.mirror.Host
184-
mirrorReq.Host = _this.mirror.Host
185-
mirrorReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
186-
187-
// Log mirror request
188-
_this.logger.Debugw("mirror request",
189-
"method", mirrorReq.Method,
190-
"url", mirrorReq.URL.String(),
191-
"headers", mirrorReq.Header,
192-
)
193-
194-
// Record request with normalized path
195-
err := _this.metrics.RecordRequest(_this.ctx, metric.MetricMirrorRequest, mirrorReq.Method, mirrorReq.URL.Path)
196-
if err != nil {
197-
_this.logger.Errorw("failed to record mirror request metric", "error", err)
198-
}
199-
200-
// Make the mirror request
201-
resp, err := _this.transport.RoundTrip(mirrorReq)
202-
if err != nil {
203-
_this.logger.Errorw("mirror error", "error", err)
204-
return
205-
}
206-
defer resp.Body.Close()
207-
208-
// Read and log the mirror response
209-
body, err := io.ReadAll(resp.Body)
210-
if err != nil {
211-
_this.logger.Errorw("failed to read mirror response", "error", err)
212-
return
213-
}
214-
215-
// Log mirror response
216-
_this.logger.Debugw("mirror response",
217-
"status", resp.StatusCode,
218-
"content_length", len(body),
219-
"content_type", resp.Header.Get("Content-Type"),
220-
)
221-
222-
attrs := []attribute.KeyValue{
223-
attribute.String("method", mirrorReq.Method),
224-
attribute.String("path", mirrorReq.URL.Path),
225-
attribute.Int("status_code", resp.StatusCode),
226-
// Group status codes into categories
227-
attribute.String("status_class", fmt.Sprintf("%dxx", resp.StatusCode/100)),
228-
}
229-
230-
err = _this.metrics.RecordDuration(_this.ctx, metric.MetricMirrorRequest, time.Since(start), attrs...)
231-
if err != nil {
232-
_this.logger.Errorw("failed to record proxy request duration metric", "error", err)
206+
reqCtx.ginContext.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody)
233207
}
234208
}
235209

0 commit comments

Comments
 (0)