2022-06-03 19:39:22 +00:00
|
|
|
// Copyright (C) 2022 The pages authors
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//
|
|
|
|
|
|
|
|
package internal
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2022-06-08 07:14:27 +00:00
|
|
|
"path"
|
2022-06-03 19:39:22 +00:00
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
|
|
|
"github.com/mjpitz/myago/auth"
|
|
|
|
basicauth "github.com/mjpitz/myago/auth/basic"
|
|
|
|
httpauth "github.com/mjpitz/myago/auth/http"
|
|
|
|
"github.com/mjpitz/myago/headers"
|
|
|
|
"github.com/mjpitz/myago/livetls"
|
2022-06-08 07:14:27 +00:00
|
|
|
"github.com/mjpitz/pages/internal/excludes"
|
|
|
|
"github.com/mjpitz/pages/internal/geoip"
|
|
|
|
"github.com/mjpitz/pages/internal/pageviews"
|
|
|
|
"github.com/mjpitz/pages/internal/session"
|
|
|
|
"github.com/mjpitz/pages/internal/web"
|
2022-06-03 19:39:22 +00:00
|
|
|
)
|
|
|
|
|
2022-06-08 07:14:27 +00:00
|
|
|
// AdminConfig encapsulates configuration for the administrative endpoints.
|
2022-06-03 19:39:22 +00:00
|
|
|
type AdminConfig struct {
|
2022-06-08 07:14:27 +00:00
|
|
|
Prefix string `json:"prefix" usage:"configure the prefix to use for admin endpoints" default:"/_admin" hidden:"true"`
|
2022-06-03 19:39:22 +00:00
|
|
|
Username string `json:"username" usage:"specify the username used to authenticate requests with the admin endpoints" default:"admin"`
|
|
|
|
Password string `json:"password" usage:"specify the password used to authenticate requests with the admin endpoints"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// BindConfig defines the set of configuration options for setting up a server.
|
|
|
|
type BindConfig struct {
|
|
|
|
Address string `json:"address" usage:"configure the bind address for the server"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServerConfig defines configuration for a public and private interface.
|
|
|
|
type ServerConfig struct {
|
|
|
|
Admin AdminConfig `json:"admin"`
|
2022-06-08 07:14:27 +00:00
|
|
|
Session session.Config `json:"session"`
|
2022-06-03 19:39:22 +00:00
|
|
|
TLS livetls.Config `json:"tls"`
|
|
|
|
Public BindConfig `json:"public"`
|
|
|
|
Private BindConfig `json:"private"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewServer constructs a Server from it's associated configuration.
|
|
|
|
func NewServer(ctx context.Context, config ServerConfig) (*Server, error) {
|
|
|
|
tls, err := livetls.New(ctx, config.TLS)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
private := mux.NewRouter()
|
|
|
|
private.Handle("/metrics", promhttp.Handler())
|
|
|
|
|
2022-06-08 07:14:27 +00:00
|
|
|
exclusions := []excludes.Exclusion{
|
|
|
|
excludes.AssetExclusion(),
|
|
|
|
excludes.PrefixExclusion(config.Admin.Prefix),
|
|
|
|
excludes.PrefixExclusion(config.Session.Prefix),
|
|
|
|
}
|
|
|
|
|
2022-06-03 19:39:22 +00:00
|
|
|
public := mux.NewRouter()
|
|
|
|
public.Use(
|
|
|
|
func(next http.Handler) http.Handler { return headers.HTTP(next) },
|
2022-06-08 07:14:27 +00:00
|
|
|
geoip.Middleware(geoip.Empty{}),
|
|
|
|
pageviews.Middleware(
|
|
|
|
pageviews.Exclusions(exclusions...),
|
|
|
|
),
|
|
|
|
session.Middleware(
|
|
|
|
session.Exclusions(exclusions...),
|
|
|
|
session.JavaScriptPath(path.Join(config.Session.Prefix, "pages.js")),
|
2022-06-03 19:39:22 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2022-06-08 07:14:27 +00:00
|
|
|
admin := public.PathPrefix(config.Admin.Prefix).Subrouter()
|
2022-06-03 19:39:22 +00:00
|
|
|
|
|
|
|
if config.Admin.Password != "" {
|
|
|
|
authenticate := basicauth.Static(config.Admin.Username, config.Admin.Password)
|
|
|
|
required := auth.Required()
|
|
|
|
|
|
|
|
admin.Use(func(next http.Handler) http.Handler {
|
|
|
|
return httpauth.Handler(next, authenticate, required)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-08 07:14:27 +00:00
|
|
|
{
|
|
|
|
var handler http.Handler = session.Handler()
|
|
|
|
handler = http.StripPrefix(config.Session.Prefix, handler)
|
|
|
|
|
|
|
|
session := public.PathPrefix(config.Session.Prefix).Subrouter()
|
|
|
|
session.HandleFunc("/pages.js", web.Handler()).Methods(http.MethodGet)
|
|
|
|
session.Handle("/", handler)
|
|
|
|
}
|
|
|
|
|
2022-06-03 19:39:22 +00:00
|
|
|
return &Server{
|
|
|
|
AdminMux: admin,
|
|
|
|
|
|
|
|
PublicMux: public,
|
|
|
|
Public: &http.Server{
|
|
|
|
TLSConfig: tls,
|
|
|
|
Addr: config.Public.Address,
|
|
|
|
Handler: promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, public),
|
|
|
|
BaseContext: func(l net.Listener) context.Context {
|
|
|
|
return ctx
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
PrivateMux: private,
|
|
|
|
Private: &http.Server{
|
|
|
|
TLSConfig: tls,
|
|
|
|
Addr: config.Private.Address,
|
|
|
|
Handler: promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, private),
|
|
|
|
BaseContext: func(l net.Listener) context.Context {
|
|
|
|
return ctx
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Server hosts a Public and Private HTTP server.
|
|
|
|
type Server struct {
|
|
|
|
AdminMux *mux.Router
|
|
|
|
PublicMux *mux.Router
|
|
|
|
Public *http.Server
|
|
|
|
PrivateMux *mux.Router
|
|
|
|
Private *http.Server
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown closes the underlying Public and Private HTTP server.
|
|
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
|
|
_ = s.Public.Shutdown(ctx)
|
|
|
|
_ = s.Private.Shutdown(ctx)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListenAndServe starts underlying Public and Private HTTP servers.
|
|
|
|
func (s *Server) ListenAndServe() error {
|
|
|
|
var group errgroup.Group
|
|
|
|
group.Go(s.Public.ListenAndServe)
|
|
|
|
group.Go(s.Private.ListenAndServe)
|
|
|
|
|
|
|
|
return group.Wait()
|
|
|
|
}
|