router.go (8234B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package router 19 20 import ( 21 "context" 22 "crypto/tls" 23 "fmt" 24 "net" 25 "net/http" 26 "time" 27 28 "codeberg.org/gruf/go-bytesize" 29 "codeberg.org/gruf/go-debug" 30 "github.com/gin-gonic/gin" 31 "github.com/superseriousbusiness/gotosocial/internal/config" 32 "github.com/superseriousbusiness/gotosocial/internal/log" 33 "golang.org/x/crypto/acme/autocert" 34 ) 35 36 const ( 37 readTimeout = 60 * time.Second 38 writeTimeout = 30 * time.Second 39 idleTimeout = 30 * time.Second 40 readHeaderTimeout = 30 * time.Second 41 shutdownTimeout = 30 * time.Second 42 maxMultipartMemory = int64(8 * bytesize.MiB) 43 ) 44 45 // Router provides the REST interface for gotosocial, using gin. 46 type Router interface { 47 // Attach global gin middlewares to this router. 48 AttachGlobalMiddleware(handlers ...gin.HandlerFunc) gin.IRoutes 49 // AttachGroup attaches the given handlers into a group with the given relativePath as 50 // base path for that group. It then returns the *gin.RouterGroup so that the caller 51 // can add any extra middlewares etc specific to that group, as desired. 52 AttachGroup(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup 53 // Attach a single gin handler to the router with the given method and path. 54 // To make middleware management easier, AttachGroup should be preferred where possible. 55 // However, this function can be used for attaching single handlers that only require 56 // global middlewares. 57 AttachHandler(method string, path string, handler gin.HandlerFunc) 58 59 // Attach 404 NoRoute handler 60 AttachNoRouteHandler(handler gin.HandlerFunc) 61 // Start the router 62 Start() 63 // Stop the router 64 Stop(ctx context.Context) error 65 } 66 67 // router fulfils the Router interface using gin and logrus 68 type router struct { 69 engine *gin.Engine 70 srv *http.Server 71 certManager *autocert.Manager 72 } 73 74 // Start starts the router nicely. It will serve two handlers if letsencrypt is enabled, and only the web/API handler if letsencrypt is not enabled. 75 func (r *router) Start() { 76 // listen is the server start function, by 77 // default pointing to regular HTTP listener, 78 // but updated to TLS if LetsEncrypt is enabled. 79 listen := r.srv.ListenAndServe 80 81 // During config validation we already checked that both Chain and Key are set 82 // so we can forego checking for both here 83 if chain := config.GetTLSCertificateChain(); chain != "" { 84 pkey := config.GetTLSCertificateKey() 85 cer, err := tls.LoadX509KeyPair(chain, pkey) 86 if err != nil { 87 log.Fatalf( 88 nil, 89 "tls: failed to load keypair from %s and %s, ensure they are PEM-encoded and can be read by this process: %s", 90 chain, pkey, err, 91 ) 92 } 93 r.srv.TLSConfig = &tls.Config{ 94 MinVersion: tls.VersionTLS12, 95 Certificates: []tls.Certificate{cer}, 96 } 97 // TLS is enabled, update the listen function 98 listen = func() error { return r.srv.ListenAndServeTLS("", "") } 99 } 100 101 if config.GetLetsEncryptEnabled() { 102 // LetsEncrypt support is enabled 103 104 // Prepare an HTTPS-redirect handler for LetsEncrypt fallback 105 redirect := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 106 target := "https://" + r.Host + r.URL.Path 107 if len(r.URL.RawQuery) > 0 { 108 target += "?" + r.URL.RawQuery 109 } 110 http.Redirect(rw, r, target, http.StatusTemporaryRedirect) 111 }) 112 113 go func() { 114 // Take our own copy of HTTP server 115 // with updated autocert manager endpoint 116 srv := (*r.srv) //nolint 117 srv.Handler = r.certManager.HTTPHandler(redirect) 118 srv.Addr = fmt.Sprintf("%s:%d", 119 config.GetBindAddress(), 120 config.GetLetsEncryptPort(), 121 ) 122 123 // Start the LetsEncrypt autocert manager HTTP server. 124 log.Infof(nil, "letsencrypt listening on %s", srv.Addr) 125 if err := srv.ListenAndServe(); err != nil && 126 err != http.ErrServerClosed { 127 log.Fatalf(nil, "letsencrypt: listen: %s", err) 128 } 129 }() 130 131 // TLS is enabled, update the listen function 132 listen = func() error { return r.srv.ListenAndServeTLS("", "") } 133 } 134 135 // Pass the server handler through a debug pprof middleware handler. 136 // For standard production builds this will be a no-op, but when the 137 // "debug" or "debugenv" build-tag is set pprof stats will be served 138 // at the standard "/debug/pprof" URL. 139 r.srv.Handler = debug.WithPprof(r.srv.Handler) 140 if debug.DEBUG { 141 // Profiling requires timeouts longer than 30s, so reset these. 142 log.Warn(nil, "resetting http.Server{} timeout to support profiling") 143 r.srv.ReadTimeout = 0 144 r.srv.WriteTimeout = 0 145 } 146 147 // Start the main listener. 148 go func() { 149 log.Infof(nil, "listening on %s", r.srv.Addr) 150 if err := listen(); err != nil && err != http.ErrServerClosed { 151 log.Fatalf(nil, "listen: %s", err) 152 } 153 }() 154 } 155 156 // Stop shuts down the router nicely 157 func (r *router) Stop(ctx context.Context) error { 158 log.Infof(nil, "shutting down http router with %s grace period", shutdownTimeout) 159 timeout, cancel := context.WithTimeout(ctx, shutdownTimeout) 160 defer cancel() 161 162 if err := r.srv.Shutdown(timeout); err != nil { 163 return fmt.Errorf("error shutting down http router: %s", err) 164 } 165 166 log.Info(nil, "http router closed connections and shut down gracefully") 167 return nil 168 } 169 170 // New returns a new Router. 171 // 172 // The router's Attach functions should be used *before* the router is Started. 173 // 174 // When the router's work is finished, Stop should be called on it to close connections gracefully. 175 // 176 // The provided context will be used as the base context for all requests passing 177 // through the underlying http.Server, so this should be a long-running context. 178 func New(ctx context.Context) (Router, error) { 179 gin.SetMode(gin.TestMode) 180 181 // create the actual engine here -- this is the core request routing handler for gts 182 engine := gin.New() 183 engine.MaxMultipartMemory = maxMultipartMemory 184 185 // set up IP forwarding via x-forward-* headers. 186 trustedProxies := config.GetTrustedProxies() 187 if err := engine.SetTrustedProxies(trustedProxies); err != nil { 188 return nil, err 189 } 190 191 // set template functions 192 LoadTemplateFunctions(engine) 193 194 // load templates onto the engine 195 if err := LoadTemplates(engine); err != nil { 196 return nil, err 197 } 198 199 // use the passed-in command context as the base context for the server, 200 // since we'll never want the server to live past the command anyway 201 baseCtx := func(_ net.Listener) context.Context { 202 return ctx 203 } 204 205 bindAddress := config.GetBindAddress() 206 port := config.GetPort() 207 addr := fmt.Sprintf("%s:%d", bindAddress, port) 208 209 s := &http.Server{ 210 Addr: addr, 211 Handler: engine, // use gin engine as handler 212 ReadTimeout: readTimeout, 213 ReadHeaderTimeout: readHeaderTimeout, 214 WriteTimeout: writeTimeout, 215 IdleTimeout: idleTimeout, 216 BaseContext: baseCtx, 217 } 218 219 // We need to spawn the underlying server slightly differently depending on whether lets encrypt is enabled or not. 220 // In either case, the gin engine will still be used for routing requests. 221 leEnabled := config.GetLetsEncryptEnabled() 222 223 var m *autocert.Manager 224 if leEnabled { 225 // le IS enabled, so roll up an autocert manager for handling letsencrypt requests 226 host := config.GetHost() 227 leCertDir := config.GetLetsEncryptCertDir() 228 leEmailAddress := config.GetLetsEncryptEmailAddress() 229 m = &autocert.Manager{ 230 Prompt: autocert.AcceptTOS, 231 HostPolicy: autocert.HostWhitelist(host), 232 Cache: autocert.DirCache(leCertDir), 233 Email: leEmailAddress, 234 } 235 s.TLSConfig = m.TLSConfig() 236 } 237 238 return &router{ 239 engine: engine, 240 srv: s, 241 certManager: m, 242 }, nil 243 }