Copyright 2019 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 middleware

import (
	
	
	
	
	

	
	
	
	
	
	
)

const experimentQueryParamKey = "experiment"
A Reporter sends errors to the Error-Reporting service.
type Reporter interface {
	Report(errorreporting.Entry)
}
ExperimentGetter is the signature of a function that gets experiments.
An Experimenter contains information about active experiments from the experiment source.
type Experimenter struct {
	p *poller.Poller
}
NewExperimenter returns an Experimenter for use in the middleware. The experimenter regularly polls for updates to the snapshot in the background.
func ( context.Context,  time.Duration,  ExperimentGetter,  Reporter) ( *Experimenter,  error) {
	defer derrors.Wrap(&, "middleware.NewExperimenter")

If we can't load the initial state, then fail.
	if  != nil {
		return nil, 
	}
	 := &Experimenter{
		p: poller.New(
			,
			func( context.Context) (interface{}, error) {
				return ()
			},
Log and report // the error.
				log.Error(, )
				if  != nil {
					.Report(errorreporting.Entry{
						Error: fmt.Errorf("loading experiments: %v", ),
					})
				}
			}),
	}
	.p.Start(, )
	return , nil
}
Experiment returns a new Middleware that sets active experiments for each incoming request.
func ( *Experimenter) Middleware {
	return func( http.Handler) http.Handler {
		return http.HandlerFunc(func( http.ResponseWriter,  *http.Request) {
			 := .setExperimentsForRequest()
			.ServeHTTP(, )
		})
	}
}
Experiments returns the experiments currently in use.
Make a copy so the caller can't modify our state.
We don't need a lock here because e.p.current will be updated without modification.
	 := make([]*internal.Experiment, len())
Assume internal.Experiment has no pointers to mutable data.
		 := *
		[] = &
	}
	return 
}
setExperimentsForRequest sets the experiments for a given request. Experiments should be stable for a given IP address.
func ( *Experimenter) ( *http.Request) *http.Request {
	 := .p.Current().([]*internal.Experiment)
	var  []string
	for ,  := range  {
		if shouldSetExperiment(, ) {
			 = append(, .Name)
		}
	}
	 = append(, .URL.Query()[experimentQueryParamKey]...)
	return .WithContext(experiment.NewContext(.Context(), ...))
}
shouldSetExperiment reports whether a given request should be enrolled in the experiment, based on the ip. e.Name, and e.Rollout. Requests from empty ip addresses are never enrolled. All requests from the same IP will be enrolled in the same set of experiments.
func ( *http.Request,  *internal.Experiment) bool {
	if .Rollout == 0 {
		return false
	}
	if .Rollout >= 100 {
		return true
	}
	 := ipKey(.Header.Get("X-Forwarded-For"))
	if  == "" {
		return false
	}
	 := fnv.New32a()
	fmt.Fprintf(, "%s %s", , .Name)
	return uint(.Sum32())%100 < .Rollout