13493: Move proxy and federation code to their own source files.
[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
14         "git.curoverse.com/arvados.git/sdk/go/arvados"
15         "git.curoverse.com/arvados.git/sdk/go/health"
16         "git.curoverse.com/arvados.git/sdk/go/httpserver"
17 )
18
19 type Handler struct {
20         Cluster     *arvados.Cluster
21         NodeProfile *arvados.NodeProfile
22
23         setupOnce    sync.Once
24         handlerStack http.Handler
25         proxyClient  *arvados.Client
26 }
27
28 func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
29         h.setupOnce.Do(h.setup)
30         h.handlerStack.ServeHTTP(w, req)
31 }
32
33 func (h *Handler) CheckHealth() error {
34         h.setupOnce.Do(h.setup)
35         _, err := findRailsAPI(h.Cluster, h.NodeProfile)
36         return err
37 }
38
39 func (h *Handler) setup() {
40         mux := http.NewServeMux()
41         mux.Handle("/_health/", &health.Handler{
42                 Token:  h.Cluster.ManagementToken,
43                 Prefix: "/_health/",
44         })
45         hs := http.NotFoundHandler()
46         hs = prepend(hs, h.proxyRailsAPI)
47         hs = prepend(hs, h.proxyRemoteCluster)
48         mux.Handle("/", hs)
49         h.handlerStack = mux
50 }
51
52 type middlewareFunc func(http.ResponseWriter, *http.Request, http.Handler)
53
54 func prepend(next http.Handler, middleware middlewareFunc) http.Handler {
55         return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
56                 middleware(w, req, next)
57         })
58 }
59
60 func (h *Handler) proxyRailsAPI(w http.ResponseWriter, req *http.Request, next http.Handler) {
61         urlOut, err := findRailsAPI(h.Cluster, h.NodeProfile)
62         if err != nil {
63                 httpserver.Error(w, err.Error(), http.StatusInternalServerError)
64                 return
65         }
66         urlOut = &url.URL{
67                 Scheme:   urlOut.Scheme,
68                 Host:     urlOut.Host,
69                 Path:     req.URL.Path,
70                 RawPath:  req.URL.RawPath,
71                 RawQuery: req.URL.RawQuery,
72         }
73         h.proxy(w, req, urlOut)
74 }
75
76 // For now, findRailsAPI always uses the rails API running on this
77 // node.
78 func findRailsAPI(cluster *arvados.Cluster, np *arvados.NodeProfile) (*url.URL, error) {
79         hostport := np.RailsAPI.Listen
80         if len(hostport) > 1 && hostport[0] == ':' && strings.TrimRight(hostport[1:], "0123456789") == "" {
81                 // ":12345" => connect to indicated port on localhost
82                 hostport = "localhost" + hostport
83         } else if _, _, err := net.SplitHostPort(hostport); err == nil {
84                 // "[::1]:12345" => connect to indicated address & port
85         } else {
86                 return nil, err
87         }
88         proto := "http"
89         if np.RailsAPI.TLS {
90                 proto = "https"
91         }
92         return url.Parse(proto + "://" + hostport)
93 }