13493: Merge branch 'master' into 13493-federation-proxy
[arvados.git] / lib / controller / handler.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package controller
6
7 import (
8         "net"
9         "net/http"
10         "net/url"
11         "strings"
12         "sync"
13         "time"
14
15         "git.curoverse.com/arvados.git/sdk/go/arvados"
16         "git.curoverse.com/arvados.git/sdk/go/health"
17         "git.curoverse.com/arvados.git/sdk/go/httpserver"
18 )
19
20 type Handler struct {
21         Cluster     *arvados.Cluster
22         NodeProfile *arvados.NodeProfile
23
24         setupOnce      sync.Once
25         handlerStack   http.Handler
26         proxy          *proxy
27         secureClient   *http.Client
28         insecureClient *http.Client
29 }
30
31 func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
32         h.setupOnce.Do(h.setup)
33         h.handlerStack.ServeHTTP(w, req)
34 }
35
36 func (h *Handler) CheckHealth() error {
37         h.setupOnce.Do(h.setup)
38         _, _, err := findRailsAPI(h.Cluster, h.NodeProfile)
39         return err
40 }
41
42 func (h *Handler) setup() {
43         mux := http.NewServeMux()
44         mux.Handle("/_health/", &health.Handler{
45                 Token:  h.Cluster.ManagementToken,
46                 Prefix: "/_health/",
47         })
48         hs := http.NotFoundHandler()
49         hs = prepend(hs, h.proxyRailsAPI)
50         hs = prepend(hs, h.proxyRemoteCluster)
51         mux.Handle("/", hs)
52         h.handlerStack = mux
53
54         sc := *arvados.DefaultSecureClient
55         sc.Timeout = time.Duration(h.Cluster.HTTPRequestTimeout)
56         h.secureClient = &sc
57
58         ic := *arvados.InsecureHTTPClient
59         ic.Timeout = time.Duration(h.Cluster.HTTPRequestTimeout)
60         h.insecureClient = &ic
61
62         h.proxy = &proxy{
63                 Name:           "arvados-controller",
64                 RequestTimeout: time.Duration(h.Cluster.HTTPRequestTimeout),
65         }
66 }
67
68 type middlewareFunc func(http.ResponseWriter, *http.Request, http.Handler)
69
70 func prepend(next http.Handler, middleware middlewareFunc) http.Handler {
71         return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
72                 middleware(w, req, next)
73         })
74 }
75
76 func (h *Handler) proxyRailsAPI(w http.ResponseWriter, req *http.Request, next http.Handler) {
77         urlOut, insecure, err := findRailsAPI(h.Cluster, h.NodeProfile)
78         if err != nil {
79                 httpserver.Error(w, err.Error(), http.StatusInternalServerError)
80                 return
81         }
82         urlOut = &url.URL{
83                 Scheme:   urlOut.Scheme,
84                 Host:     urlOut.Host,
85                 Path:     req.URL.Path,
86                 RawPath:  req.URL.RawPath,
87                 RawQuery: req.URL.RawQuery,
88         }
89         client := h.secureClient
90         if insecure {
91                 client = h.insecureClient
92         }
93         h.proxy.Do(w, req, urlOut, client)
94 }
95
96 // For now, findRailsAPI always uses the rails API running on this
97 // node.
98 func findRailsAPI(cluster *arvados.Cluster, np *arvados.NodeProfile) (*url.URL, bool, error) {
99         hostport := np.RailsAPI.Listen
100         if len(hostport) > 1 && hostport[0] == ':' && strings.TrimRight(hostport[1:], "0123456789") == "" {
101                 // ":12345" => connect to indicated port on localhost
102                 hostport = "localhost" + hostport
103         } else if _, _, err := net.SplitHostPort(hostport); err == nil {
104                 // "[::1]:12345" => connect to indicated address & port
105         } else {
106                 return nil, false, err
107         }
108         proto := "http"
109         if np.RailsAPI.TLS {
110                 proto = "https"
111         }
112         url, err := url.Parse(proto + "://" + hostport)
113         return url, np.RailsAPI.Insecure, err
114 }