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 }