Skip to content

Commit 3645315

Browse files
committed
feat(examples): wrap handler with OTel propagation
1 parent 322c51e commit 3645315

7 files changed

Lines changed: 135 additions & 24 deletions

File tree

examples/gateway/common/handler.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/ipfs/boxo/gateway"
77
"github.com/prometheus/client_golang/prometheus"
88
"github.com/prometheus/client_golang/prometheus/promhttp"
9+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
910
)
1011

1112
func NewHandler(gwAPI gateway.IPFSBackend) http.Handler {
@@ -58,10 +59,15 @@ func NewHandler(gwAPI gateway.IPFSBackend) http.Handler {
5859
var handler http.Handler
5960
handler = gateway.WithHostname(mux, gwAPI, publicGateways, noDNSLink)
6061

61-
// Finally, wrap with the withConnect middleware. This is required since we use
62+
// Then, wrap with the withConnect middleware. This is required since we use
6263
// http.ServeMux which does not support CONNECT by default.
6364
handler = withConnect(handler)
6465

66+
// Finally, wrap with the otelhttp handler. This will allow the tracing system
67+
// to work and for correct propagation of tracing headers. This step is optional
68+
// and only required if you want to use tracing.
69+
handler = otelhttp.NewHandler(handler, "Gateway.Request", otelhttp.WithPropagators(Propagators))
70+
6571
return handler
6672
}
6773

examples/gateway/common/tracing.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package common
2+
3+
import (
4+
"net/http"
5+
6+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
7+
"go.opentelemetry.io/otel/propagation"
8+
)
9+
10+
var Propagators = propagation.NewCompositeTextMapPropagator(
11+
propagation.TraceContext{},
12+
propagation.Baggage{},
13+
)
14+
15+
var DefaultClient = &http.Client{
16+
Transport: otelhttp.NewTransport(
17+
http.DefaultTransport,
18+
otelhttp.WithPropagators(Propagators),
19+
),
20+
}

examples/gateway/proxy/blockstore.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8-
"net/url"
98

9+
"github.com/ipfs/boxo/examples/gateway/common"
1010
"github.com/ipfs/boxo/exchange"
1111
blocks "github.com/ipfs/go-block-format"
1212
"github.com/ipfs/go-cid"
@@ -19,7 +19,7 @@ type proxyExchange struct {
1919

2020
func newProxyExchange(gatewayURL string, client *http.Client) exchange.Interface {
2121
if client == nil {
22-
client = http.DefaultClient
22+
client = common.DefaultClient
2323
}
2424

2525
return &proxyExchange{
@@ -29,17 +29,13 @@ func newProxyExchange(gatewayURL string, client *http.Client) exchange.Interface
2929
}
3030

3131
func (e *proxyExchange) fetch(ctx context.Context, c cid.Cid) (blocks.Block, error) {
32-
u, err := url.Parse(fmt.Sprintf("%s/ipfs/%s?format=raw", e.gatewayURL, c))
32+
urlStr := fmt.Sprintf("%s/ipfs/%s?format=raw", e.gatewayURL, c)
33+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
3334
if err != nil {
3435
return nil, err
3536
}
36-
resp, err := e.httpClient.Do(&http.Request{
37-
Method: http.MethodGet,
38-
URL: u,
39-
Header: http.Header{
40-
"Accept": []string{"application/vnd.ipld.raw"},
41-
},
42-
})
37+
req.Header.Set("Accept", "application/vnd.ipld.raw")
38+
resp, err := e.httpClient.Do(req)
4339
if err != nil {
4440
return nil, err
4541
}

examples/gateway/proxy/main_test.go

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package main
22

33
import (
4+
"context"
5+
"fmt"
46
"io"
57
"net/http"
68
"net/http/httptest"
9+
"strings"
710
"testing"
811

912
"github.com/ipfs/boxo/blockservice"
@@ -14,12 +17,24 @@ import (
1417
"github.com/ipfs/go-datastore"
1518
dssync "github.com/ipfs/go-datastore/sync"
1619
"github.com/stretchr/testify/assert"
20+
"go.opentelemetry.io/otel"
21+
"go.opentelemetry.io/otel/propagation"
22+
"go.opentelemetry.io/otel/sdk/trace"
1723
)
1824

1925
const (
2026
HelloWorldCID = "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"
2127
)
2228

29+
func newTracerProvider(t *testing.T) *trace.TracerProvider {
30+
tp := trace.NewTracerProvider()
31+
otel.SetTracerProvider(tp)
32+
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(common.Propagators))
33+
ctx := context.Background()
34+
t.Cleanup(func() { _ = tp.Shutdown(ctx) })
35+
return tp
36+
}
37+
2338
func newProxyGateway(t *testing.T, rs *httptest.Server) *httptest.Server {
2439
blockStore := blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))
2540
exch := newProxyExchange(rs.URL, nil)
@@ -51,7 +66,7 @@ func TestErrorOnInvalidContent(t *testing.T) {
5166
body, err := io.ReadAll(res.Body)
5267
res.Body.Close()
5368
assert.Nil(t, err)
54-
assert.EqualValues(t, res.StatusCode, http.StatusInternalServerError)
69+
assert.EqualValues(t, http.StatusInternalServerError, res.StatusCode)
5570
assert.Contains(t, string(body), blocks.ErrWrongHash.Error())
5671
}
5772

@@ -68,6 +83,67 @@ func TestPassOnOnCorrectContent(t *testing.T) {
6883
body, err := io.ReadAll(res.Body)
6984
res.Body.Close()
7085
assert.Nil(t, err)
71-
assert.EqualValues(t, res.StatusCode, http.StatusOK)
86+
assert.EqualValues(t, http.StatusOK, res.StatusCode)
7287
assert.EqualValues(t, string(body), "hello world")
7388
}
89+
90+
func TestTraceContext(t *testing.T) {
91+
doCheckRequest := func(t *testing.T, req *http.Request) {
92+
res, err := http.DefaultClient.Do(req)
93+
assert.Nil(t, err)
94+
assert.EqualValues(t, http.StatusOK, res.StatusCode)
95+
defer res.Body.Close()
96+
97+
body, err := io.ReadAll(res.Body)
98+
assert.Nil(t, err)
99+
assert.EqualValues(t, string(body), "hello world")
100+
}
101+
102+
const (
103+
traceVersion = "00"
104+
traceID = "4bf92f3577b34da6a3ce929d0e0e4736"
105+
traceParentID = "00f067aa0ba902b7"
106+
traceFlags = "00"
107+
)
108+
109+
// Creating a trace provider and registering it will make OTel enable tracing.
110+
_ = newTracerProvider(t)
111+
112+
t.Run("Re-use Traceparent Trace ID Of Initial Request", func(t *testing.T) {
113+
rs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
114+
// The expected prefix for the traceparent header consists of the version and trace id.
115+
expectedPrefix := fmt.Sprintf("%s-%s-", traceVersion, traceID)
116+
if !strings.HasPrefix(r.Header.Get("traceparent"), expectedPrefix) {
117+
w.WriteHeader(http.StatusBadRequest)
118+
} else {
119+
w.Write([]byte("hello world"))
120+
}
121+
}))
122+
123+
t.Cleanup(rs.Close)
124+
ts := newProxyGateway(t, rs)
125+
126+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipfs/"+HelloWorldCID, nil)
127+
assert.Nil(t, err)
128+
req.Header.Set("Traceparent", fmt.Sprintf("%s-%s-%s-%s", traceVersion, traceID, traceParentID, traceFlags))
129+
doCheckRequest(t, req)
130+
})
131+
132+
t.Run("Create New Trace ID If Not Given", func(t *testing.T) {
133+
rs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
134+
// In this request we are not sending a traceparent header, so a new one should be created.
135+
if r.Header.Get("traceparent") == "" {
136+
w.WriteHeader(http.StatusBadRequest)
137+
} else {
138+
w.Write([]byte("hello world"))
139+
}
140+
}))
141+
142+
t.Cleanup(rs.Close)
143+
ts := newProxyGateway(t, rs)
144+
145+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipfs/"+HelloWorldCID, nil)
146+
assert.Nil(t, err)
147+
doCheckRequest(t, req)
148+
})
149+
}

examples/gateway/proxy/routing.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8-
"net/url"
98
"strings"
109

1110
"github.com/gogo/protobuf/proto"
11+
"github.com/ipfs/boxo/examples/gateway/common"
1212
"github.com/ipfs/boxo/ipns"
1313
ipns_pb "github.com/ipfs/boxo/ipns/pb"
1414
ic "github.com/libp2p/go-libp2p/core/crypto"
@@ -23,7 +23,7 @@ type proxyRouting struct {
2323

2424
func newProxyRouting(gatewayURL string, client *http.Client) routing.ValueStore {
2525
if client == nil {
26-
client = http.DefaultClient
26+
client = common.DefaultClient
2727
}
2828

2929
return &proxyRouting{
@@ -77,17 +77,18 @@ func (ps *proxyRouting) SearchValue(ctx context.Context, k string, opts ...routi
7777
}
7878

7979
func (ps *proxyRouting) fetch(ctx context.Context, id peer.ID) ([]byte, error) {
80-
u, err := url.Parse(fmt.Sprintf("%s/ipns/%s", ps.gatewayURL, peer.ToCid(id).String()))
80+
urlStr := fmt.Sprintf("%s/ipns/%s", ps.gatewayURL, peer.ToCid(id).String())
81+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
8182
if err != nil {
8283
return nil, err
8384
}
84-
resp, err := ps.httpClient.Do(&http.Request{
85-
Method: http.MethodGet,
86-
URL: u,
87-
Header: http.Header{
88-
"Accept": []string{"application/vnd.ipfs.ipns-record"},
89-
},
90-
})
85+
req.Header.Set("Accept", "application/vnd.ipfs.ipns-record")
86+
resp, err := ps.httpClient.Do(req)
87+
if err != nil {
88+
return nil, err
89+
}
90+
defer resp.Body.Close()
91+
9192
if err != nil {
9293
return nil, err
9394
}

examples/go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ require (
1515
github.com/multiformats/go-multicodec v0.8.1
1616
github.com/prometheus/client_golang v1.14.0
1717
github.com/stretchr/testify v1.8.2
18+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0
19+
go.opentelemetry.io/otel v1.14.0
20+
go.opentelemetry.io/otel/sdk v1.14.0
1821
)
1922

2023
require (
@@ -33,6 +36,7 @@ require (
3336
github.com/docker/go-units v0.5.0 // indirect
3437
github.com/dustin/go-humanize v1.0.0 // indirect
3538
github.com/elastic/gosigar v0.14.2 // indirect
39+
github.com/felixge/httpsnoop v1.0.3 // indirect
3640
github.com/flynn/noise v1.0.0 // indirect
3741
github.com/francoispqt/gojay v1.2.13 // indirect
3842
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
@@ -125,7 +129,7 @@ require (
125129
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect
126130
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
127131
go.opencensus.io v0.24.0 // indirect
128-
go.opentelemetry.io/otel v1.14.0 // indirect
132+
go.opentelemetry.io/otel/metric v0.37.0 // indirect
129133
go.opentelemetry.io/otel/trace v1.14.0 // indirect
130134
go.uber.org/atomic v1.10.0 // indirect
131135
go.uber.org/dig v1.15.0 // indirect

examples/go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
105105
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
106106
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
107107
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
108+
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
109+
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
108110
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
109111
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
110112
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
@@ -644,8 +646,14 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
644646
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
645647
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
646648
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
649+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE=
650+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI=
647651
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
648652
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
653+
go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs=
654+
go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s=
655+
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
656+
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
649657
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
650658
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
651659
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=

0 commit comments

Comments
 (0)