mirror of
https://github.com/foomo/keel.git
synced 2025-10-16 12:35:34 +00:00
feat: add roundtripperwares
This commit is contained in:
parent
0aa5e02fe9
commit
fe636f68ad
@ -10,6 +10,7 @@ require (
|
||||
github.com/jackc/pgx/v4 v4.14.1
|
||||
github.com/nats-io/nats.go v1.13.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/tinylib/msgp v1.1.6 // indirect
|
||||
go.mongodb.org/mongo-driver v1.8.1
|
||||
go.opentelemetry.io/otel v0.20.0
|
||||
go.opentelemetry.io/otel/metric v0.20.0
|
||||
|
||||
@ -514,6 +514,8 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv
|
||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -619,6 +621,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
@ -992,6 +996,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
|
||||
25
example/roundtripwares/logger/client.go
Normal file
25
example/roundtripwares/logger/client.go
Normal file
@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/foomo/keel/log"
|
||||
"github.com/foomo/keel/net/http"
|
||||
"github.com/foomo/keel/net/http/roundtripware"
|
||||
)
|
||||
|
||||
func client() {
|
||||
l := log.Logger()
|
||||
|
||||
client := http.NewHTTPClient(
|
||||
http.HTTPClientWithRoundTripware(l,
|
||||
roundtripware.Logger(),
|
||||
),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
_, err = client.Get("http://localhost:8080") // nolint:noctx
|
||||
log.Must(l, err, "failed to retrieve response")
|
||||
|
||||
_, err = client.Get("http://localhost:8080/404") // nolint:noctx
|
||||
log.Must(l, err, "failed to retrieve response")
|
||||
}
|
||||
13
example/roundtripwares/logger/main.go
Normal file
13
example/roundtripwares/logger/main.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
go server()
|
||||
|
||||
go client()
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
33
example/roundtripwares/logger/server.go
Normal file
33
example/roundtripwares/logger/server.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
)
|
||||
|
||||
func server() {
|
||||
svr := keel.NewServer()
|
||||
|
||||
// get logger
|
||||
l := svr.Logger()
|
||||
|
||||
// create demo service
|
||||
svs := http.NewServeMux()
|
||||
|
||||
svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
svs.HandleFunc("/404", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte("not found"))
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", ":8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
}
|
||||
1
go.mod
1
go.mod
@ -12,6 +12,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tinylib/msgp v1.1.6
|
||||
go.mongodb.org/mongo-driver v1.8.1
|
||||
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.20.0
|
||||
go.opentelemetry.io/contrib/instrumentation/host v0.20.0
|
||||
|
||||
5
go.sum
5
go.sum
@ -514,6 +514,8 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv
|
||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -619,6 +621,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
@ -992,6 +996,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
|
||||
44
log/with.go
44
log/with.go
@ -9,8 +9,6 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.uber.org/zap"
|
||||
|
||||
httputils "github.com/foomo/keel/net/http"
|
||||
)
|
||||
|
||||
func With(l *zap.Logger, fields ...zap.Field) *zap.Logger {
|
||||
@ -50,10 +48,10 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger {
|
||||
if r.Host != "" {
|
||||
fields = append(fields, FHTTPHost(r.Host))
|
||||
}
|
||||
if id := r.Header.Get(httputils.HeaderXRequestID); id != "" {
|
||||
if id := r.Header.Get("X-Request-ID"); id != "" {
|
||||
fields = append(fields, FHTTPRequestID(id))
|
||||
}
|
||||
if id := r.Header.Get(httputils.HeaderXSessionID); id != "" {
|
||||
if id := r.Header.Get("X-Session-ID"); id != "" {
|
||||
fields = append(fields, FHTTPSessionID(id))
|
||||
}
|
||||
if r.TLS != nil {
|
||||
@ -68,13 +66,13 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger {
|
||||
}
|
||||
|
||||
var clientIP string
|
||||
if value := r.Header.Get(httputils.HeaderXForwardedFor); value != "" {
|
||||
if value := r.Header.Get("X-Forwarded-For"); value != "" {
|
||||
if i := strings.IndexAny(value, ", "); i > 0 {
|
||||
clientIP = value[:i]
|
||||
} else {
|
||||
clientIP = value
|
||||
}
|
||||
} else if value := r.Header.Get(httputils.HeaderXRealIP); value != "" {
|
||||
} else if value := r.Header.Get("X-Real-IP"); value != "" {
|
||||
clientIP = value
|
||||
} else if value, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
clientIP = value
|
||||
@ -91,3 +89,37 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger {
|
||||
|
||||
return With(l, fields...)
|
||||
}
|
||||
|
||||
func WithHTTPRequestOut(l *zap.Logger, r *http.Request) *zap.Logger {
|
||||
fields := []zap.Field{
|
||||
FHTTPWroteBytes(r.ContentLength),
|
||||
FHTTPMethod(r.Method),
|
||||
FHTTPTarget(r.URL.Path),
|
||||
}
|
||||
|
||||
if r.URL.Host != "" {
|
||||
fields = append(fields, FHTTPHost(r.URL.Host))
|
||||
}
|
||||
if id := r.Header.Get("X-Request-ID"); id != "" {
|
||||
fields = append(fields, FHTTPRequestID(id))
|
||||
}
|
||||
if id := r.Header.Get("X-Session-ID"); id != "" {
|
||||
fields = append(fields, FHTTPSessionID(id))
|
||||
}
|
||||
if r.TLS != nil {
|
||||
fields = append(fields, FHTTPScheme("https"))
|
||||
} else {
|
||||
fields = append(fields, FHTTPScheme("http"))
|
||||
}
|
||||
if r.ProtoMajor == 1 {
|
||||
fields = append(fields, FHTTPFlavor(fmt.Sprintf("1.%d", r.ProtoMinor)))
|
||||
} else if r.ProtoMajor == 2 {
|
||||
fields = append(fields, FHTTPFlavor("2"))
|
||||
}
|
||||
|
||||
if spanCtx := trace.SpanContextFromContext(r.Context()); spanCtx.IsValid() {
|
||||
fields = append(fields, FTraceID(spanCtx.TraceID().String()))
|
||||
}
|
||||
|
||||
return With(l, fields...)
|
||||
}
|
||||
|
||||
247
net/http/client.go
Normal file
247
net/http/client.go
Normal file
@ -0,0 +1,247 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel/net/http/roundtripware"
|
||||
)
|
||||
|
||||
type HTTPClientOption func(*http.Client)
|
||||
|
||||
func HTTPClientWithTimeout(o time.Duration) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
v.Timeout = o
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithJar(o http.CookieJar) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
v.Jar = o
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithTransport(o http.RoundTripper) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
v.Transport = o
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithCheckRedirect(o func(req *http.Request, via []*http.Request) error) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
v.CheckRedirect = o
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithProxy(o func(request *http.Request) (*url.URL, error)) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.Proxy = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithDialContext(o func(ctx context.Context, network, addr string) (net.Conn, error)) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.DialContext = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithDialTLSContext(o func(ctx context.Context, network, addr string) (net.Conn, error)) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.DialTLSContext = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithTLSClientConfig(o *tls.Config) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.TLSClientConfig = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithTLSHandshakeTimeout(o time.Duration) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.TLSHandshakeTimeout = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithDisableKeepAlives(o bool) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.DisableKeepAlives = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithDisableCompression(o bool) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.DisableCompression = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithMaxIdleConns(o int) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.MaxIdleConns = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithMaxIdleConnsPerHost(o int) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.MaxIdleConnsPerHost = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithMaxConnsPerHost(o int) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.MaxConnsPerHost = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithIdleConnTimeout(o time.Duration) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.IdleConnTimeout = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithResponseHeaderTimeout(o time.Duration) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.ResponseHeaderTimeout = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithExpectContinueTimeout(o time.Duration) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.ExpectContinueTimeout = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithTLSNextProto(o map[string]func(authority string, c *tls.Conn) http.RoundTripper) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.TLSNextProto = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithProxyConnectHeader(o http.Header) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.ProxyConnectHeader = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithGetProxyConnectHeader(o func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error)) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.GetProxyConnectHeader = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithMaxResponseHeaderBytes(o int64) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.MaxResponseHeaderBytes = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithWriteBufferSize(o int) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.WriteBufferSize = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithReadBufferSize(o int) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.ReadBufferSize = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithForceAttemptHTTP2(o bool) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
if t, ok := v.Transport.(*http.Transport); ok {
|
||||
t.ForceAttemptHTTP2 = o
|
||||
v.Transport = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPClientWithRoundTripware(l *zap.Logger, roundTripware ...roundtripware.RoundTripware) HTTPClientOption {
|
||||
return func(v *http.Client) {
|
||||
v.Transport = roundtripware.NewRoundTripper(l, v.Transport, roundTripware...)
|
||||
}
|
||||
}
|
||||
|
||||
func NewHTTPClient(opts ...HTTPClientOption) *http.Client {
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 45 * time.Second,
|
||||
KeepAlive: 45 * time.Second,
|
||||
}).DialContext,
|
||||
DisableKeepAlives: true,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 5 * time.Second,
|
||||
}
|
||||
inst := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 2 * time.Minute,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(inst)
|
||||
}
|
||||
return inst
|
||||
}
|
||||
39
net/http/roundtripware/dump.go
Normal file
39
net/http/roundtripware/dump.go
Normal file
@ -0,0 +1,39 @@
|
||||
package roundtripware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// DumpRequest returns a RoundTripper which prints out the request object
|
||||
func DumpRequest() RoundTripware {
|
||||
return func(l *zap.Logger, next Handler) Handler {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
if req.Header != nil && req.Header.Get("Content-Type") != "" {
|
||||
var body string
|
||||
if req.Body, body = readBodyPretty(req.Header.Get("Content-Type"), req.Body); body != "" {
|
||||
fmt.Printf("Request %s:\n%s\n", req.URL, body)
|
||||
}
|
||||
}
|
||||
return next(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DumpResponse returns a RoundTripper which prints out the response object
|
||||
func DumpResponse() RoundTripware {
|
||||
return func(l *zap.Logger, next Handler) Handler {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := next(req)
|
||||
if resp.Header != nil && resp.Header.Get("Content-Type") != "" {
|
||||
var body string
|
||||
if resp.Body, body = readBodyPretty(resp.Header.Get("Content-Type"), resp.Body); body != "" {
|
||||
fmt.Printf("Response %s:\n%s\n", req.URL, body)
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
}
|
||||
45
net/http/roundtripware/helper.go
Normal file
45
net/http/roundtripware/helper.go
Normal file
@ -0,0 +1,45 @@
|
||||
package roundtripware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
func readBodyPretty(contentType string, original io.ReadCloser) (io.ReadCloser, string) {
|
||||
var bs bytes.Buffer
|
||||
var body string
|
||||
defer func() {
|
||||
_ = original.Close()
|
||||
}()
|
||||
|
||||
// read in body
|
||||
if _, err := io.Copy(&bs, original); err != nil {
|
||||
return original, ""
|
||||
} else {
|
||||
body = bs.String()
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(contentType, "application/json"):
|
||||
var prettyJSON bytes.Buffer
|
||||
if err := json.Indent(&prettyJSON, bs.Bytes(), "", " "); err == nil {
|
||||
body = prettyJSON.String()
|
||||
}
|
||||
case strings.HasPrefix(contentType, "application/msgpack"):
|
||||
var prettyJSON bytes.Buffer
|
||||
var out bytes.Buffer
|
||||
if _, err := msgp.UnmarshalAsJSON(&out, bs.Bytes()); err == nil {
|
||||
if err := json.Indent(&prettyJSON, out.Bytes(), "", " "); err == nil {
|
||||
body = prettyJSON.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return copy of the original
|
||||
return ioutil.NopCloser(strings.NewReader(bs.String())), body
|
||||
}
|
||||
27
net/http/roundtripware/logger.go
Normal file
27
net/http/roundtripware/logger.go
Normal file
@ -0,0 +1,27 @@
|
||||
package roundtripware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel/log"
|
||||
keeltime "github.com/foomo/keel/time"
|
||||
)
|
||||
|
||||
// Logger returns a RoundTripware which logs all requests
|
||||
func Logger() RoundTripware {
|
||||
msg := "sent request"
|
||||
return func(l *zap.Logger, next Handler) Handler {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
start := keeltime.Now()
|
||||
resp, err := next(req)
|
||||
log.WithHTTPRequestOut(l, req).Info(msg,
|
||||
log.FDuration(keeltime.Now().Sub(start)),
|
||||
log.FHTTPStatusCode(resp.StatusCode),
|
||||
log.FHTTPRequestContentLength(resp.ContentLength),
|
||||
)
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
}
|
||||
63
net/http/roundtripware/recover.go
Normal file
63
net/http/roundtripware/recover.go
Normal file
@ -0,0 +1,63 @@
|
||||
package roundtripware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel/log"
|
||||
)
|
||||
|
||||
type (
|
||||
RecoverOptions struct {
|
||||
DisablePrintStack bool
|
||||
}
|
||||
RecoverOption func(options *RecoverOptions)
|
||||
)
|
||||
|
||||
// GetDefaultRecoverOptions returns the default options
|
||||
func GetDefaultRecoverOptions() RecoverOptions {
|
||||
return RecoverOptions{
|
||||
DisablePrintStack: false,
|
||||
}
|
||||
}
|
||||
|
||||
// RecoverWithDisablePrintStack roundTripware option
|
||||
func RecoverWithDisablePrintStack(v bool) RecoverOption {
|
||||
return func(o *RecoverOptions) {
|
||||
o.DisablePrintStack = v
|
||||
}
|
||||
}
|
||||
|
||||
// Recover returns a RoundTripper which catches any panics
|
||||
func Recover(opts ...RecoverOption) RoundTripware {
|
||||
options := GetDefaultRecoverOptions()
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&options)
|
||||
}
|
||||
}
|
||||
return RecoverWithOptions(options)
|
||||
}
|
||||
|
||||
// RecoverWithOptions returns a RoundTripper which catches any panics
|
||||
func RecoverWithOptions(opts RecoverOptions) RoundTripware {
|
||||
return func(l *zap.Logger, next Handler) Handler {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err, ok := e.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
if !opts.DisablePrintStack {
|
||||
l = l.With(log.FStackSkip(3))
|
||||
}
|
||||
log.WithError(l, err).Error("recovering from panic")
|
||||
}
|
||||
}()
|
||||
return next(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
33
net/http/roundtripware/roundtripware.go
Normal file
33
net/http/roundtripware/roundtripware.go
Normal file
@ -0,0 +1,33 @@
|
||||
package roundtripware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
Handler func(r *http.Request) (*http.Response, error)
|
||||
RoundTripware func(l *zap.Logger, next Handler) Handler
|
||||
)
|
||||
|
||||
type RoundTripper struct {
|
||||
http.RoundTripper
|
||||
handler Handler
|
||||
}
|
||||
|
||||
func NewRoundTripper(l *zap.Logger, parent http.RoundTripper, roundTripwares ...RoundTripware) *RoundTripper {
|
||||
next := parent.RoundTrip
|
||||
for _, roundTripware := range roundTripwares {
|
||||
next = roundTripware(l, next)
|
||||
}
|
||||
|
||||
return &RoundTripper{
|
||||
RoundTripper: parent,
|
||||
handler: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
return rt.handler(req)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user