cmd/teeproxy,internal/teeproxy,internal/breaker: delete

Requests are now teed to pkg.go.dev directly from godoc.org, so this
service is no longer needed.

Change-Id: I8190488244cec1f4faca63652b937a0703ccfd3a
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/279132
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/cmd/teeproxy/main.go b/cmd/teeproxy/main.go
deleted file mode 100644
index 7f38cab..0000000
--- a/cmd/teeproxy/main.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// The teeproxy hits the frontend with a URL from godoc.org.
-package main
-
-import (
-	"context"
-	"flag"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"os"
-
-	"golang.org/x/pkgsite/internal/auth"
-	"golang.org/x/pkgsite/internal/breaker"
-	"golang.org/x/pkgsite/internal/config"
-	"golang.org/x/pkgsite/internal/dcensus"
-	"golang.org/x/pkgsite/internal/log"
-	"golang.org/x/pkgsite/internal/secrets"
-	"golang.org/x/pkgsite/internal/teeproxy"
-)
-
-func main() {
-	var credsFile = flag.String("creds", "", "filename for credentials, when running locally")
-	flag.Usage = func() {
-		fmt.Fprintf(flag.CommandLine.Output(), "usage: %s [flags]\n", os.Args[0])
-		flag.PrintDefaults()
-	}
-	flag.Parse()
-	ctx := context.Background()
-
-	cfg, err := config.Init(ctx)
-	if err != nil {
-		log.Fatal(ctx, err)
-	}
-	cfg.Dump(os.Stderr)
-
-	log.SetLevel(cfg.LogLevel)
-	if cfg.OnGCP() {
-		_, err := log.UseStackdriver(ctx, cfg, "teeproxy-log")
-		if err != nil {
-			log.Fatal(ctx, err)
-		}
-	}
-	client := &http.Client{}
-	var jsonCreds []byte
-	if *credsFile != "" {
-		jsonCreds, err = ioutil.ReadFile(*credsFile)
-		if err != nil {
-			log.Fatal(ctx, err)
-		}
-	} else {
-		const secretName = "load-test-agent-creds"
-		log.Infof(ctx, "getting secret %q", secretName)
-		s, err := secrets.Get(context.Background(), secretName)
-		if err != nil {
-			log.Infof(ctx, "secret %q not found", secretName)
-		} else {
-			jsonCreds = []byte(s)
-		}
-	}
-
-	if jsonCreds != nil {
-		client, err = auth.NewClient(ctx, jsonCreds, false)
-		if err != nil {
-			log.Fatal(ctx, err)
-		}
-	}
-
-	views := append(dcensus.ServerViews,
-		teeproxy.TeeproxyGddoRequestCount,
-		teeproxy.TeeproxyPkgGoDevRequestCount,
-		teeproxy.TeeproxyGddoRequestLatencyDistribution,
-		teeproxy.TeeproxyPkgGoDevRequestLatencyDistribution,
-		teeproxy.TeeproxyPkgGoDevBrokenPathCount,
-	)
-	dcensus.Init(cfg, views...)
-
-	http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
-		http.ServeFile(w, r, "content/static/img/favicon.ico")
-	})
-	server, err := teeproxy.NewServer(teeproxy.Config{
-		AuthKey:   cfg.Teeproxy.AuthKey,
-		AuthValue: cfg.Teeproxy.AuthValue,
-		Rate:      cfg.Teeproxy.Rate,
-		Burst:     cfg.Teeproxy.Burst,
-		BreakerConfig: breaker.Config{
-			FailsToRed:       cfg.Teeproxy.FailsToRed,
-			FailureThreshold: cfg.Teeproxy.FailureThreshold,
-			GreenInterval:    cfg.Teeproxy.GreenInterval,
-			MinTimeout:       cfg.Teeproxy.MinTimeout,
-			MaxTimeout:       cfg.Teeproxy.MaxTimeout,
-			SuccsToGreen:     cfg.Teeproxy.SuccsToGreen,
-		},
-		Hosts:  cfg.Teeproxy.Hosts,
-		Client: client,
-	})
-	if err != nil {
-		log.Fatal(ctx, err)
-	}
-	http.Handle("/", server)
-
-	addr := cfg.HostAddr("localhost:8020")
-	log.Infof(ctx, "Listening on addr %s", addr)
-	log.Fatal(ctx, http.ListenAndServe(addr, nil))
-}
diff --git a/internal/breaker/breaker.go b/internal/breaker/breaker.go
deleted file mode 100644
index 98e40a7..0000000
--- a/internal/breaker/breaker.go
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package breaker implements the circuit breaker pattern.
-// see https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn589784(v=pandp.10).
-//
-// This package uses different terminologies for the state of the circuit breaker
-// for better readability. Since it is unintuitive for users unfamiliar with
-// circuits that a "closed" state means to "let the requests pass through" and
-// "open" state means "don't let anything through", we use the colors red,
-// yellow, and green instead of open, half-open, and closed, respectively.
-//
-// When the API is stable, this package may be factored out to an external location.
-package breaker
-
-import (
-	"fmt"
-	"sync"
-	"time"
-)
-
-// For testing.
-var timeNow = time.Now
-
-// numBuckets is the number of buckets in the breaker's sliding window.
-const numBuckets = 8
-
-// State represents the current state the breaker is in.
-type State int
-
-// The breaker can have three states: Red, Yellow, or Green.
-const (
-	// Red state means that requests should not be allowed.
-	Red State = iota
-	// Yellow state means that certain requests may proceed with caution.
-	Yellow
-	// Green state means that requests are allowed to pass.
-	Green
-)
-
-// String returns the string version of the state.
-func (s State) String() string {
-	switch s {
-	case Red:
-		return "red state"
-	case Yellow:
-		return "yellow state"
-	case Green:
-		return "green state"
-	default:
-		return "invalid state"
-	}
-}
-
-// Config holds the configuration values for a breaker.
-type Config struct {
-	// FailsToRed is the number of failures to exceed before the breaker shifts
-	// from green to red state.
-	FailsToRed int
-	// FailureThreshold is the failure ratio to exceed before the breaker
-	// shifts from green to red state.
-	FailureThreshold float64
-	// GreenInterval is the length of the interval with which the breaker
-	// checks for conditions to move from green to red state.
-	GreenInterval time.Duration
-	// MinTimeout is the minimum timeout period that the breaker stays in the
-	// red state before moving to the yellow state.
-	MinTimeout time.Duration
-	// MaxTimeout is the maxmimum timeout period that the breaker stays in the
-	// red state before moving to the yellow state.
-	MaxTimeout time.Duration
-	// SuccsToGreen is the number of successes required to shift from the
-	// yellow state to the green state.
-	SuccsToGreen int
-}
-
-// Breaker represents a circuit breaker.
-//
-// In the green state, the breaker remains green until it encounters a time
-// window of length GreenInterval where there are more than FailsToRed failures
-// and a failureRatio of more than FailureThreshold, in which case the
-// state becomes red.
-//
-// In the red state, the breaker halts all requests and waits for a timeout period
-// before shifting to the yellow state.
-//
-// In the yellow state, the breaker allows the first SuccsToGreen requests. If
-// any of these fail, the state reverts to red. Otherwise, the state becomes
-// green again.
-//
-// The timeout period is initially set to MinTimeout when the breaker shifts
-// from green to yellow. By default, the timeout period is doubled each time
-// the breaker fails to shift from the yellow state to the green state and is
-// capped at MaxTimeout.
-type Breaker struct {
-	config Config
-
-	// buckets represents a time sliding window, implemented as a ring buffer.
-	buckets [numBuckets]bucket
-	// granularity is the length of time each bucket is responsible for.
-	granularity time.Duration
-
-	mu               sync.Mutex
-	state            State
-	cur              int
-	consecutiveSuccs int
-	timeout          time.Duration
-	lastEvent        time.Time
-}
-
-// New creates a Breaker with the given configuration.
-func New(config Config) (*Breaker, error) {
-	switch {
-	case config.FailsToRed <= 0:
-		return nil, fmt.Errorf("illegal value for FailsToRed")
-	case config.FailureThreshold <= 0, config.FailureThreshold > 1:
-		return nil, fmt.Errorf("illegal value for FailureThreshold")
-	case config.GreenInterval <= 0:
-		return nil, fmt.Errorf("illegal value for GreenInterval")
-	case config.MinTimeout <= 0:
-		return nil, fmt.Errorf("illegal value for MinTimeout")
-	case config.MaxTimeout <= 0:
-		return nil, fmt.Errorf("illegal value for MaxTimeout")
-	case config.SuccsToGreen <= 0:
-		return nil, fmt.Errorf("illegal value for SuccsToGreen")
-	default:
-		return &Breaker{
-			config:      config,
-			state:       Green,
-			granularity: config.GreenInterval / numBuckets,
-			timeout:     config.MinTimeout,
-			lastEvent:   timeNow(),
-		}, nil
-	}
-}
-
-// State returns the state of the breaker.
-func (b *Breaker) State() State {
-	b.mu.Lock()
-	defer b.mu.Unlock()
-	return b.checkState()
-}
-
-// checkState returns the state of the breaker without obtaining a mutex lock.
-// The state is updated if sufficient time has passed since the last event.
-func (b *Breaker) checkState() State {
-	now := timeNow()
-	if b.state == Red && now.After(b.lastEvent.Add(b.timeout)) {
-		b.state = Yellow
-	}
-	return b.state
-}
-
-// Allow reports whether an event may happen at time now. If Allow returns
-// true, the user must then call Record to register whether the event succeeded.
-func (b *Breaker) Allow() bool {
-	return b.State() != Red
-}
-
-// Record registers the success or failure of an event with the circuit breaker.
-// Use this function after a call to Allow returned true.
-func (b *Breaker) Record(success bool) {
-	b.mu.Lock()
-	defer b.mu.Unlock()
-	b.update(timeNow())
-	if success {
-		b.succeeded()
-	} else {
-		b.failed()
-	}
-}
-
-// succeeded signals that an allowed request has succeeded. The breaker state is
-// changed if necessary.
-func (b *Breaker) succeeded() {
-	b.buckets[b.cur].successes++
-	b.consecutiveSuccs++
-	if b.checkState() == Yellow && b.consecutiveSuccs >= b.config.SuccsToGreen {
-		b.state = Green
-		b.timeout = b.config.MinTimeout
-		b.resetCounts()
-	}
-}
-
-// failed signals that an allowed request has failed. The breaker state is
-// changed if necessary.
-func (b *Breaker) failed() {
-	b.buckets[b.cur].failures++
-	b.consecutiveSuccs = 0
-	switch b.checkState() {
-	case Yellow:
-		b.increaseTimeout()
-		b.state = Red
-	case Green:
-		// Check conditions to move to red state.
-		successes, failures := b.counts()
-		totalRequests := successes + failures
-		if failures <= b.config.FailsToRed || totalRequests == 0 {
-			return
-		}
-		failureRatio := float64(failures) / float64(totalRequests)
-		if failureRatio > b.config.FailureThreshold {
-			b.state = Red
-		}
-	}
-}
-
-// update updates the values of breaker due to the passage of time.
-func (b *Breaker) update(now time.Time) {
-	// Ignore updates from the past.
-	if now.Before(b.lastEvent) {
-		return
-	}
-	since := now.Sub(b.lastEvent)
-	b.advance(int(since / b.granularity))
-	b.lastEvent = now
-}
-
-// advance advances the breaker's sliding window by n buckets. The counts of
-// successes and failures are also updated to include only the buckets in the
-// current window.
-func (b *Breaker) advance(n int) {
-	if n >= len(b.buckets) {
-		b.resetCounts()
-		b.cur = 0
-		return
-	}
-	for i := 0; i < n; i++ {
-		b.cur = (b.cur + 1) % len(b.buckets)
-		b.buckets[b.cur].reset()
-	}
-}
-
-// counts returns the total number of successes and failures in the breaker's
-// sliding window.
-func (b *Breaker) counts() (successes, failures int) {
-	for _, bu := range b.buckets {
-		successes += bu.successes
-		failures += bu.failures
-	}
-	return successes, failures
-}
-
-// resetCounts resets all the buckets in breaker.
-func (b *Breaker) resetCounts() {
-	for i := range b.buckets {
-		b.buckets[i].reset()
-	}
-}
-
-// increaseTimeout exponentially increases the breaker's timeout period,
-// capped at maxTimeout.
-func (b *Breaker) increaseTimeout() {
-	if 2*b.timeout <= b.config.MaxTimeout {
-		b.timeout *= 2
-		return
-	}
-	b.timeout = b.config.MaxTimeout
-}
-
-type bucket struct {
-	successes int
-	failures  int
-}
-
-// reset resets the values in the bucket.
-func (bu *bucket) reset() {
-	bu.successes = 0
-	bu.failures = 0
-}
diff --git a/internal/breaker/breaker_test.go b/internal/breaker/breaker_test.go
deleted file mode 100644
index b773980..0000000
--- a/internal/breaker/breaker_test.go
+++ /dev/null
@@ -1,717 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package breaker
-
-import (
-	"sync"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-cmp/cmp/cmpopts"
-)
-
-func TestBucketReset(t *testing.T) {
-	bu := bucket{12, 8}
-	bu.reset()
-	if bu.successes != 0 {
-		t.Errorf("got successes = %d, want %d", bu.successes, 0)
-	}
-	if bu.failures != 0 {
-		t.Errorf("got failures = %d, want %d", bu.failures, 0)
-	}
-}
-
-func TestResetCounts(t *testing.T) {
-	b := newTestBreaker(Config{})
-	for i := 0; i < len(b.buckets); i++ {
-		b.buckets[i].successes = 10
-		b.buckets[i].failures = 15
-	}
-
-	b.resetCounts()
-	testBuckets(t, b.buckets[:], 0, 0)
-	testCounts(t, b, 0, 0, 0)
-}
-
-func TestNewBreaker(t *testing.T) {
-	timeNow = func() time.Time {
-		return time.Date(2020, time.May, 26, 18, 0, 0, 0, time.UTC)
-	}
-	got, err := New(Config{
-		FailsToRed:       10,
-		FailureThreshold: 0.65,
-		GreenInterval:    20 * time.Second,
-		MinTimeout:       30 * time.Second,
-		MaxTimeout:       16 * time.Minute,
-		SuccsToGreen:     15,
-	})
-	if err != nil {
-		t.Fatalf("New() returned %e, want nil", err)
-	}
-	want := &Breaker{
-		config: Config{
-			FailsToRed:       10,
-			FailureThreshold: 0.65,
-			GreenInterval:    20 * time.Second,
-			MinTimeout:       30 * time.Second,
-			MaxTimeout:       16 * time.Minute,
-			SuccsToGreen:     15,
-		},
-		buckets:          [numBuckets]bucket{},
-		granularity:      2500 * time.Millisecond,
-		state:            Green,
-		cur:              0,
-		consecutiveSuccs: 0,
-		timeout:          30 * time.Second,
-		lastEvent:        time.Date(2020, time.May, 26, 18, 0, 0, 0, time.UTC),
-	}
-
-	diff := cmp.Diff(want, got, cmpopts.IgnoreUnexported(sync.Mutex{}), cmp.AllowUnexported(Breaker{}, bucket{}))
-	if diff != "" {
-		t.Fatalf("mismatch (-want +got):\n%s", diff)
-	}
-}
-
-func TestIllegalBreaker(t *testing.T) {
-	for _, test := range []struct {
-		name   string
-		config Config
-	}{
-		{
-			name: "FailsToRed cannot be 0",
-			config: Config{
-				FailsToRed:       0,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "FailsToRed cannot be negative",
-			config: Config{
-				FailsToRed:       -5,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "FailureThreshold cannot be 0",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "FailureThreshold cannot be negative",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: -0.8,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "FailureThreshold cannot exceed 1",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 1.2,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "GreenInterval cannot be 0",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    0,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "GreenInterval cannot be negative",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    -4 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "MinTimeout cannot be 0",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       0,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "MinTimeout cannot be negative",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       -2 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "MaxTimeout cannot be 0",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       0,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "MaxTimeout cannot be negative",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       -12 * time.Minute,
-				SuccsToGreen:     15,
-			},
-		},
-		{
-			name: "SuccsToGreen cannot be 0",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     0,
-			},
-		},
-		{
-			name: "SuccsToGreen cannot be negative",
-			config: Config{
-				FailsToRed:       8,
-				FailureThreshold: 0.65,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     -7,
-			},
-		},
-		{
-			name: "multiple illegal values return error",
-			config: Config{
-				FailsToRed:       0,
-				FailureThreshold: 1.4,
-				GreenInterval:    20 * time.Second,
-				MinTimeout:       -30 * time.Second,
-				MaxTimeout:       16 * time.Minute,
-				SuccsToGreen:     100,
-			},
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			b, err := New(test.config)
-			if err == nil {
-				t.Fatalf("New() returned nil error")
-			}
-			if b != nil {
-				t.Fatalf("New() returned %+v, want nil", b)
-			}
-		})
-	}
-}
-
-func TestBreakerGranularity(t *testing.T) {
-	for _, test := range []struct {
-		config Config
-		want   time.Duration
-	}{
-		{
-			config: Config{},
-			want:   1250 * time.Millisecond,
-		},
-		{
-			config: Config{GreenInterval: 1 * time.Second},
-			want:   125 * time.Millisecond,
-		},
-		{
-			config: Config{GreenInterval: 3 * time.Second},
-			want:   375 * time.Millisecond,
-		},
-		{
-			config: Config{GreenInterval: 1 * time.Minute},
-			want:   7500 * time.Millisecond,
-		},
-		{
-			config: Config{GreenInterval: 1 * time.Hour},
-			want:   450 * time.Second,
-		},
-	} {
-		b := newTestBreaker(test.config)
-		if b.granularity != test.want {
-			t.Errorf("b.granularity = %d, want %d", b.granularity, test.want)
-		}
-	}
-}
-
-func TestState(t *testing.T) {
-	for _, want := range []State{
-		Green,
-		Yellow,
-		Red,
-	} {
-		b := newTestBreaker(Config{})
-		b.state = want
-		if got := b.checkState(); got != want {
-			t.Errorf("b.checkState() = %s, got %s", got, want)
-		}
-		if got := b.State(); got != want {
-			t.Errorf("b.State() = %s, want %s", got, want)
-		}
-	}
-}
-func TestAllow(t *testing.T) {
-	for _, test := range []struct {
-		state       State
-		shouldAllow bool
-	}{
-		{
-			state:       Green,
-			shouldAllow: true,
-		},
-		{
-			state:       Yellow,
-			shouldAllow: true,
-		},
-		{
-			state:       Red,
-			shouldAllow: false,
-		},
-	} {
-		b := newTestBreaker(Config{})
-		b.state = test.state
-		allowed := b.Allow()
-		if allowed != test.shouldAllow {
-			t.Errorf("b.Allow() = %t in %s, want %t", allowed, test.state, test.shouldAllow)
-		}
-	}
-}
-
-func TestSuccesses(t *testing.T) {
-	b := newTestBreaker(Config{})
-	b.succeeded()
-	testCounts(t, b, 1, 1, 0)
-	b.succeeded()
-	testCounts(t, b, 2, 2, 0)
-	b.succeeded()
-	testCounts(t, b, 3, 3, 0)
-}
-
-func TestFailures(t *testing.T) {
-	b := newTestBreaker(Config{})
-	b.failed()
-	testCounts(t, b, 0, 0, 1)
-	b.failed()
-	testCounts(t, b, 0, 0, 2)
-	b.failed()
-	testCounts(t, b, 0, 0, 3)
-}
-
-func TestSucceededAndFailed(t *testing.T) {
-	b := newTestBreaker(Config{})
-	b.succeeded()
-	testCounts(t, b, 1, 1, 0)
-	b.failed()
-	testCounts(t, b, 0, 1, 1)
-	b.failed()
-	testCounts(t, b, 0, 1, 2)
-	b.succeeded()
-	testCounts(t, b, 1, 2, 2)
-	b.succeeded()
-	testCounts(t, b, 2, 3, 2)
-	b.succeeded()
-	testCounts(t, b, 3, 4, 2)
-	b.failed()
-	testCounts(t, b, 0, 4, 3)
-}
-
-func TestUpdate(t *testing.T) {
-	now := time.Now()
-	b := newTestBreaker(Config{})
-	b.lastEvent = now
-	b.granularity = 1 * time.Second
-	for i := 0; i < len(b.buckets); i++ {
-		b.buckets[i].successes = 4
-		b.buckets[i].failures = 9
-	}
-
-	// Update 0 buckets.
-	b.update(now.Add(-1 * time.Second))
-	if b.cur != 0 {
-		t.Errorf("cur: got %d, want %d", b.cur, 0)
-	}
-	testBuckets(t, b.buckets[:], 4, 9)
-	testCounts(t, b, 0, 4*len(b.buckets), 9*len(b.buckets))
-
-	// Update next 3 buckets.
-	b.update(now.Add(3 * time.Second))
-	if b.cur != 3 {
-		t.Errorf("cur: got %d, want %d", b.cur, 3)
-	}
-	testBuckets(t, b.buckets[:1], 4, 9)
-	testBuckets(t, b.buckets[1:4], 0, 0)
-	testBuckets(t, b.buckets[4:], 4, 9)
-	testCounts(t, b, 0, 4*len(b.buckets)-12, 9*len(b.buckets)-27)
-
-	// Update all buckets.
-	b.update(now.Add(1003 * time.Second))
-	expectedCur := 0
-	if b.cur != expectedCur {
-		t.Errorf("cur: got %d, want %d", b.cur, expectedCur)
-	}
-	testBuckets(t, b.buckets[:], 0, 0)
-	testCounts(t, b, 0, 0, 0)
-}
-
-func TestStateChanges(t *testing.T) {
-	for _, test := range []struct {
-		name         string
-		config       Config
-		preSuccesses int
-		preFailures  int
-		fromState    State
-		allow        bool
-		success      bool
-		sleep        time.Duration
-		toState      State
-	}{
-		{
-			name:         "breaker state remains green when FailsToRed is not exceeded",
-			config:       Config{FailsToRed: 8, FailureThreshold: 0.5},
-			preSuccesses: 6,
-			preFailures:  7,
-			fromState:    Green,
-			allow:        true,
-			success:      false,
-			toState:      Green,
-		},
-		{
-			name:         "breaker state remains green when FailureThreshold is not exceeded",
-			config:       Config{FailsToRed: 2, FailureThreshold: 0.8},
-			preSuccesses: 3,
-			preFailures:  6,
-			fromState:    Green,
-			allow:        true,
-			success:      false,
-			toState:      Green,
-		},
-		{
-			name:         "breaker state remains green when failure ratio = FailureThreshold",
-			config:       Config{FailsToRed: 10, FailureThreshold: 0.5},
-			preSuccesses: 20,
-			preFailures:  19,
-			fromState:    Green,
-			allow:        true,
-			success:      false,
-			toState:      Green,
-		},
-		{
-			name:         "breaker state remains green when failures = FailsToRed",
-			config:       Config{FailsToRed: 10, FailureThreshold: 0.3},
-			preSuccesses: 10,
-			preFailures:  9,
-			fromState:    Green,
-			allow:        true,
-			success:      false,
-			toState:      Green,
-		},
-		{
-			name:         "breaker state changes to red when FailureThreshold is exceeded and after FailsToRed has been exceeded",
-			config:       Config{FailsToRed: 10, FailureThreshold: 0.5},
-			preSuccesses: 20,
-			preFailures:  20,
-			fromState:    Green,
-			allow:        true,
-			success:      false,
-			toState:      Red,
-		},
-		{
-			name:         "breaker state changes to red when FailsToRed is exceeded and after FailureThreshold has been exceeded",
-			config:       Config{FailsToRed: 20, FailureThreshold: 0.3},
-			preSuccesses: 20,
-			preFailures:  20,
-			fromState:    Green,
-			allow:        true,
-			success:      false,
-			toState:      Red,
-		},
-		{
-			name:         "breaker state changes from green to red",
-			config:       Config{FailsToRed: 4, FailureThreshold: 0.5},
-			preSuccesses: 4,
-			preFailures:  4,
-			fromState:    Green,
-			allow:        true,
-			success:      false,
-			toState:      Red,
-		},
-		{
-			name:         "failure in yellow state changes breaker to red state",
-			config:       Config{},
-			preSuccesses: 0,
-			preFailures:  0,
-			fromState:    Yellow,
-			allow:        true,
-			success:      false,
-			toState:      Red,
-		},
-		{
-			name:         "breaker state changes from yellow to green",
-			config:       Config{SuccsToGreen: 1},
-			preSuccesses: 0,
-			preFailures:  0,
-			fromState:    Yellow,
-			allow:        true,
-			success:      true,
-			toState:      Green,
-		},
-		{
-			name:         "breaker state changes from red to yellow",
-			config:       Config{MinTimeout: 1 * time.Second},
-			preSuccesses: 0,
-			preFailures:  0,
-			fromState:    Red,
-			sleep:        1*time.Second + 1*time.Nanosecond,
-			toState:      Yellow,
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			now := time.Time{}
-			timeNow = func() time.Time { return now }
-			b := newTestBreaker(test.config)
-			b.state = test.fromState
-			b.buckets[0].successes = test.preSuccesses
-			b.buckets[0].failures = test.preFailures
-
-			allowed := b.Allow()
-			if allowed != test.allow {
-				t.Fatalf("b.Allow() = %t in %s, want %t", allowed, test.fromState, test.allow)
-			}
-			if test.allow {
-				b.Record(test.success)
-			}
-
-			// Pseudo sleep.
-			now = now.Add(test.sleep)
-
-			if state := b.State(); state != test.toState {
-				t.Errorf("b.State() = %s, want %s", state, test.toState)
-			}
-		})
-	}
-}
-
-func TestRunningBreaker(t *testing.T) {
-	now := time.Time{}
-	timeNow = func() time.Time { return now }
-	b := newTestBreaker(Config{
-		GreenInterval: 5 * time.Second,
-	})
-
-	// The following tests happen sequentially. The tests' states depend on previous tests.
-	for _, test := range []struct {
-		name                 string
-		firstSleep           time.Duration
-		allow                bool
-		secondSleep          time.Duration
-		success              bool
-		wantConsecutiveSuccs int
-		wantSuccesses        int
-		wantFailures         int
-	}{
-		{
-			name:                 "successFunc called after a long time updates counts",
-			firstSleep:           20 * time.Second,
-			allow:                true,
-			secondSleep:          20 * time.Second,
-			success:              true,
-			wantConsecutiveSuccs: 1,
-			wantSuccesses:        1,
-			wantFailures:         0,
-		},
-		{
-			name:                 "success within GreenInterval updates counts correctly",
-			firstSleep:           1 * time.Second,
-			allow:                true,
-			secondSleep:          3 * time.Second,
-			success:              true,
-			wantConsecutiveSuccs: 2,
-			wantSuccesses:        2,
-			wantFailures:         0,
-		},
-		{
-			name:                 "success after a long time updates counts correctly",
-			firstSleep:           30 * time.Second,
-			allow:                true,
-			secondSleep:          80 * time.Second,
-			success:              true,
-			wantConsecutiveSuccs: 3,
-			wantSuccesses:        1,
-			wantFailures:         0,
-		},
-		{
-			name:                 "failure within GreenInterval updates counts correctly",
-			firstSleep:           1 * time.Second,
-			allow:                true,
-			secondSleep:          3 * time.Second,
-			success:              false,
-			wantConsecutiveSuccs: 0,
-			wantSuccesses:        1,
-			wantFailures:         1,
-		},
-		{
-			name:                 "second failure within GreenInterval updates counts correctly",
-			firstSleep:           1 * time.Millisecond,
-			allow:                true,
-			secondSleep:          3 * time.Millisecond,
-			success:              false,
-			wantConsecutiveSuccs: 0,
-			wantSuccesses:        1,
-			wantFailures:         2,
-		},
-		{
-			name:                 "failure after a long time updates counts correctly",
-			firstSleep:           10 * time.Second,
-			allow:                true,
-			secondSleep:          4 * time.Minute,
-			success:              false,
-			wantConsecutiveSuccs: 0,
-			wantSuccesses:        0,
-			wantFailures:         1,
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			now = now.Add(test.firstSleep)
-			allowed := b.Allow()
-
-			if allowed != test.allow {
-				t.Fatalf("breaker.Allow() = %t, want %t", allowed, test.allow)
-			}
-
-			now = now.Add(test.secondSleep)
-			if test.allow {
-				b.Record(test.success)
-			}
-
-			testCounts(t, b, test.wantConsecutiveSuccs, test.wantSuccesses, test.wantFailures)
-		})
-	}
-}
-
-func TestIncreaseTimeout(t *testing.T) {
-	b := newTestBreaker(Config{
-		MinTimeout: 1 * time.Second,
-		MaxTimeout: 12 * time.Second,
-	})
-	b.timeout = 3 * time.Second
-
-	b.increaseTimeout()
-	testTimeouts(t, b, 6*time.Second, 1*time.Second, 12*time.Second)
-	b.increaseTimeout()
-	testTimeouts(t, b, 12*time.Second, 1*time.Second, 12*time.Second)
-	b.increaseTimeout()
-	testTimeouts(t, b, 12*time.Second, 1*time.Second, 12*time.Second)
-
-	b.config.MaxTimeout = 14 * time.Second
-	testTimeouts(t, b, 12*time.Second, 1*time.Second, 14*time.Second)
-	b.increaseTimeout()
-	testTimeouts(t, b, 14*time.Second, 1*time.Second, 14*time.Second)
-	b.increaseTimeout()
-	testTimeouts(t, b, 14*time.Second, 1*time.Second, 14*time.Second)
-}
-
-// newTestBreaker is like New, but with default values for easier testing.
-func newTestBreaker(config Config) *Breaker {
-	if config.FailsToRed <= 0 {
-		config.FailsToRed = 10
-	}
-	if config.FailureThreshold <= 0 {
-		config.FailureThreshold = 0.5
-	}
-	if config.GreenInterval <= 0 {
-		config.GreenInterval = 10 * time.Second
-	}
-	if config.MinTimeout <= 0 {
-		config.MinTimeout = 30 * time.Second
-	}
-	if config.MaxTimeout <= 0 {
-		config.MaxTimeout = 4 * time.Minute
-	}
-	if config.SuccsToGreen <= 0 {
-		config.SuccsToGreen = 20
-	}
-	b, _ := New(config)
-	return b
-}
-
-func testCounts(t *testing.T, b *Breaker, consecutiveSuccs, wantSuccesses, wantFailures int) {
-	if b.consecutiveSuccs != consecutiveSuccs {
-		t.Errorf("b.consecutiveSuccs = %d, want %d", b.consecutiveSuccs, consecutiveSuccs)
-	}
-	successes, failures := b.counts()
-	if successes != wantSuccesses {
-		t.Errorf("successes = %d, want %d", successes, wantSuccesses)
-	}
-	if failures != wantFailures {
-		t.Errorf("failures = %d, want %d", failures, wantFailures)
-	}
-}
-
-func testBuckets(t *testing.T, buckets []bucket, successes, failures int) {
-	for i, bu := range buckets {
-		if bu.successes != successes {
-			t.Errorf("slice bucket %d successes: got %d, want %d", i, bu.successes, successes)
-		}
-		if bu.failures != failures {
-			t.Errorf("slice bucket %d failures: got %d, want %d", i, bu.failures, failures)
-		}
-	}
-}
-
-func testTimeouts(t *testing.T, b *Breaker, timeout, minTimeout, maxTimeout time.Duration) {
-	if b.timeout != timeout {
-		t.Errorf("b.timeout = %s, want %s", b.timeout, timeout)
-	}
-	if b.config.MinTimeout != minTimeout {
-		t.Errorf("b.config.MinTimeout = %s, want %s", b.config.MinTimeout, minTimeout)
-	}
-	if b.config.MaxTimeout != maxTimeout {
-		t.Errorf("b.config.MaxTimeout = %s, want %s", b.config.MaxTimeout, maxTimeout)
-	}
-}
diff --git a/internal/teeproxy/teeproxy.go b/internal/teeproxy/teeproxy.go
deleted file mode 100644
index 29616d8..0000000
--- a/internal/teeproxy/teeproxy.go
+++ /dev/null
@@ -1,457 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package teeproxy provides functionality for running a service which tees
-// traffic to pkg.go.dev.
-package teeproxy
-
-import (
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"strconv"
-	"strings"
-	"time"
-
-	"go.opencensus.io/plugin/ochttp"
-	"go.opencensus.io/stats"
-	"go.opencensus.io/stats/view"
-	"go.opencensus.io/tag"
-	"golang.org/x/net/context/ctxhttp"
-	"golang.org/x/pkgsite/internal/breaker"
-	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/log"
-	"golang.org/x/time/rate"
-)
-
-// Server receives requests from godoc.org and tees them to specified hosts.
-type Server struct {
-	hosts    []string
-	client   *http.Client
-	limiter  *rate.Limiter
-	breakers map[string]*breaker.Breaker
-	// authKey and authValue are used to indicate to pkg.go.dev that the
-	// request is coming from the teeproxy.
-	authKey, authValue string
-}
-
-// Config contains configuration values for Server.
-type Config struct {
-	// AuthKey is the name of the header that is used by pkg.go.dev to
-	// determine if a request is coming from a trusted source.
-	AuthKey string
-	// AuthValue is the value of the header that is used by pkg.go.dev to
-	// determine that the request is coming from the teeproxy.
-	AuthValue string
-	// Hosts is the list of hosts that the teeproxy forwards requests to.
-	Hosts []string
-	// Client is the HTTP client used by the teeproxy to forward requests
-	// to the hosts.
-	Client *http.Client
-	// Rate is the rate at which requests are rate limited.
-	Rate float64
-	// Burst is the maximum burst of requests permitted.
-	Burst         int
-	BreakerConfig breaker.Config
-}
-
-// RequestEvent stores information about a godoc.org or pkg.go.dev request.
-type RequestEvent struct {
-	Host    string
-	Path    string
-	URL     string
-	Header  http.Header
-	Latency time.Duration
-	Status  int
-	Error   error
-	// IsRobot reports whether this request came from a robot.
-	// https://github.com/golang/gddo/blob/a4ebd2f/gddo-server/main.go#L152
-	IsRobot bool
-}
-
-var gddoToPkgGoDevRequest = map[string]string{
-	"/":            "/",
-	"/-/about":     "/about",
-	"/-/go":        "/std",
-	"/-/subrepo":   "/search?q=golang.org/x",
-	"/C":           "/C",
-	"/favicon.ico": "/favicon.ico",
-}
-
-// expected404s are a list of godoc.org URLs that we expected to 404 on
-// pkg.go.dev.
-var expected404s = map[string]bool{
-	"/-/bootstrap.min.css":           true,
-	"/-/bootstrap.min.js":            true,
-	"/-/bot":                         true,
-	"/-/jquery-2.0.3.min.js":         true,
-	"/-/refresh":                     true,
-	"/-/sidebar.css":                 true,
-	"/-/site.css":                    true,
-	"/-/site.js":                     true,
-	"/BingSiteAuth.xml":              true,
-	"/google3d2f3cd4cc2bb44b.html":   true,
-	"/humans.txt":                    true,
-	"/robots.txt":                    true,
-	"/third_party/jquery.timeago.js": true,
-}
-
-// statusRedBreaker is a custom HTTP status code that denotes that a request
-// cannot be handled because the circuit breaker is in the red state.
-const statusRedBreaker = 530
-
-var (
-	// keyTeeproxyStatus is a census tag for teeproxy response status codes.
-	keyTeeproxyStatus = tag.MustNewKey("teeproxy.status")
-	// keyTeeproxyHost is a census tag for hosts that teeproxy forward requests to.
-	keyTeeproxyHost = tag.MustNewKey("teeproxy.host")
-	// keyTeeproxyPath is a census tag for godoc.org paths that don't work in
-	// pkg.go.dev.
-	keyTeeproxyPath = tag.MustNewKey("teeproxy.path")
-	// teeproxyGddoLatency holds observed latency in individual teeproxy
-	// requests from godoc.org.
-	teeproxyGddoLatency = stats.Float64(
-		"go-discovery/teeproxy/gddo-latency",
-		"Latency of a teeproxy request from godoc.org.",
-		stats.UnitMilliseconds,
-	)
-	// teeproxyPkgGoDevLatency holds observed latency in individual teeproxy
-	// requests to pkg.go.dev.
-	teeproxyPkgGoDevLatency = stats.Float64(
-		"go-discovery/teeproxy/pkgGoDev-latency",
-		"Latency of a teeproxy request to pkg.go.dev.",
-		stats.UnitMilliseconds,
-	)
-	// teeproxyPkgGoDevBrokenPaths counts broken paths in pkg.go.dev that work
-	// in godoc.org
-	teeproxyPkgGoDevBrokenPaths = stats.Int64(
-		"go-discovery/teeproxy/pkgGoDev-brokenPaths",
-		"Count of paths that error in pkg.go.dev but 200 in godoc.org.",
-		stats.UnitDimensionless,
-	)
-
-	// TeeproxyGddoRequestLatencyDistribution aggregates the latency of
-	// teeproxy requests from godoc.org by status code and host.
-	TeeproxyGddoRequestLatencyDistribution = &view.View{
-		Name:        "go-discovery/teeproxy/gddo-latency",
-		Measure:     teeproxyGddoLatency,
-		Aggregation: ochttp.DefaultLatencyDistribution,
-		Description: "Teeproxy latency from godoc.org, by response status code",
-		TagKeys:     []tag.Key{keyTeeproxyStatus, keyTeeproxyHost},
-	}
-	// TeeproxyPkgGoDevRequestLatencyDistribution aggregates the latency of
-	// teeproxy requests to pkg.go.dev by status code and host.
-	TeeproxyPkgGoDevRequestLatencyDistribution = &view.View{
-		Name:        "go-discovery/teeproxy/pkgGoDev-latency",
-		Measure:     teeproxyPkgGoDevLatency,
-		Aggregation: ochttp.DefaultLatencyDistribution,
-		Description: "Teeproxy latency to pkg.go.dev, by response status code",
-		TagKeys:     []tag.Key{keyTeeproxyStatus, keyTeeproxyHost},
-	}
-	// TeeproxyGddoRequestCount counts teeproxy requests from godoc.org.
-	TeeproxyGddoRequestCount = &view.View{
-		Name:        "go-discovery/teeproxy/gddo-count",
-		Measure:     teeproxyGddoLatency,
-		Aggregation: view.Count(),
-		Description: "Count of teeproxy requests from godoc.org",
-		TagKeys:     []tag.Key{keyTeeproxyStatus, keyTeeproxyHost},
-	}
-	// TeeproxyPkgGoDevRequestCount counts teeproxy requests to pkg.go.dev.
-	TeeproxyPkgGoDevRequestCount = &view.View{
-		Name:        "go-discovery/teeproxy/pkgGoDev-count",
-		Measure:     teeproxyPkgGoDevLatency,
-		Aggregation: view.Count(),
-		Description: "Count of teeproxy requests to pkg.go.dev",
-		TagKeys:     []tag.Key{keyTeeproxyStatus, keyTeeproxyHost},
-	}
-	// TeeproxyPkgGoDevBrokenPathCount counts teeproxy requests to pkg.go.dev
-	// that return 4xx or 5xx but return 2xx or 3xx on godoc.org.
-	TeeproxyPkgGoDevBrokenPathCount = &view.View{
-		Name:        "go-discovery/teeproxy/pkgGoDev-brokenPath",
-		Measure:     teeproxyPkgGoDevBrokenPaths,
-		Aggregation: view.Count(),
-		Description: "Count of broken paths in pkg.go.dev",
-		TagKeys:     []tag.Key{keyTeeproxyStatus, keyTeeproxyHost, keyTeeproxyPath},
-	}
-)
-
-// NewServer returns a new Server struct with preconfigured settings.
-//
-// The server is rate limited and allows events up to a rate of "Rate" and
-// a burst of "Burst".
-//
-// The server also implements the circuit breaker pattern and maintains a
-// breaker for each host. Each breaker can be in one of three states: green,
-// yellow, or red.
-//
-// In the green state, the breaker remains green until it encounters a time
-// window of length "GreenInterval" where there are more than of "FailsToRed"
-// failures and a failureRatio of more than "FailureThreshold", in which case
-// the state becomes red.
-//
-// In the red state, the breaker halts all requests and waits for a timeout
-// period before shifting to the yellow state.
-//
-// In the yellow state, the breaker allows the first "SuccsToGreen" requests.
-// If any of these fail, the state reverts to red.
-// Otherwise, the state becomes green again.
-//
-// The timeout period is initially set to "MinTimeout" when the breaker shifts
-// from green to yellow. By default, the timeout period is doubled each time
-// the breaker fails to shift from the yellow state to the green state and is
-// capped at "MaxTimeout".
-func NewServer(config Config) (_ *Server, err error) {
-	defer derrors.Wrap(&err, "NewServer")
-	var breakers = make(map[string]*breaker.Breaker)
-	for _, host := range config.Hosts {
-		if host == "" {
-			return nil, errors.New("host cannot be empty")
-		}
-		b, err := breaker.New(config.BreakerConfig)
-		if err != nil {
-			return nil, err
-		}
-		breakers[host] = b
-	}
-	var client = http.DefaultClient
-	if config.Client != nil {
-		client = config.Client
-	}
-
-	authKey := config.AuthKey
-	if authKey == "" {
-		authKey = "auth-key-for-testing"
-	}
-	return &Server{
-		hosts:     config.Hosts,
-		client:    client,
-		limiter:   rate.NewLimiter(rate.Limit(config.Rate), config.Burst),
-		breakers:  breakers,
-		authKey:   authKey,
-		authValue: config.AuthValue,
-	}, nil
-}
-
-// ServeHTTP receives requests from godoc.org and forwards them to the
-// specified hosts.
-// These requests are validated and rate limited before being forwarded. Too
-// many error responses returned by pkg.go.dev will cause the server to back
-// off temporarily before trying to forward requests to the hosts again.
-// ServeHTTP will always reply with StatusOK as long as the request is a valid
-// godoc.org request, even if the request could not be processed by the hosts.
-// Instead, problems with processing the request by the hosts will logged.
-func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	// Ignore internal App Engine requests.
-	if strings.HasPrefix(r.URL.Path, "/_ah/") {
-		// Don't log requests.
-		return
-	}
-	results, status, err := s.doRequest(r)
-	if err != nil {
-		log.Infof(r.Context(), "teeproxy.Server.ServeHTTP: %v", err)
-		http.Error(w, http.StatusText(status), status)
-		return
-	}
-	log.Info(r.Context(), results)
-}
-
-func (s *Server) doRequest(r *http.Request) (results map[string]*RequestEvent, status int, err error) {
-	defer derrors.Wrap(&err, "doRequest(%q): referer=%q", r.URL.Path, r.Referer())
-	ctx := r.Context()
-	if status, err = validateTeeProxyRequest(r); err != nil {
-		return results, status, err
-	}
-	gddoEvent, err := getGddoEvent(r)
-	if err != nil {
-		return results, http.StatusBadRequest, err
-	}
-
-	results = map[string]*RequestEvent{
-		"godoc.org": gddoEvent,
-	}
-	if gddoEvent.IsRobot {
-		// Don't tee robot requests since these will use up pkg.go.dev cache
-		// space.
-		return results, http.StatusOK, nil
-	}
-	if _, ok := expected404s[gddoEvent.Path]; ok {
-		// Don't tee these requests, since we know they will 404.
-		return results, http.StatusOK, nil
-	}
-	for _, s := range []string{
-		"?import-graph",
-		"?status.png",
-		"?status.svg",
-		"api.godoc.org",
-	} {
-		if strings.Contains(gddoEvent.URL, s) {
-			// Don't tee these requests, since we know they will 404.
-			return results, http.StatusOK, nil
-		}
-	}
-	if len(s.hosts) > 0 {
-		rateLimited := !s.limiter.Allow()
-		for _, host := range s.hosts {
-			event := &RequestEvent{
-				Host: host,
-			}
-
-			if rateLimited {
-				event.Status = http.StatusTooManyRequests
-				event.Error = errors.New("rate limit exceeded")
-			} else {
-				event = s.doRequestOnHost(ctx, gddoEvent, host)
-			}
-
-			if event.Error != nil {
-				log.Errorf(r.Context(), "teeproxy.Server.doRequest(%q): %s", host, event.Error)
-			}
-			results[strings.TrimPrefix(host, "https://")] = event
-			recordTeeProxyMetric(r.Context(), host, gddoEvent.Path, gddoEvent.Status, event.Status, gddoEvent.Latency, event.Latency)
-		}
-	}
-	return results, http.StatusOK, nil
-}
-
-func (s *Server) doRequestOnHost(ctx context.Context, gddoEvent *RequestEvent, host string) *RequestEvent {
-	redirectPath := pkgGoDevPath(gddoEvent.Path)
-	event := &RequestEvent{
-		Host: host,
-		Path: redirectPath,
-	}
-
-	breaker := s.breakers[host]
-	if breaker == nil {
-		// This case should never be reached.
-		event.Status = http.StatusInternalServerError
-		event.Error = errors.New("breaker is nil")
-		return event
-	}
-
-	if !breaker.Allow() {
-		event.Status = statusRedBreaker
-		event.Error = errors.New("breaker is red")
-		return event
-	}
-
-	event = s.makePkgGoDevRequest(ctx, host, pkgGoDevPath(gddoEvent.Path))
-	if event.Error != nil {
-		return event
-	}
-	success := event.Status < http.StatusInternalServerError
-	breaker.Record(success)
-	return event
-}
-
-// validateTeeProxyRequest validates that a request to the teeproxy is allowed.
-// It will return the error code and error if a request is invalid. Otherwise,
-// it will return http.StatusOK.
-func validateTeeProxyRequest(r *http.Request) (code int, err error) {
-	defer derrors.Wrap(&err, "validateTeeProxyRequest(r)")
-	if r.Method != "POST" {
-		return http.StatusMethodNotAllowed, fmt.Errorf("%s: %q", http.StatusText(http.StatusMethodNotAllowed), r.Method)
-	}
-	ct := r.Header.Get("Content-Type")
-	if ct != "application/json; charset=utf-8" {
-		return http.StatusUnsupportedMediaType, fmt.Errorf("Content-Type %q is not supported", ct)
-	}
-	return http.StatusOK, nil
-}
-
-// pkgGoDevPath returns the corresponding path on pkg.go.dev for the given
-// godoc.org path.
-func pkgGoDevPath(gddoPath string) string {
-	redirectPath, ok := gddoToPkgGoDevRequest[gddoPath]
-	if ok {
-		return redirectPath
-	}
-	return gddoPath
-}
-
-// getGddoEvent constructs a url.URL and RequestEvent from the request.
-func getGddoEvent(r *http.Request) (gddoEvent *RequestEvent, err error) {
-	defer func() {
-		derrors.Wrap(&err, "getGddoEvent(r)")
-		if gddoEvent != nil && err != nil {
-			log.Info(r.Context(), map[string]interface{}{
-				"godoc.org": gddoEvent,
-				"tee-error": err.Error(),
-			})
-		}
-	}()
-	body, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		return nil, err
-	}
-	gddoEvent = &RequestEvent{}
-	if err := json.Unmarshal(body, gddoEvent); err != nil {
-		return nil, err
-	}
-	return gddoEvent, nil
-}
-
-// makePkgGoDevRequest makes a request to the redirectHost and redirectPath,
-// and returns a requestEvent based on the output.
-func (s *Server) makePkgGoDevRequest(ctx context.Context, redirectHost, redirectPath string) *RequestEvent {
-	redirectURL := redirectHost + redirectPath
-	event := &RequestEvent{
-		Host: redirectHost,
-		Path: redirectPath,
-		URL:  redirectURL,
-	}
-	defer derrors.Wrap(&event.Error, "makePkgGoDevRequest(%q, %q)", redirectHost, redirectPath)
-
-	req, err := http.NewRequest("GET", redirectURL, nil)
-	if err != nil {
-		event.Status = http.StatusInternalServerError
-		event.Error = err
-		return event
-	}
-	start := time.Now()
-	req.Header.Set(s.authKey, s.authValue)
-	resp, err := ctxhttp.Do(ctx, s.client, req)
-	if err != nil {
-		// Use StatusBadGateway to indicate the upstream error.
-		event.Status = http.StatusBadGateway
-		event.Error = err
-		return event
-	}
-
-	event.Status = resp.StatusCode
-	event.Latency = time.Since(start)
-	return event
-}
-
-// recordTeeProxyMetric records the latencies and counts of requests from
-// godoc.org and to pkg.go.dev, tagged with the response status code, as well
-// as any path that errors on pkg.go.dev but not on godoc.org.
-func recordTeeProxyMetric(ctx context.Context, host, path string, gddoStatus, pkgGoDevStatus int, gddoLatency, pkgGoDevLatency time.Duration) {
-	gddoL := gddoLatency.Seconds() * 1000
-	pkgGoDevL := pkgGoDevLatency.Seconds() * 1000
-
-	// Record latency.
-	stats.RecordWithTags(ctx, []tag.Mutator{
-		tag.Upsert(keyTeeproxyStatus, strconv.Itoa(pkgGoDevStatus)),
-		tag.Upsert(keyTeeproxyHost, host),
-	},
-		teeproxyGddoLatency.M(gddoL),
-		teeproxyPkgGoDevLatency.M(pkgGoDevL),
-	)
-
-	// Record path that returns 4xx or 5xx on pkg.go.dev but returns 2xx or 3xx
-	// on godoc.org, excluding rate limiter and circuit breaker errors.
-	if pkgGoDevStatus >= 400 && gddoStatus < 400 &&
-		pkgGoDevStatus != http.StatusTooManyRequests && pkgGoDevStatus != statusRedBreaker {
-		stats.RecordWithTags(ctx, []tag.Mutator{
-			tag.Upsert(keyTeeproxyStatus, strconv.Itoa(pkgGoDevStatus)),
-			tag.Upsert(keyTeeproxyHost, host),
-			tag.Upsert(keyTeeproxyPath, path),
-		},
-			teeproxyPkgGoDevBrokenPaths.M(1),
-		)
-	}
-}
diff --git a/internal/teeproxy/teeproxy_test.go b/internal/teeproxy/teeproxy_test.go
deleted file mode 100644
index 2ca0616..0000000
--- a/internal/teeproxy/teeproxy_test.go
+++ /dev/null
@@ -1,428 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package teeproxy
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-cmp/cmp/cmpopts"
-	"golang.org/x/pkgsite/internal/breaker"
-)
-
-func TestPkgGoDevPath(t *testing.T) {
-	for _, test := range []struct {
-		path string
-		want string
-	}{
-		{
-			path: "/-/about",
-			want: "/about",
-		},
-		{
-			path: "/-/subrepo",
-			want: "/search?q=golang.org/x",
-		},
-		{
-			path: "/net/http",
-			want: "/net/http",
-		},
-		{
-			path: "/",
-			want: "/",
-		},
-		{
-			path: "",
-			want: "",
-		},
-	} {
-		if got := pkgGoDevPath(test.path); got != test.want {
-			t.Fatalf("pkgGoDevPath(%q) = %q; want = %q", test.path, got, test.want)
-		}
-	}
-}
-
-func TestPkgGoDevRequest(t *testing.T) {
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-	}))
-	defer ts.Close()
-
-	ctx := context.Background()
-	s := newTestServer(Config{})
-
-	got := s.makePkgGoDevRequest(ctx, ts.URL, "")
-	if got.Error != nil {
-		t.Fatal(got.Error)
-	}
-
-	want := &RequestEvent{
-		Host:   ts.URL,
-		URL:    ts.URL,
-		Status: http.StatusOK,
-	}
-	if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(RequestEvent{}, "Latency")); diff != "" {
-		t.Fatalf("mismatch (-want +got):\n%s", diff)
-	}
-}
-
-func TestGetGddoEvent(t *testing.T) {
-	for _, test := range []struct {
-		gddoEvent *RequestEvent
-	}{
-		{
-
-			&RequestEvent{
-				Host:    "godoc.org",
-				URL:     "https://godoc.org/net/http",
-				Latency: 100,
-				Status:  200,
-			},
-		},
-	} {
-		requestBody, err := json.Marshal(test.gddoEvent)
-		if err != nil {
-			t.Fatal(err)
-		}
-		r := httptest.NewRequest("POST", "/", bytes.NewBuffer(requestBody))
-		gotEvent, err := getGddoEvent(r)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if diff := cmp.Diff(test.gddoEvent, gotEvent); diff != "" {
-			t.Fatalf("mismatch (-want +got):\n%s", diff)
-		}
-	}
-}
-
-func TestServerHandler(t *testing.T) {
-	for _, test := range []struct {
-		name         string
-		serverConfig Config
-		handler      http.Handler
-		steps        []interface{}
-	}{
-		{
-			name:         "rate limiter permits requests below cap",
-			serverConfig: Config{Rate: 20, Burst: 20},
-			handler:      alwaysHandler{http.StatusOK},
-			steps: []interface{}{
-				request{15, http.StatusOK},
-			},
-		},
-		{
-			name:         "rate limiter permits requests up to cap",
-			serverConfig: Config{Rate: 20, Burst: 20},
-			handler:      alwaysHandler{http.StatusOK},
-			steps: []interface{}{
-				request{20, http.StatusOK},
-			},
-		},
-		{
-			name:         "rate limiter drops requests over cap",
-			serverConfig: Config{Rate: 5, Burst: 5},
-			handler:      alwaysHandler{http.StatusOK},
-			steps: []interface{}{
-				request{5, http.StatusOK},
-				request{6, http.StatusTooManyRequests},
-			},
-		},
-		{
-			name:         "rate limiter permits requests after replenishing",
-			serverConfig: Config{Rate: 2, Burst: 2},
-			handler:      alwaysHandler{http.StatusOK},
-			steps: []interface{}{
-				request{2, http.StatusOK},
-				request{3, http.StatusTooManyRequests},
-				wait{1 * time.Second},
-				request{2, http.StatusOK},
-				request{3, http.StatusTooManyRequests},
-			},
-		},
-		{
-			name:         "green breaker passes requests",
-			serverConfig: Config{Rate: 100, Burst: 100},
-			handler:      alwaysHandler{http.StatusOK},
-			steps: []interface{}{
-				checkState{breaker.Green},
-				request{25, http.StatusOK},
-				checkState{breaker.Green},
-				request{25, http.StatusOK},
-				checkState{breaker.Green},
-			},
-		},
-		{
-			name: "green breaker resets failure count after interval",
-			serverConfig: Config{BreakerConfig: breaker.Config{
-				FailsToRed:    5,
-				GreenInterval: 100 * time.Millisecond,
-			}},
-			handler: alwaysHandler{http.StatusServiceUnavailable},
-			steps: []interface{}{
-				checkState{breaker.Green},
-				request{5, http.StatusServiceUnavailable},
-				checkState{breaker.Green},
-				wait{150 * time.Millisecond},
-				checkState{breaker.Green},
-				request{5, http.StatusServiceUnavailable},
-				checkState{breaker.Green},
-				request{1, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-			},
-		},
-		{
-			name: "breaker changes to red state and blocks requests",
-			serverConfig: Config{BreakerConfig: breaker.Config{
-				FailsToRed: 5,
-				MinTimeout: 1 * time.Second,
-			}},
-			handler: alwaysHandler{http.StatusServiceUnavailable},
-			steps: []interface{}{
-				checkState{breaker.Green},
-				request{6, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-				request{20, statusRedBreaker},
-				checkState{breaker.Red},
-				wait{100 * time.Millisecond},
-				request{20, statusRedBreaker},
-				checkState{breaker.Red},
-			},
-		},
-		{
-			name: "breaker changes to yellow state",
-			serverConfig: Config{BreakerConfig: breaker.Config{
-				FailsToRed: 5,
-				MinTimeout: 100 * time.Millisecond,
-			}},
-			handler: &handler{6, http.StatusServiceUnavailable, alwaysHandler{http.StatusOK}},
-			steps: []interface{}{
-				request{6, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-				request{20, statusRedBreaker},
-				checkState{breaker.Red},
-				wait{150 * time.Millisecond},
-				checkState{breaker.Yellow},
-				request{9, http.StatusOK},
-				checkState{breaker.Yellow},
-			},
-		},
-		{
-			name: "breaker changes to green state again",
-			serverConfig: Config{BreakerConfig: breaker.Config{
-				FailsToRed:   5,
-				MinTimeout:   100 * time.Millisecond,
-				SuccsToGreen: 10,
-			}},
-			handler: &handler{6, http.StatusServiceUnavailable, alwaysHandler{http.StatusOK}},
-			steps: []interface{}{
-				request{6, http.StatusServiceUnavailable},
-				request{20, statusRedBreaker},
-				wait{150 * time.Millisecond},
-				request{9, http.StatusOK},
-				checkState{breaker.Yellow},
-				request{1, http.StatusOK},
-				checkState{breaker.Green},
-				request{5, http.StatusOK},
-			},
-		},
-		{
-			name: "breaker reverts to red state and doubles timeout period on repeated failures",
-			serverConfig: Config{BreakerConfig: breaker.Config{
-				FailsToRed: 5,
-				MinTimeout: 100 * time.Millisecond,
-				MaxTimeout: 400 * time.Millisecond,
-			}},
-			handler: alwaysHandler{http.StatusServiceUnavailable},
-			steps: []interface{}{
-				request{6, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-				wait{100 * time.Millisecond},
-				checkState{breaker.Yellow},
-				request{1, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-				wait{100 * time.Millisecond},
-				checkState{breaker.Red},
-				wait{100 * time.Millisecond},
-				checkState{breaker.Yellow},
-				request{1, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-			},
-		},
-		{
-			name: "breaker timeout period does not exceed maxTimeout",
-			serverConfig: Config{BreakerConfig: breaker.Config{
-				FailsToRed: 5,
-				MinTimeout: 100 * time.Millisecond,
-				MaxTimeout: 100 * time.Millisecond,
-			}},
-			handler: alwaysHandler{http.StatusServiceUnavailable},
-			steps: []interface{}{
-				request{6, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-				wait{100 * time.Millisecond},
-				checkState{breaker.Yellow},
-				request{1, http.StatusServiceUnavailable},
-				checkState{breaker.Red},
-				wait{100 * time.Millisecond},
-				checkState{breaker.Yellow},
-			},
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			mockPkgGoDevServer := httptest.NewServer(test.handler)
-			defer mockPkgGoDevServer.Close()
-			test.serverConfig.Hosts = []string{mockPkgGoDevServer.URL}
-			server := newTestServer(test.serverConfig)
-			executeSteps(t, server, mockPkgGoDevServer.URL, test.steps)
-		})
-	}
-}
-
-func executeSteps(t *testing.T, server *Server, pkgGoDevURL string, steps []interface{}) {
-	for s, step := range steps {
-		switch step := step.(type) {
-		case request:
-			for i := 0; i < step.repeat; i++ {
-				event := makePostRequest(t, server, pkgGoDevURL)
-				if event.Status != step.expectedStatus {
-					t.Errorf("step %d request %d: got status %d, want %d", s, i, event.Status, step.expectedStatus)
-				}
-			}
-		case wait:
-			time.Sleep(step.wait)
-		case checkState:
-			if server.breakers[pkgGoDevURL].State() != step.expectedState {
-				t.Errorf("step %d: got %s, want %s", s, server.breakers[pkgGoDevURL].State().String(), step.expectedState.String())
-			}
-		default:
-			panic("invalid step type")
-		}
-	}
-}
-
-// TestHandler tests that the handler struct returns
-// the correct status codes.
-func TestHandler(t *testing.T) {
-	h := &handler{5, 500, alwaysHandler{200}}
-	s := httptest.NewServer(h)
-	defer s.Close()
-
-	for i := 0; i < 5; i++ {
-		resp, err := http.PostForm(s.URL, nil)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if resp.StatusCode != 500 {
-			t.Errorf("request %d: got status %d, want %d", i, resp.StatusCode, 500)
-		}
-	}
-
-	for i := 0; i < 20; i++ {
-		resp, err := http.PostForm(s.URL, nil)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if resp.StatusCode != 200 {
-			t.Errorf("request %d: got status %d, want %d", i, resp.StatusCode, 200)
-		}
-	}
-}
-
-func makePostRequest(t *testing.T, server *Server, pkgGoDevURL string) *RequestEvent {
-	gddoEvent := &RequestEvent{
-		Host: "godoc.org",
-		URL:  "https://godoc.org/net/http",
-	}
-	requestBody, err := json.Marshal(gddoEvent)
-	if err != nil {
-		t.Fatal(err)
-	}
-	r := httptest.NewRequest("POST", "/", bytes.NewBuffer(requestBody))
-	r.Header.Set("Content-Type", "application/json; charset=utf-8")
-	results, _, err := server.doRequest(r)
-	if err != nil || results == nil {
-		t.Fatalf("doRequest = %v: %v", results, err)
-	}
-	event := results[pkgGoDevURL]
-	if event == nil {
-		t.Fatalf("results[%q] = %v", pkgGoDevURL, event)
-	}
-	return event
-}
-
-// newTestServer is like NewServer, but with default values for easier testing.
-func newTestServer(config Config) *Server {
-	// Set default values.
-	if config.Rate <= 0 {
-		config.Rate = 50
-	}
-	if config.Burst <= 0 {
-		config.Burst = 50
-	}
-	if config.BreakerConfig.FailsToRed <= 0 {
-		config.BreakerConfig.FailsToRed = 10
-	}
-	if config.BreakerConfig.FailureThreshold <= 0 {
-		config.BreakerConfig.FailureThreshold = 0.5
-	}
-	if config.BreakerConfig.GreenInterval <= 0 {
-		config.BreakerConfig.GreenInterval = 200 * time.Millisecond
-	}
-	if config.BreakerConfig.MinTimeout <= 0 {
-		config.BreakerConfig.MinTimeout = 100 * time.Millisecond
-	}
-	if config.BreakerConfig.MaxTimeout <= 0 {
-		config.BreakerConfig.MaxTimeout = 400 * time.Millisecond
-	}
-	if config.BreakerConfig.SuccsToGreen <= 0 {
-		config.BreakerConfig.SuccsToGreen = 20
-	}
-
-	server, _ := NewServer(config)
-	return server
-}
-
-// handler returns statusCode for the first n requests
-// and uses innerHandler to serve the remaining requests.
-type handler struct {
-	n            int
-	statusCode   int
-	innerHandler http.Handler
-}
-
-func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	if h.n <= 0 {
-		h.innerHandler.ServeHTTP(w, r)
-		return
-	}
-	h.n--
-	w.WriteHeader(h.statusCode)
-}
-
-type alwaysHandler struct {
-	statusCode int
-}
-
-func (h alwaysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	w.WriteHeader(h.statusCode)
-}
-
-type request struct {
-	repeat         int
-	expectedStatus int
-}
-
-type wait struct {
-	wait time.Duration
-}
-
-type checkState struct {
-	expectedState breaker.State
-}