middleware.go 5.59 KB
Newer Older
1 2 3 4 5 6 7
package http

import (
	"fmt"
	"net/http"
	"runtime/debug"
	"strings"
8
	"time"
9 10 11

	"github.com/ethereum/go-ethereum/metrics"
	"github.com/ethereum/go-ethereum/swarm/api"
12
	"github.com/ethereum/go-ethereum/swarm/chunk"
13
	"github.com/ethereum/go-ethereum/swarm/log"
14
	"github.com/ethereum/go-ethereum/swarm/sctx"
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
	"github.com/ethereum/go-ethereum/swarm/spancontext"
	"github.com/pborman/uuid"
)

// Adapt chains h (main request handler) main handler to adapters (middleware handlers)
// Please note that the order of execution for `adapters` is FIFO (adapters[0] will be executed first)
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
	for i := range adapters {
		adapter := adapters[len(adapters)-1-i]
		h = adapter(h)
	}
	return h
}

type Adapter func(http.Handler) http.Handler

func SetRequestID(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		r = r.WithContext(SetRUID(r.Context(), uuid.New()[:8]))
		metrics.GetOrRegisterCounter(fmt.Sprintf("http.request.%s", r.Method), nil).Inc(1)
		log.Info("created ruid for request", "ruid", GetRUID(r.Context()), "method", r.Method, "url", r.RequestURI)

		h.ServeHTTP(w, r)
	})
}

41 42 43 44 45 46 47 48 49
func SetRequestHost(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		r = r.WithContext(sctx.SetHost(r.Context(), r.Host))
		log.Info("setting request host", "ruid", GetRUID(r.Context()), "host", sctx.GetHost(r.Context()))

		h.ServeHTTP(w, r)
	})
}

50 51 52 53 54
func ParseURI(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
55
			respondError(w, r, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest)
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
			return
		}
		if uri.Addr != "" && strings.HasPrefix(uri.Addr, "0x") {
			uri.Addr = strings.TrimPrefix(uri.Addr, "0x")

			msg := fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.<br/>
			Please click <a href='%[1]s'>here</a> if your browser does not redirect you within 5 seconds.<script>setTimeout("location.href='%[1]s';",5000);</script>`, "/"+uri.String())
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(msg))
			return
		}

		ctx := r.Context()
		r = r.WithContext(SetURI(ctx, uri))
		log.Debug("parsed request path", "ruid", GetRUID(r.Context()), "method", r.Method, "uri.Addr", uri.Addr, "uri.Path", uri.Path, "uri.Scheme", uri.Scheme)

		h.ServeHTTP(w, r)
	})
}

func InitLoggingResponseWriter(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
78
		tn := time.Now()
79

80 81
		writer := newLoggingResponseWriter(w)
		h.ServeHTTP(writer, r)
82 83

		ts := time.Since(tn)
84
		log.Info("request served", "ruid", GetRUID(r.Context()), "code", writer.statusCode, "time", ts)
85 86
		metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.time", r.Method), nil).Update(ts)
		metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.%d.time", r.Method, writer.statusCode), nil).Update(ts)
87 88 89
	})
}

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
// InitUploadTag creates a new tag for an upload to the local HTTP proxy
// if a tag is not named using the SwarmTagHeaderName, a fallback name will be used
// when the Content-Length header is set, an ETA on chunking will be available since the
// number of chunks to be split is known in advance (not including enclosing manifest chunks)
// the tag can later be accessed using the appropriate identifier in the request context
func InitUploadTag(h http.Handler, tags *chunk.Tags) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var (
			tagName        string
			err            error
			estimatedTotal int64 = 0
			contentType          = r.Header.Get("Content-Type")
			headerTag            = r.Header.Get(SwarmTagHeaderName)
		)
		if headerTag != "" {
			tagName = headerTag
			log.Trace("got tag name from http header", "tagName", tagName)
		} else {
			tagName = fmt.Sprintf("unnamed_tag_%d", time.Now().Unix())
		}

		if !strings.Contains(contentType, "multipart") && r.ContentLength > 0 {
			log.Trace("calculating tag size", "contentType", contentType, "contentLength", r.ContentLength)
			uri := GetURI(r.Context())
			if uri != nil {
				log.Debug("got uri from context")
				if uri.Addr == "encrypt" {
					estimatedTotal = calculateNumberOfChunks(r.ContentLength, true)
				} else {
					estimatedTotal = calculateNumberOfChunks(r.ContentLength, false)
				}
			}
		}

		log.Trace("creating tag", "tagName", tagName, "estimatedTotal", estimatedTotal)

		t, err := tags.New(tagName, estimatedTotal)
		if err != nil {
			log.Error("error creating tag", "err", err, "tagName", tagName)
		}

		log.Trace("setting tag id to context", "uid", t.Uid)
		ctx := sctx.SetTag(r.Context(), t.Uid)

		h.ServeHTTP(w, r.WithContext(ctx))
	})
}

138 139 140 141 142 143 144 145 146
func InstrumentOpenTracing(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		uri := GetURI(r.Context())
		if uri == nil || r.Method == "" || (uri != nil && uri.Scheme == "") {
			h.ServeHTTP(w, r) // soft fail
			return
		}
		spanName := fmt.Sprintf("http.%s.%s", r.Method, uri.Scheme)
		ctx, sp := spancontext.StartSpan(r.Context(), spanName)
147

148 149 150 151 152 153 154 155 156
		defer sp.Finish()
		h.ServeHTTP(w, r.WithContext(ctx))
	})
}

func RecoverPanic(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
157
				log.Error("panic recovery!", "stack trace", string(debug.Stack()), "url", r.URL.String(), "headers", r.Header)
158 159 160 161 162
			}
		}()
		h.ServeHTTP(w, r)
	})
}