Skip to content

Commit

Permalink
feat(examples): wrap handler with OTel propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Apr 11, 2023
1 parent 999d939 commit 9f4acdf
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 24 deletions.
12 changes: 12 additions & 0 deletions examples/gateway/car/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"flag"
"io"
"log"
Expand All @@ -21,12 +22,23 @@ func main() {
port := flag.Int("p", 8040, "port to run this gateway from")
flag.Parse()

// Setups up tracing. This is optional and only required if the implementer
// wants to be able to enable tracing.
tp, err := common.SetupTracing("CAR Gateway Example")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
defer (func() { _ = tp.Shutdown(ctx) })()

// Sets up a block service based on the CAR file.
blockService, roots, f, err := newBlockServiceFromCAR(*carFilePtr)
if err != nil {
log.Fatal(err)
}
defer f.Close()

// Creates the gateway API with the block service.
gwAPI, err := gateway.NewBlocksGateway(blockService)
if err != nil {
log.Fatal(err)
Expand Down
9 changes: 8 additions & 1 deletion examples/gateway/common/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/ipfs/boxo/gateway"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func NewHandler(gwAPI gateway.IPFSBackend) http.Handler {
Expand Down Expand Up @@ -58,10 +59,16 @@ func NewHandler(gwAPI gateway.IPFSBackend) http.Handler {
var handler http.Handler
handler = gateway.WithHostname(mux, gwAPI, publicGateways, noDNSLink)

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

// Finally, wrap with the otelhttp handler. This will allow the tracing system
// to work and for correct propagation of tracing headers. This step is optional
// and only required if you want to use tracing. Note that OTel must be correctly
// setup in order for this to have an effect.
handler = otelhttp.NewHandler(handler, "Gateway.Request")

return handler
}

Expand Down
59 changes: 59 additions & 0 deletions examples/gateway/common/tracing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package common

import (
"net/http"

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/contrib/propagators/autoprop"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)

// SetupTracing setups Open Telemetry tracing with some defaults that will enable
// tracing in this system. Please note that in a real-case scenario this would
// look slightly different and include more configurations.
func SetupTracing(serviceName string) (*trace.TracerProvider, error) {
// Creates the resources to create a new tracing provider.
r, err := resource.Merge(
resource.Default(),
resource.NewSchemaless(
semconv.ServiceNameKey.String(serviceName),
),
)
if err != nil {
return nil, err
}

// Creates a new tracing provider. In a real-case scenario, this trace provider
// will likely be configured with exporters in order to be able to access
// the tracing information. https://opentelemetry.io/docs/instrumentation/go/exporters/
tp := trace.NewTracerProvider(
trace.WithResource(r),
)

// Sets the default trace provider for this process. If this is not done, tracing
// will not be enabled. Please note that this will apply to the entire process
// as it is set as the default tracer.
otel.SetTracerProvider(tp)

// Configures the default propagators used by the Open Telemetry library. By
// using autoprop.NewTextMapPropagator, we ensure the value of the environmental
// variable OTEL_PROPAGATORS is respected, if set. By default, Trace Context
// and Baggage are used. More details on:
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md
otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())

return tp, nil
}

// NewClient creates a new HTTP Client wraped with the OTel transport. This will
// ensure correct propagation of tracing headers across multiple HTTP requests when
// Open Telemetry is configured. Please note that NewClient will use the default
// global trace provider and propagators. Therefore, SetupTracing must be called first.
func NewClient() *http.Client {
return &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
}
16 changes: 6 additions & 10 deletions examples/gateway/proxy/blockstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"fmt"
"io"
"net/http"
"net/url"

"github.com/ipfs/boxo/examples/gateway/common"
"github.com/ipfs/boxo/exchange"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
Expand All @@ -19,7 +19,7 @@ type proxyExchange struct {

func newProxyExchange(gatewayURL string, client *http.Client) exchange.Interface {
if client == nil {
client = http.DefaultClient
client = common.NewClient()
}

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

func (e *proxyExchange) fetch(ctx context.Context, c cid.Cid) (blocks.Block, error) {
u, err := url.Parse(fmt.Sprintf("%s/ipfs/%s?format=raw", e.gatewayURL, c))
urlStr := fmt.Sprintf("%s/ipfs/%s?format=raw", e.gatewayURL, c)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, err
}
resp, err := e.httpClient.Do(&http.Request{
Method: http.MethodGet,
URL: u,
Header: http.Header{
"Accept": []string{"application/vnd.ipld.raw"},
},
})
req.Header.Set("Accept", "application/vnd.ipld.raw")
resp, err := e.httpClient.Do(req)
if err != nil {
return nil, err
}
Expand Down
10 changes: 10 additions & 0 deletions examples/gateway/proxy/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"flag"
"log"
"net/http"
Expand All @@ -19,6 +20,15 @@ func main() {
port := flag.Int("p", 8040, "port to run this gateway from")
flag.Parse()

// Setups up tracing. This is optional and only required if the implementer
// wants to be able to enable tracing.
tp, err := common.SetupTracing("Proxy Gateway Example")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
defer (func() { _ = tp.Shutdown(ctx) })()

// Sets up a blockstore to hold the blocks we request from the gateway
// Note: in a production environment you would likely want to choose a more efficient datastore implementation
// as well as one that has a way of pruning storage so as not to hold data in memory indefinitely.
Expand Down
71 changes: 69 additions & 2 deletions examples/gateway/proxy/main_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package main

import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/ipfs/boxo/blockservice"
Expand Down Expand Up @@ -51,7 +54,7 @@ func TestErrorOnInvalidContent(t *testing.T) {
body, err := io.ReadAll(res.Body)
res.Body.Close()
assert.Nil(t, err)
assert.EqualValues(t, res.StatusCode, http.StatusInternalServerError)
assert.EqualValues(t, http.StatusInternalServerError, res.StatusCode)
assert.Contains(t, string(body), blocks.ErrWrongHash.Error())
}

Expand All @@ -68,6 +71,70 @@ func TestPassOnOnCorrectContent(t *testing.T) {
body, err := io.ReadAll(res.Body)
res.Body.Close()
assert.Nil(t, err)
assert.EqualValues(t, res.StatusCode, http.StatusOK)
assert.EqualValues(t, http.StatusOK, res.StatusCode)
assert.EqualValues(t, string(body), "hello world")
}

func TestTraceContext(t *testing.T) {
doCheckRequest := func(t *testing.T, req *http.Request) {
res, err := http.DefaultClient.Do(req)
assert.Nil(t, err)
assert.EqualValues(t, http.StatusOK, res.StatusCode)
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
assert.Nil(t, err)
assert.EqualValues(t, string(body), "hello world")
}

const (
traceVersion = "00"
traceID = "4bf92f3577b34da6a3ce929d0e0e4736"
traceParentID = "00f067aa0ba902b7"
traceFlags = "00"
)

// Creating a trace provider and registering it will make OTel enable tracing.
tp, err := common.SetupTracing("Proxy Test")
assert.Nil(t, err)
ctx := context.Background()
t.Cleanup(func() { _ = tp.Shutdown(ctx) })

t.Run("Re-use Traceparent Trace ID Of Initial Request", func(t *testing.T) {
rs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// The expected prefix for the traceparent header consists of the version and trace id.
expectedPrefix := fmt.Sprintf("%s-%s-", traceVersion, traceID)
if !strings.HasPrefix(r.Header.Get("traceparent"), expectedPrefix) {
w.WriteHeader(http.StatusBadRequest)
} else {
w.Write([]byte("hello world"))
}
}))

t.Cleanup(rs.Close)
ts := newProxyGateway(t, rs)

req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipfs/"+HelloWorldCID, nil)
assert.Nil(t, err)
req.Header.Set("Traceparent", fmt.Sprintf("%s-%s-%s-%s", traceVersion, traceID, traceParentID, traceFlags))
doCheckRequest(t, req)
})

t.Run("Create New Trace ID If Not Given", func(t *testing.T) {
rs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// In this request we are not sending a traceparent header, so a new one should be created.
if r.Header.Get("traceparent") == "" {
w.WriteHeader(http.StatusBadRequest)
} else {
w.Write([]byte("hello world"))
}
}))

t.Cleanup(rs.Close)
ts := newProxyGateway(t, rs)

req, err := http.NewRequest(http.MethodGet, ts.URL+"/ipfs/"+HelloWorldCID, nil)
assert.Nil(t, err)
doCheckRequest(t, req)
})
}
21 changes: 11 additions & 10 deletions examples/gateway/proxy/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"

"github.com/gogo/protobuf/proto"
"github.com/ipfs/boxo/examples/gateway/common"
"github.com/ipfs/boxo/ipns"
ipns_pb "github.com/ipfs/boxo/ipns/pb"
ic "github.com/libp2p/go-libp2p/core/crypto"
Expand All @@ -23,7 +23,7 @@ type proxyRouting struct {

func newProxyRouting(gatewayURL string, client *http.Client) routing.ValueStore {
if client == nil {
client = http.DefaultClient
client = common.NewClient()
}

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

func (ps *proxyRouting) fetch(ctx context.Context, id peer.ID) ([]byte, error) {
u, err := url.Parse(fmt.Sprintf("%s/ipns/%s", ps.gatewayURL, peer.ToCid(id).String()))
urlStr := fmt.Sprintf("%s/ipns/%s", ps.gatewayURL, peer.ToCid(id).String())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, err
}
resp, err := ps.httpClient.Do(&http.Request{
Method: http.MethodGet,
URL: u,
Header: http.Header{
"Accept": []string{"application/vnd.ipfs.ipns-record"},
},
})
req.Header.Set("Accept", "application/vnd.ipfs.ipns-record")
resp, err := ps.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if err != nil {
return nil, err
}
Expand Down
11 changes: 10 additions & 1 deletion examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ require (
github.com/multiformats/go-multicodec v0.8.1
github.com/prometheus/client_golang v1.14.0
github.com/stretchr/testify v1.8.2
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0
go.opentelemetry.io/contrib/propagators/autoprop v0.40.0
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/sdk v1.14.0
)

require (
Expand All @@ -33,6 +37,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
Expand Down Expand Up @@ -125,7 +130,11 @@ require (
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.15.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.15.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.15.0 // indirect
go.opentelemetry.io/contrib/propagators/ot v1.15.0 // indirect
go.opentelemetry.io/otel/metric v0.37.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/dig v1.15.0 // indirect
Expand Down
18 changes: 18 additions & 0 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
Expand Down Expand Up @@ -644,8 +646,24 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI=
go.opentelemetry.io/contrib/propagators/autoprop v0.40.0 h1:Lj33jj7eIrBfIShiK8NU91u2BglKnUS1UUxVemuQJtw=
go.opentelemetry.io/contrib/propagators/autoprop v0.40.0/go.mod h1:6QO816FeZ+6zahs6hYqbUCCsnNBm7o+t4iwVySpzcdI=
go.opentelemetry.io/contrib/propagators/aws v1.15.0 h1:FLe+bRTMAhEALItDQt1U2S/rdq8/rGGJTJpOpCDvMu0=
go.opentelemetry.io/contrib/propagators/aws v1.15.0/go.mod h1:Z/nqdjqKjErrS3gYoEMZt8//dt8VZbqalD0V+7vh7lM=
go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q=
go.opentelemetry.io/contrib/propagators/b3 v1.15.0/go.mod h1:VjU0g2v6HSQ+NwfifambSLAeBgevjIcqmceaKWEzl0c=
go.opentelemetry.io/contrib/propagators/jaeger v1.15.0 h1:xdJjwy5t/8I+TZehMMQ+r2h50HREihH2oMUhimQ+jug=
go.opentelemetry.io/contrib/propagators/jaeger v1.15.0/go.mod h1:tU0nwW4QTvKceNUP60/PQm0FI8zDSwey7gIFt3RR/yw=
go.opentelemetry.io/contrib/propagators/ot v1.15.0 h1:iBNejawWy7wWZ5msuZDNcMjBy14Wc0v3gCAXukGHN/Q=
go.opentelemetry.io/contrib/propagators/ot v1.15.0/go.mod h1:0P7QQ+MHt6SXR1ATaMpewSiWlp8NbKErNLKcaU4EEKI=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs=
go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s=
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
Expand Down

0 comments on commit 9f4acdf

Please sign in to comment.