Skip to content

Commit eabd3e1

Browse files
committed
Exported new server struct, exposed server router and content, fixed concurrency issue.
1 parent c1ccb6f commit eabd3e1

File tree

5 files changed

+74
-33
lines changed

5 files changed

+74
-33
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.5.0
4+
5+
* exported new server struct
6+
* exposed server router and content
7+
* fixed concurrency issue
8+
39
## 0.4.0
410

511
* fixed concurrency issue

cmd/shifu/main.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ func main() {
2929

3030
switch cmd {
3131
case "run":
32-
if err := shifu.Start(dir, nil); err != nil {
32+
server := shifu.NewServer(dir, shifu.ServerOptions{})
33+
34+
if err := server.Start(nil); err != nil {
3335
slog.Error("Error starting Shifu", "error", err)
3436
}
3537
case "init":

pkg/cms/cms.go

-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ func NewCMS(options Options) *CMS {
6666
}
6767
cms.tpl = NewCache(filepath.Join(options.BaseDir, "tpl"), options.FuncMap, options.HotReload)
6868
cms.source.Update(options.Ctx, func() {
69-
cms.m.Lock()
70-
defer cms.m.Unlock()
7169
cms.updateTpl()
7270
cms.updateContent()
7371
cms.updateSitemap()

pkg/server.go

+64-29
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,60 @@ import (
2424
"time"
2525
)
2626

27-
// Start starts the Shifu server for given directory.
27+
// ServerOptions are the options for the Shifu Server.
28+
type ServerOptions struct {
29+
// FuncMap will be merged with the default Shifu template function map.
30+
FuncMap template.FuncMap
31+
}
32+
33+
// Server is the Shifu server.
34+
type Server struct {
35+
// Content is the CMS content.
36+
Content *cms.CMS
37+
38+
// Router is the router for the server.
39+
// It has a CORS and gzip middleware, as well as a route for robots.txt, the static directory, and any unmatched path (/*) by default.
40+
Router chi.Router
41+
42+
dir string
43+
funcMap template.FuncMap
44+
}
45+
46+
// NewServer creates a new Shifu server for given directory.
2847
// The second argument is an optional template.FuncMap that will be merged with Shifu's funcmap.
29-
func Start(dir string, funcMap template.FuncMap) error {
30-
slog.Info("Starting Shifu", "version", version, "directory", dir)
31-
funcMap = cms.Merge(funcMap)
32-
ctx, cancel := context.WithCancel(context.Background())
48+
func NewServer(dir string, options ServerOptions) *Server {
49+
return &Server{
50+
dir: dir,
51+
funcMap: options.FuncMap,
52+
}
53+
}
3354

34-
if err := cfg.Watch(ctx, dir, funcMap); err != nil {
35-
cancel()
55+
// Start starts the Shifu server.
56+
// The context.CancelFunc is optional and will be called on server shutdown or error if set.
57+
func (server *Server) Start(cancel context.CancelFunc) error {
58+
slog.Info("Starting Shifu", "version", version, "directory", server.dir)
59+
server.funcMap = cms.Merge(server.funcMap)
60+
ctx, cancelServer := context.WithCancel(context.Background())
61+
stop := func() {
62+
cancelServer()
63+
64+
if cancel != nil {
65+
cancel()
66+
}
67+
}
68+
69+
if err := cfg.Watch(ctx, server.dir, server.funcMap); err != nil {
70+
stop()
3671
return err
3772
}
3873

39-
if err := sass.Watch(ctx, dir); err != nil {
40-
cancel()
74+
if err := sass.Watch(ctx, server.dir); err != nil {
75+
stop()
4176
return err
4277
}
4378

44-
if err := js.Watch(ctx, dir); err != nil {
45-
cancel()
79+
if err := js.Watch(ctx, server.dir); err != nil {
80+
stop()
4681
return err
4782
}
4883

@@ -51,45 +86,45 @@ func Start(dir string, funcMap template.FuncMap) error {
5186

5287
switch strings.ToLower(config.Provider) {
5388
case "fs":
54-
provider = source.NewFS(dir, config.UpdateSeconds)
89+
provider = source.NewFS(server.dir, config.UpdateSeconds)
5590
break
5691
case "git":
57-
provider = source.NewGit(dir, config.Repository, config.UpdateSeconds)
92+
provider = source.NewGit(server.dir, config.Repository, config.UpdateSeconds)
5893
break
5994
default:
60-
cancel()
95+
stop()
6196
return errors.New("content provider not found")
6297
}
6398

6499
sm := sitemap.New()
65-
content := cms.NewCMS(cms.Options{
100+
server.Content = cms.NewCMS(cms.Options{
66101
Ctx: ctx,
67-
BaseDir: dir,
102+
BaseDir: server.dir,
68103
HotReload: cfg.Get().Dev,
69-
FuncMap: funcMap,
104+
FuncMap: server.funcMap,
70105
Source: provider,
71106
Sitemap: sm,
72107
})
73108
analytics.Init()
74-
router := setupRouter(dir, content, sm)
75-
<-startServer(router, cancel)
109+
server.Router = server.setupRouter(server.dir, server.Content, sm)
110+
<-server.startServer(server.Router, stop)
76111
return nil
77112
}
78113

79-
func setupRouter(dir string, cms *cms.CMS, sm *sitemap.Sitemap) chi.Router {
114+
func (server *Server) setupRouter(dir string, cms *cms.CMS, sm *sitemap.Sitemap) chi.Router {
80115
router := chi.NewRouter()
81116
router.Use(
82117
middleware.Cors(),
83118
middleware.Gzip(),
84119
)
85120
sm.Serve(router)
86-
serveRobotsTxt(router)
87-
serveStaticDir(router, dir)
121+
server.serveRobotsTxt(router)
122+
server.serveStaticDir(router, dir)
88123
router.Handle("/*", http.HandlerFunc(cms.Serve))
89124
return router
90125
}
91126

92-
func serveRobotsTxt(router chi.Router) {
127+
func (server *Server) serveRobotsTxt(router chi.Router) {
93128
robotsTxt := fmt.Sprintf("User-agent: *\nDisallow:\n\nSitemap: %s/sitemap.xml\n", cfg.Get().Server.Hostname)
94129
router.Handle("/robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
95130
w.Header().Add("Content-Type", "text/plain")
@@ -101,18 +136,18 @@ func serveRobotsTxt(router chi.Router) {
101136
}))
102137
}
103138

104-
func serveStaticDir(router chi.Router, dir string) {
139+
func (server *Server) serveStaticDir(router chi.Router, dir string) {
105140
fs := http.StripPrefix("/static/", http.FileServer(http.Dir(filepath.Join(dir, "static"))))
106141
router.Handle("/static/*", gzhttp.GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
107142
fs.ServeHTTP(w, r)
108143
})))
109144
}
110145

111-
func startServer(handler http.Handler, cancel context.CancelFunc) chan struct{} {
146+
func (server *Server) startServer(handler http.Handler, cancel context.CancelFunc) chan struct{} {
112147
slog.Info("Starting server...")
113148
config := cfg.Get()
114149
addr := fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port)
115-
server := &http.Server{
150+
httpServer := &http.Server{
116151
Handler: handler,
117152
Addr: addr,
118153
WriteTimeout: time.Second * time.Duration(config.Server.WriteTimeout),
@@ -127,7 +162,7 @@ func startServer(handler http.Handler, cancel context.CancelFunc) chan struct{}
127162
cancel()
128163
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(config.Server.ShutdownTimeout))
129164

130-
if err := server.Shutdown(ctx); err != nil {
165+
if err := httpServer.Shutdown(ctx); err != nil {
131166
slog.Error("Error shutting down server gracefully", "error", err)
132167
panic(err)
133168
}
@@ -139,12 +174,12 @@ func startServer(handler http.Handler, cancel context.CancelFunc) chan struct{}
139174

140175
go func() {
141176
if config.Server.TLSCertFile != "" && config.Server.TLSKeyFile != "" {
142-
if err := server.ListenAndServeTLS(config.Server.TLSCertFile, config.Server.TLSKeyFile); err != nil {
177+
if err := httpServer.ListenAndServeTLS(config.Server.TLSCertFile, config.Server.TLSKeyFile); err != nil {
143178
slog.Error("Error starting server", "error", err)
144179
panic(err)
145180
}
146181
} else {
147-
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
182+
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
148183
slog.Error("Error starting server", "error", err)
149184
panic(err)
150185
}

pkg/version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package pkg
22

33
const (
4-
version = "0.4.0"
4+
version = "0.5.0"
55
)
66

77
// Version returns the Shifu version number.

0 commit comments

Comments
 (0)