github.com/bazelbuild/rules_webtesting@v0.2.0/go/wtl/proxy/proxy.go (about)

     1  // Copyright 2016 Google Inc.
     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 proxy provides a WebDriver protocol that forwards requests
    16  // to a WebDriver server provided by an environment.Env.
    17  package proxy
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"log"
    23  	"net"
    24  	"net/http"
    25  	"strconv"
    26  	"time"
    27  
    28  	"github.com/bazelbuild/rules_webtesting/go/errors"
    29  	"github.com/bazelbuild/rules_webtesting/go/healthreporter"
    30  	"github.com/bazelbuild/rules_webtesting/go/httphelper"
    31  	"github.com/bazelbuild/rules_webtesting/go/metadata"
    32  	"github.com/bazelbuild/rules_webtesting/go/portpicker"
    33  	"github.com/bazelbuild/rules_webtesting/go/wtl/diagnostics"
    34  	"github.com/bazelbuild/rules_webtesting/go/wtl/environment"
    35  )
    36  
    37  const (
    38  	compName = "WebDriver proxy"
    39  	timeout  = 60 * time.Second
    40  )
    41  
    42  var handlerProviders = map[string]HTTPHandlerProvider{}
    43  
    44  // A HTTPHandlerProvider is a function that provides a HTTPHandler.
    45  type HTTPHandlerProvider func(*Proxy) (HTTPHandler, error)
    46  
    47  // A HTTPHandler implements http.Handler plus a Shutdown method.
    48  type HTTPHandler interface {
    49  	http.Handler
    50  	healthreporter.HealthReporter
    51  
    52  	// Shutdown is called when the proxy is in the process of shutting down.
    53  	Shutdown(context.Context) error
    54  }
    55  
    56  // AddHTTPHandlerProvider adds a HTTPHandlerProvider used to create handlers for
    57  // specified routes in any Proxy structs creates by New.
    58  func AddHTTPHandlerProvider(route string, provider HTTPHandlerProvider) {
    59  	handlerProviders[route] = provider
    60  }
    61  
    62  type certs struct {
    63  	certFile string
    64  	keyFile  string
    65  }
    66  
    67  // Proxy starts a WebDriver protocol proxy.
    68  type Proxy struct {
    69  	Diagnostics  diagnostics.Diagnostics
    70  	Env          environment.Env
    71  	Metadata     *metadata.Metadata
    72  	HTTPAddress  string
    73  	HTTPSAddress string
    74  	handlers     []HTTPHandler
    75  	httpSrv      *http.Server
    76  	httpsSrv     *http.Server
    77  	httpPort     int
    78  	httpsPort    int
    79  	certs        *certs
    80  }
    81  
    82  // New creates a new Proxy object.
    83  func New(env environment.Env, m *metadata.Metadata, d diagnostics.Diagnostics) (*Proxy, error) {
    84  	fqdn, err := httphelper.FQDN()
    85  	if err != nil {
    86  		return nil, errors.New(compName, err)
    87  	}
    88  
    89  	httpPort, err := portpicker.PickUnusedPort()
    90  	if err != nil {
    91  		return nil, errors.New(compName, err)
    92  	}
    93  
    94  	httpAddress := net.JoinHostPort(fqdn, strconv.Itoa(httpPort))
    95  
    96  	httpsPort := -1
    97  	httpsAddress := ""
    98  	certs := newCerts(m)
    99  	if certs != nil {
   100  		httpsPort, err = portpicker.PickUnusedPort()
   101  		if err != nil {
   102  			return nil, errors.New(compName, err)
   103  		}
   104  		httpsAddress = net.JoinHostPort(fqdn, strconv.Itoa(httpsPort))
   105  	}
   106  
   107  	p := &Proxy{
   108  		Diagnostics:  d,
   109  		Env:          env,
   110  		Metadata:     m,
   111  		HTTPAddress:  httpAddress,
   112  		HTTPSAddress: httpsAddress,
   113  		httpPort:     httpPort,
   114  		httpsPort:    httpsPort,
   115  		certs:        certs,
   116  	}
   117  
   118  	mux := http.NewServeMux()
   119  
   120  	for route, provider := range handlerProviders {
   121  		h, err := provider(p)
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  		p.handlers = append(p.handlers, h)
   126  		mux.Handle(route, h)
   127  	}
   128  
   129  	p.httpSrv = &http.Server{
   130  		Addr:    ":" + strconv.Itoa(p.httpPort),
   131  		Handler: mux,
   132  	}
   133  
   134  	if p.certs != nil {
   135  		p.httpsSrv = &http.Server{
   136  			Addr:    ":" + strconv.Itoa(p.httpsPort),
   137  			Handler: mux,
   138  		}
   139  	}
   140  
   141  	return p, nil
   142  }
   143  
   144  func newCerts(m *metadata.Metadata) *certs {
   145  	certFile, err := m.GetFilePath("PROXY_TLS_CERT")
   146  	if err != nil {
   147  		return nil
   148  	}
   149  	keyFile, err := m.GetFilePath("PROXY_TLS_KEY")
   150  	if err != nil {
   151  		return nil
   152  	}
   153  
   154  	return &certs{certFile, keyFile}
   155  }
   156  
   157  // Name returns the name used in error messages.
   158  func (*Proxy) Name() string {
   159  	return compName
   160  }
   161  
   162  // Start configures the proxy with handlers, starts its listen loop, and waits for it to respond to a health check.
   163  func (p *Proxy) Start(ctx context.Context) error {
   164  	start := time.Now()
   165  	defer func() {
   166  		if err := p.Diagnostics.Timing(p.Name(), "start", "", start, time.Now()); err != nil {
   167  			log.Print(err)
   168  		}
   169  	}()
   170  
   171  	go func() {
   172  		log.Printf("launching HTTP server at: %v", p.HTTPAddress)
   173  		err := errors.New(p.Name(), p.httpSrv.ListenAndServe())
   174  		p.Diagnostics.Severe(err)
   175  	}()
   176  
   177  	if p.httpsSrv != nil {
   178  		go func() {
   179  			log.Printf("launching HTTPS server at: %v", p.HTTPSAddress)
   180  			err := errors.New(p.Name(), p.httpsSrv.ListenAndServeTLS(p.certs.certFile, p.certs.keyFile))
   181  			p.Diagnostics.Severe(err)
   182  		}()
   183  	}
   184  
   185  	healthyCtx, cancel := context.WithTimeout(ctx, timeout)
   186  	defer cancel()
   187  	return healthreporter.WaitForHealthy(healthyCtx, p)
   188  }
   189  
   190  // Healthy returns nil if the proxy is able to receive requests.
   191  func (p *Proxy) Healthy(ctx context.Context) error {
   192  	for _, h := range p.handlers {
   193  		if err := h.Healthy(ctx); err != nil {
   194  			return err
   195  		}
   196  	}
   197  	if err := p.httpHealthy(ctx); err != nil {
   198  		return err
   199  	}
   200  	if err := p.httpsHealthy(ctx); err != nil {
   201  		return err
   202  	}
   203  	return nil
   204  }
   205  
   206  func (p *Proxy) httpHealthy(ctx context.Context) error {
   207  	url := fmt.Sprintf("http://%s/healthz", p.HTTPAddress)
   208  	resp, err := httphelper.Get(ctx, url)
   209  	if err != nil {
   210  		return errors.New(p.Name(), fmt.Errorf("error getting %s: %v", url, err))
   211  	}
   212  	resp.Body.Close()
   213  	if resp.StatusCode != http.StatusOK {
   214  		return errors.New(p.Name(), fmt.Errorf("request to %s returned status %v", url, resp.StatusCode))
   215  	}
   216  	return nil
   217  }
   218  
   219  func (p *Proxy) httpsHealthy(ctx context.Context) error {
   220  	if p.HTTPSAddress == "" {
   221  		return nil
   222  	}
   223  	url := fmt.Sprintf("https://%s/healthz", p.HTTPSAddress)
   224  	resp, err := httphelper.Get(ctx, url)
   225  	if err != nil {
   226  		return errors.New(p.Name(), fmt.Errorf("error getting %s: %v", url, err))
   227  	}
   228  	resp.Body.Close()
   229  	if resp.StatusCode != http.StatusOK {
   230  		return errors.New(p.Name(), fmt.Errorf("request to %s returned status %v", url, resp.StatusCode))
   231  	}
   232  	return nil
   233  }
   234  
   235  // Shutdown calls Shutdown on all handlers, then shuts the HTTP server down.
   236  func (p *Proxy) Shutdown(ctx context.Context) error {
   237  	for _, handler := range p.handlers {
   238  		if err := handler.Shutdown(ctx); err != nil {
   239  			p.Diagnostics.Warning(err)
   240  		}
   241  	}
   242  	// TODO(DrMarcII) figure out why Shutdown doesn't work.
   243  	return nil
   244  }