summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/reverseproxy/httptransport.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddyhttp/reverseproxy/httptransport.go')
-rw-r--r--modules/caddyhttp/reverseproxy/httptransport.go208
1 files changed, 208 insertions, 0 deletions
diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go
new file mode 100644
index 0000000..c135ac8
--- /dev/null
+++ b/modules/caddyhttp/reverseproxy/httptransport.go
@@ -0,0 +1,208 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package reverseproxy
+
+import (
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/base64"
+ "fmt"
+ "net"
+ "net/http"
+ "reflect"
+ "time"
+
+ "github.com/caddyserver/caddy/v2"
+)
+
+func init() {
+ caddy.RegisterModule(HTTPTransport{})
+}
+
+// HTTPTransport is essentially a configuration wrapper for http.Transport.
+// It defines a JSON structure useful when configuring the HTTP transport
+// for Caddy's reverse proxy.
+type HTTPTransport struct {
+ // TODO: It's possible that other transports (like fastcgi) might be
+ // able to borrow/use at least some of these config fields; if so,
+ // move them into a type called CommonTransport and embed it
+ TLS *TLSConfig `json:"tls,omitempty"`
+ KeepAlive *KeepAlive `json:"keep_alive,omitempty"`
+ Compression *bool `json:"compression,omitempty"`
+ MaxConnsPerHost int `json:"max_conns_per_host,omitempty"` // TODO: NOTE: we use our health check stuff to enforce max REQUESTS per host, but this is connections
+ DialTimeout caddy.Duration `json:"dial_timeout,omitempty"`
+ FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"`
+ ResponseHeaderTimeout caddy.Duration `json:"response_header_timeout,omitempty"`
+ ExpectContinueTimeout caddy.Duration `json:"expect_continue_timeout,omitempty"`
+ MaxResponseHeaderSize int64 `json:"max_response_header_size,omitempty"`
+ WriteBufferSize int `json:"write_buffer_size,omitempty"`
+ ReadBufferSize int `json:"read_buffer_size,omitempty"`
+
+ RoundTripper http.RoundTripper `json:"-"`
+}
+
+// CaddyModule returns the Caddy module information.
+func (HTTPTransport) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ Name: "http.handlers.reverse_proxy.transport.http",
+ New: func() caddy.Module { return new(HTTPTransport) },
+ }
+}
+
+// Provision sets up h.RoundTripper with a http.Transport
+// that is ready to use.
+func (h *HTTPTransport) Provision(_ caddy.Context) error {
+ dialer := &net.Dialer{
+ Timeout: time.Duration(h.DialTimeout),
+ FallbackDelay: time.Duration(h.FallbackDelay),
+ // TODO: Resolver
+ }
+
+ rt := &http.Transport{
+ DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
+ // the proper dialing information should be embedded into the request's context
+ if dialInfoVal := ctx.Value(DialInfoCtxKey); dialInfoVal != nil {
+ dialInfo := dialInfoVal.(DialInfo)
+ network = dialInfo.Network
+ address = dialInfo.Address
+ }
+ return dialer.DialContext(ctx, network, address)
+ },
+ MaxConnsPerHost: h.MaxConnsPerHost,
+ ResponseHeaderTimeout: time.Duration(h.ResponseHeaderTimeout),
+ ExpectContinueTimeout: time.Duration(h.ExpectContinueTimeout),
+ MaxResponseHeaderBytes: h.MaxResponseHeaderSize,
+ WriteBufferSize: h.WriteBufferSize,
+ ReadBufferSize: h.ReadBufferSize,
+ }
+
+ if h.TLS != nil {
+ rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout)
+
+ var err error
+ rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig()
+ if err != nil {
+ return fmt.Errorf("making TLS client config: %v", err)
+ }
+ }
+
+ if h.KeepAlive != nil {
+ dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval)
+ if enabled := h.KeepAlive.Enabled; enabled != nil {
+ rt.DisableKeepAlives = !*enabled
+ }
+ rt.MaxIdleConns = h.KeepAlive.MaxIdleConns
+ rt.MaxIdleConnsPerHost = h.KeepAlive.MaxIdleConnsPerHost
+ rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout)
+ }
+
+ if h.Compression != nil {
+ rt.DisableCompression = !*h.Compression
+ }
+
+ h.RoundTripper = rt
+
+ return nil
+}
+
+// RoundTrip implements http.RoundTripper with h.RoundTripper.
+func (h HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ return h.RoundTripper.RoundTrip(req)
+}
+
+// TLSConfig holds configuration related to the
+// TLS configuration for the transport/client.
+type TLSConfig struct {
+ RootCAPool []string `json:"root_ca_pool,omitempty"`
+ // TODO: Should the client cert+key config use caddytls.CertificateLoader modules?
+ ClientCertificateFile string `json:"client_certificate_file,omitempty"`
+ ClientCertificateKeyFile string `json:"client_certificate_key_file,omitempty"`
+ InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"`
+ HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"`
+}
+
+// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
+// If there is no custom TLS configuration, a nil config may be returned.
+func (t TLSConfig) MakeTLSClientConfig() (*tls.Config, error) {
+ cfg := new(tls.Config)
+
+ // client auth
+ if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile == "" {
+ return nil, fmt.Errorf("client_certificate_file specified without client_certificate_key_file")
+ }
+ if t.ClientCertificateFile == "" && t.ClientCertificateKeyFile != "" {
+ return nil, fmt.Errorf("client_certificate_key_file specified without client_certificate_file")
+ }
+ if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile != "" {
+ cert, err := tls.LoadX509KeyPair(t.ClientCertificateFile, t.ClientCertificateKeyFile)
+ if err != nil {
+ return nil, fmt.Errorf("loading client certificate key pair: %v", err)
+ }
+ cfg.Certificates = []tls.Certificate{cert}
+ }
+
+ // trusted root CAs
+ if len(t.RootCAPool) > 0 {
+ rootPool := x509.NewCertPool()
+ for _, encodedCACert := range t.RootCAPool {
+ caCert, err := decodeBase64DERCert(encodedCACert)
+ if err != nil {
+ return nil, fmt.Errorf("parsing CA certificate: %v", err)
+ }
+ rootPool.AddCert(caCert)
+ }
+ cfg.RootCAs = rootPool
+ }
+
+ // throw all security out the window
+ cfg.InsecureSkipVerify = t.InsecureSkipVerify
+
+ // only return a config if it's not empty
+ if reflect.DeepEqual(cfg, new(tls.Config)) {
+ return nil, nil
+ }
+
+ cfg.NextProtos = []string{"h2", "http/1.1"} // TODO: ensure that this actually enables HTTP/2
+
+ return cfg, nil
+}
+
+// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
+func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
+ // decode base64
+ derBytes, err := base64.StdEncoding.DecodeString(certStr)
+ if err != nil {
+ return nil, err
+ }
+
+ // parse the DER-encoded certificate
+ return x509.ParseCertificate(derBytes)
+}
+
+// KeepAlive holds configuration pertaining to HTTP Keep-Alive.
+type KeepAlive struct {
+ Enabled *bool `json:"enabled,omitempty"`
+ ProbeInterval caddy.Duration `json:"probe_interval,omitempty"`
+ MaxIdleConns int `json:"max_idle_conns,omitempty"`
+ MaxIdleConnsPerHost int `json:"max_idle_conns_per_host,omitempty"`
+ IdleConnTimeout caddy.Duration `json:"idle_timeout,omitempty"` // how long should connections be kept alive when idle
+}
+
+// Interface guards
+var (
+ _ caddy.Provisioner = (*HTTPTransport)(nil)
+ _ http.RoundTripper = (*HTTPTransport)(nil)
+)