"context"
"crypto/tls"
"fmt"
- "io"
"net"
"net/http"
+ "net/http/httputil"
+ "net/url"
"strconv"
- "strings"
"sync"
"gopkg.in/yaml.v3"
"go.fuhry.dev/runtime/mtls"
)
-type staticUpstreamAction struct {
+type proxyAction struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Identity string `yaml:"mtls_id"`
- client *http.Client
- clientOnce sync.Once
+ rProxy *httputil.ReverseProxy
+ rProxyErr error
+ rProxyOnce sync.Once
}
// httpClient returns an HTTP client for making requests to the backend.
-func (su *staticUpstreamAction) httpClient() (*http.Client, error) {
+func (su *proxyAction) reverseProxy() (*httputil.ReverseProxy, error) {
var err error
- su.clientOnce.Do(func() {
+ su.rProxyOnce.Do(func() {
transport := &http.Transport{}
var tlsConfig *tls.Config
myIdentity := mtls.DefaultIdentity()
tlsConfig, err = myIdentity.TlsConfig(context.Background())
if err != nil {
+ su.rProxyErr = err
return
}
verifier.AllowFrom(mtls.Service, su.Identity)
err = verifier.ConfigureClient(tlsConfig)
if err != nil {
+ su.rProxyErr = err
return
}
transport.TLSClientConfig = tlsConfig
}
- client := &http.Client{
- Transport: transport,
+ target := &url.URL{
+ Scheme: "http",
+ Host: net.JoinHostPort(su.Host, strconv.Itoa(su.Port)),
+ }
+ if su.Identity != "" {
+ target.Scheme = "https"
}
- su.client = client
+ su.rProxy = &httputil.ReverseProxy{
+ Transport: transport,
+ Rewrite: func(r *httputil.ProxyRequest) {
+ r.SetURL(target)
+ r.SetXForwarded()
+ },
+ }
})
- if err != nil {
- return nil, err
+ if su.rProxyErr != nil {
+ return nil, su.rProxyErr
}
- return su.client, nil
+ return su.rProxy, nil
}
// Handle implements RouteAction.
-func (su *staticUpstreamAction) Handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+func (su *proxyAction) Handle(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
logger := LoggerFromContext(r.Context())
upstreamReq := r.Clone(r.Context())
upstreamReq.URL.Scheme = "https"
}
upstreamReq.URL.Host = net.JoinHostPort(su.Host, strconv.Itoa(su.Port))
+ upstreamReq.Host = upstreamReq.URL.Host
upstreamReq.RequestURI = ""
// set proxy headers
}
// proxy the request to the backend
- client, err := su.httpClient()
+ rproxy, err := su.reverseProxy()
if err != nil {
http.Error(w,
fmt.Sprintf("error setting up connection to backend: %v", err),
http.StatusInternalServerError)
return
}
- response, err := client.Do(upstreamReq)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadGateway)
- return
- }
-
- for name, value := range response.Header {
- w.Header().Set(name, strings.Join(value, ", "))
- }
-
- if response.StatusCode == http.StatusSwitchingProtocols {
- hijacker, ok := w.(http.Hijacker)
- if !ok {
- http.Error(w, "websocket passthrough not supported", http.StatusMethodNotAllowed)
- return
- }
-
- upstreamWriter, ok := response.Body.(io.Writer)
- if !ok {
- http.Error(w, "body doesn't support io.Writer", http.StatusMethodNotAllowed)
- return
- }
-
- w.WriteHeader(response.StatusCode)
-
- conn, rw, err := hijacker.Hijack()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- wg := sync.WaitGroup{}
- wg.Add(2)
- pipe := func(w io.Writer, r io.Reader) {
- defer wg.Done()
- io.Copy(w, r)
- }
- go pipe(rw, response.Body)
- go pipe(upstreamWriter, rw)
-
- wg.Wait()
- conn.Close()
- return
- }
- w.WriteHeader(response.StatusCode)
- io.Copy(w, response.Body)
+ rproxy.ServeHTTP(w, r)
}
-func staticUpstreamActionFromYaml(node *yaml.Node) (RouteAction, error) {
+func proxyActionFromYaml(node *yaml.Node) (RouteAction, error) {
var rawNode struct {
- Proxy *staticUpstreamAction `yaml:"proxy"`
+ Proxy *proxyAction `yaml:"proxy"`
}
err := node.Decode(&rawNode)
}
func init() {
- AddRouteParseFunc(staticUpstreamActionFromYaml)
+ AddRouteParseFunc(proxyActionFromYaml)
}