dex/vendor/github.com/testcontainers/testcontainers-go/wait/http.go
2020-01-31 10:32:00 +01:00

147 lines
3.4 KiB
Go

package wait
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"time"
"github.com/docker/go-connections/nat"
)
// Implement interface
var _ Strategy = (*HTTPStrategy)(nil)
type HTTPStrategy struct {
// all Strategies should have a startupTimeout to avoid waiting infinitely
startupTimeout time.Duration
// additional properties
Port nat.Port
Path string
StatusCodeMatcher func(status int) bool
UseTLS bool
AllowInsecure bool
}
// NewHTTPStrategy constructs a HTTP strategy waiting on port 80 and status code 200
func NewHTTPStrategy(path string) *HTTPStrategy {
return &HTTPStrategy{
startupTimeout: defaultStartupTimeout(),
Port: "80/tcp",
Path: path,
StatusCodeMatcher: defaultStatusCodeMatcher,
UseTLS: false,
}
}
func defaultStatusCodeMatcher(status int) bool {
return status == http.StatusOK
}
// fluent builders for each property
// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
// this is true for all properties, even the "shared" ones like startupTimeout
func (ws *HTTPStrategy) WithStartupTimeout(startupTimeout time.Duration) *HTTPStrategy {
ws.startupTimeout = startupTimeout
return ws
}
func (ws *HTTPStrategy) WithPort(port nat.Port) *HTTPStrategy {
ws.Port = port
return ws
}
func (ws *HTTPStrategy) WithStatusCodeMatcher(statusCodeMatcher func(status int) bool) *HTTPStrategy {
ws.StatusCodeMatcher = statusCodeMatcher
return ws
}
func (ws *HTTPStrategy) WithTLS(useTLS bool) *HTTPStrategy {
ws.UseTLS = useTLS
return ws
}
func (ws *HTTPStrategy) WithAllowInsecure(allowInsecure bool) *HTTPStrategy {
ws.AllowInsecure = allowInsecure
return ws
}
// ForHTTP is a convenience method similar to Wait.java
// https://github.com/testcontainers/testcontainers-java/blob/1d85a3834bd937f80aad3a4cec249c027f31aeb4/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java
func ForHTTP(path string) *HTTPStrategy {
return NewHTTPStrategy(path)
}
// WaitUntilReady implements Strategy.WaitUntilReady
func (ws *HTTPStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
// limit context to startupTimeout
ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout)
defer cancelContext()
ipAddress, err := target.Host(ctx)
if err != nil {
return
}
port, err := target.MappedPort(ctx, ws.Port)
if err != nil {
return
}
if port.Proto() != "tcp" {
return errors.New("Cannot use HTTP client on non-TCP ports")
}
portNumber := port.Int()
portString := strconv.Itoa(portNumber)
address := net.JoinHostPort(ipAddress, portString)
var proto string
if ws.UseTLS {
proto = "https"
} else {
proto = "http"
}
url := fmt.Sprintf("%s://%s%s", proto, address, ws.Path)
tripper := http.DefaultTransport
if ws.AllowInsecure {
tripper.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
client := http.Client{Timeout: ws.startupTimeout, Transport: tripper}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
req = req.WithContext(ctx)
Retry:
for {
select {
case <-ctx.Done():
break Retry
default:
resp, err := client.Do(req)
if err != nil || !ws.StatusCodeMatcher(resp.StatusCode) {
time.Sleep(100 * time.Millisecond)
continue
}
break Retry
}
}
return nil
}