Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 01:00 UTC
| 1 | // Copyright 2015 Matthew Holt and The Caddy Authors |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package reverseproxy |
| 16 | |
| 17 | import ( |
| 18 | "context" |
| 19 | "net" |
| 20 | "net/http" |
| 21 | "net/http/httptest" |
| 22 | "strings" |
| 23 | "testing" |
| 24 | |
| 25 | "go.opentelemetry.io/otel/attribute" |
| 26 | "go.opentelemetry.io/otel/codes" |
| 27 | sdktrace "go.opentelemetry.io/otel/sdk/trace" |
| 28 | "go.opentelemetry.io/otel/sdk/trace/tracetest" |
| 29 | "go.opentelemetry.io/otel/trace" |
| 30 | |
| 31 | "github.com/caddyserver/caddy/v2" |
| 32 | ) |
| 33 | |
| 34 | func TestUpstreamClientSpans(t *testing.T) { |
| 35 | var gotTraceparent []string |
| 36 | upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 37 | gotTraceparent = append(gotTraceparent, r.Header.Get("Traceparent")) |
| 38 | w.WriteHeader(http.StatusOK) |
| 39 | })) |
| 40 | t.Cleanup(upstream.Close) |
| 41 | |
| 42 | t.Run("traced request", func(t *testing.T) { |
| 43 | gotTraceparent = nil |
| 44 | recorder := tracetest.NewSpanRecorder() |
| 45 | tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) |
| 46 | t.Cleanup(func() { _ = tp.Shutdown(context.Background()) }) |
| 47 | |
| 48 | // round robin selects index 1 first, which refuses connections, so |
| 49 | // the retry produces a second client span under the same server span |
| 50 | h := minimalHandler(1, |
| 51 | &Upstream{Host: new(Host), Dial: upstream.Listener.Addr().String()}, |
| 52 | &Upstream{Host: new(Host), Dial: deadUpstreamAddr(t)}, |
| 53 | ) |
| 54 | // HTTPTransport sets the URL scheme itself, so the span only records |
| 55 | // it if roundTrip calls SetScheme before the traced transport runs |
| 56 | caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) |
| 57 | t.Cleanup(cancel) |
| 58 | ht := new(HTTPTransport) |
| 59 | if err := ht.Provision(caddyCtx); err != nil { |
| 60 | t.Fatalf("provisioning transport: %v", err) |
| 61 | } |
| 62 | h.Transport = ht |
| 63 | h.tracedTransport = newTracedTransport(h.Transport) |
| 64 | |
| 65 | ctx, serverSpan := tp.Tracer("test").Start(context.Background(), "server", |
| 66 | trace.WithSpanKind(trace.SpanKindServer)) |
| 67 | req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil).WithContext(ctx) |
| 68 | req = prepareTestRequest(req) |
| 69 | rec := httptest.NewRecorder() |
| 70 | if err := h.ServeHTTP(rec, req, nil); err != nil { |
| 71 | t.Fatalf("ServeHTTP: %v", err) |
| 72 | } |
| 73 | serverSpan.End() |
| 74 | |
| 75 | var clientSpans []sdktrace.ReadOnlySpan |
| 76 | for _, s := range recorder.Ended() { |
| 77 | if s.SpanKind() == trace.SpanKindClient { |
| 78 | clientSpans = append(clientSpans, s) |
| 79 | } |
| 80 | } |
| 81 | if len(clientSpans) != 2 { |
| 82 | t.Fatalf("expected 2 client spans, got %d", len(clientSpans)) |
| 83 | } |
| 84 | for i, s := range clientSpans { |
| 85 | if s.Parent().SpanID() != serverSpan.SpanContext().SpanID() { |
| 86 | t.Errorf("client span %d: parent is not the server span", i) |
| 87 | } |
| 88 | } |
| 89 | if clientSpans[0].Status().Code != codes.Error { |
| 90 | t.Errorf("expected failed attempt to have error status, got %v", clientSpans[0].Status().Code) |
| 91 | } |
| 92 | |
| 93 | upstreamHost, upstreamPort, _ := net.SplitHostPort(upstream.Listener.Addr().String()) |
| 94 | attrs := attribute.NewSet(clientSpans[1].Attributes()...) |
| 95 | for key, want := range map[attribute.Key]string{ |
| 96 | "url.full": "http://" + upstream.Listener.Addr().String() + "/", |
| 97 | "server.address": upstreamHost, |
| 98 | "server.port": upstreamPort, |
| 99 | "http.response.status_code": "200", |
| 100 | } { |
| 101 | if got, _ := attrs.Value(key); got.Emit() != want { |
| 102 | t.Errorf("client span attribute %s: expected %q, got %q", key, want, got.Emit()) |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | if len(gotTraceparent) != 1 { |
| 107 | t.Fatalf("expected 1 upstream request, got %d", len(gotTraceparent)) |
| 108 | } |
| 109 | want := clientSpans[1].SpanContext().SpanID().String() |
| 110 | if !strings.Contains(gotTraceparent[0], want) { |
| 111 | t.Errorf("upstream traceparent %q does not reference client span %s", gotTraceparent[0], want) |
| 112 | } |
| 113 | }) |
| 114 | |
| 115 | t.Run("untraced request", func(t *testing.T) { |
| 116 | gotTraceparent = nil |
| 117 | h := minimalHandler(0, &Upstream{Host: new(Host), Dial: upstream.Listener.Addr().String()}) |
| 118 | h.tracedTransport = newTracedTransport(h.Transport) |
| 119 | |
| 120 | req := prepareTestRequest(httptest.NewRequest(http.MethodGet, "http://example.com/", nil)) |
| 121 | if err := h.ServeHTTP(httptest.NewRecorder(), req, nil); err != nil { |
| 122 | t.Fatalf("ServeHTTP: %v", err) |
| 123 | } |
| 124 | if len(gotTraceparent) != 1 || gotTraceparent[0] != "" { |
| 125 | t.Errorf("expected no traceparent upstream, got %q", gotTraceparent) |
| 126 | } |
| 127 | }) |
| 128 | } |