-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserve.go
More file actions
207 lines (177 loc) · 5.63 KB
/
serve.go
File metadata and controls
207 lines (177 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Package peerdb provides a collaboration platform (database and application framework).
package peerdb
import (
"context"
"io/fs"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"gitlab.com/tozd/go/cli"
"gitlab.com/tozd/go/errors"
"gitlab.com/tozd/waf"
internalCore "gitlab.com/peerdb/peerdb/internal/core"
)
// Service is the main HTTP service for PeerDB.
type Service struct {
waf.Service[*Site]
// Is service running in development mode.
Development bool
}
// Init initializes the HTTP service and is used together with Prepare to implement Run.
func (c *ServeCommand) Init(ctx context.Context, globals *Globals, files fs.FS) (*Service, func(), errors.E) {
c.Server.Logger = globals.Logger
sites := map[string]*Site{}
for i := range globals.Sites {
site := &globals.Sites[i]
sites[site.Domain] = site
}
if len(sites) == 0 && c.Domain != "" {
// If sites are not provided, but default domain is,
// we create a site based on the default domain.
globals.Sites = []Site{{
Site: waf.Site{
Domain: c.Domain,
CertFile: "",
KeyFile: "",
},
Build: nil,
Index: globals.Elastic.Index,
Schema: globals.Postgres.Schema,
Title: c.Title,
Logo: "",
LanguagePriority: nil,
DefaultLanguage: "",
LanguageCodes: nil,
Features: SiteFeatures{},
Base: nil,
DBPool: nil,
ESClient: nil,
RiverClient: nil,
debugRiverHandler: nil,
initialized: false,
propertiesTotal: 0,
unitsTotal: 0,
}}
sites[c.Domain] = &globals.Sites[0]
}
// If sites are not provided (and no default domain), sites
// are automatically constructed based on the certificate.
sitesProvided := len(sites) > 0
sites, errE := c.Server.Init(sites)
if errE != nil {
return nil, nil, errE
}
if !sitesProvided {
// We set fields not set when sites are automatically constructed.
for domain, site := range sites {
site.Index = globals.Elastic.Index
site.Schema = globals.Postgres.Schema
site.Title = c.Title
// We copy the site to globals.Sites.
globals.Sites = append(globals.Sites, *site)
// And then we update the reference to this copy.
sites[domain] = &globals.Sites[len(globals.Sites)-1]
}
}
// We set build information on sites.
if cli.Version != "" || cli.BuildTimestamp != "" || cli.Revision != "" {
for _, site := range sites {
site.Build = &Build{
Version: cli.Version,
BuildTimestamp: cli.BuildTimestamp,
Revision: cli.Revision,
}
}
}
onShutdown, errE := Init(ctx, globals)
if errE != nil {
return nil, onShutdown, errE
}
var middleware []func(http.Handler) http.Handler
if c.Username != "" && c.Password != nil {
middleware = append(middleware, basicAuthHandler(c.Username, strings.TrimSpace(string(c.Password))))
globals.Logger.Info().Str("username", c.Username).Msg("authentication enabled for all sites")
}
service := &Service{ //nolint:forcetypeassert
Service: waf.Service[*Site]{
Logger: globals.Logger,
CanonicalLogger: globals.Logger,
WithContext: globals.WithContext,
StaticFiles: files.(fs.ReadFileFS), //nolint:errcheck
Routes: nil,
Sites: sites,
Middleware: middleware,
SiteContextPath: "/context.json",
RoutesPath: "/routes.json",
ProxyStaticTo: c.Server.ProxyToInDevelopment(),
SkipServingFile: func(path string) bool {
switch path {
case "/index.html":
// We want the file to be served by Home route at / and not be
// available at index.html (as well).
return true
case "/LICENSE.txt":
// We want the file to be served by License route at /LICENSE and not be
// available at LICENSE.txt (as well).
return true
case "/NOTICE.txt":
// We want the file to be served by Notice route at /NOTICE and not be
// available at NOTICE.txt (as well).
return true
default:
return false
}
},
},
Development: c.Server.Development,
}
service.setRoutes()
return service, onShutdown, nil
}
// Prepare prepares the HTTP service for serving.
func (c *ServeCommand) Prepare(ctx context.Context, service *Service) (http.Handler, errors.E) {
for _, site := range service.Sites {
siteCtx := WithFallbackDBContext(ctx, site.Schema, "prepare")
documents, errE := site.fetchDocuments(siteCtx, internalCore.PropertyClassID)
if errE != nil {
return nil, errE
}
languages, errE := site.fetchDocuments(siteCtx, internalCore.LanguageClassID)
if errE != nil {
return nil, errE
}
documents = append(documents, languages...)
errE = site.Start(siteCtx, documents)
if errE != nil {
return nil, errE
}
c.Server.Logger.Info().Str("domain", site.Domain).Str("index", site.Index).Str("schema", site.Schema).Msg("serving")
}
// Construct the main handler for the service using the router.
router := new(waf.Router)
return service.RouteWith(router)
}
// Run starts the HTTP server and serves the PeerDB application.
func (c *ServeCommand) Run(globals *Globals, files fs.FS) errors.E {
// We stop the server gracefully on ctrl-c and TERM signal.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
ctx = globals.Logger.WithContext(ctx)
ctx, cancel := context.WithCancel(ctx)
service, onShutdown, errE := c.Init(ctx, globals, files)
if onShutdown != nil {
defer onShutdown()
}
defer cancel()
if errE != nil {
return errE
}
handler, errE := c.Prepare(ctx, service)
if errE != nil {
return errE
}
// It returns only on error or if the server is gracefully shut down using ctrl-c.
return c.Server.Run(ctx, handler)
}