11package main
22
33import (
4+ "context"
45 "flag"
56 "fmt"
7+ "net/http"
8+ "os"
9+ "sync"
10+ "time"
11+
612 "github.com/gorilla/mux"
13+ "github.com/x1unix/foundation/app"
714 "github.com/x1unix/go-playground/pkg/analyzer"
815 "github.com/x1unix/go-playground/pkg/compiler"
916 "github.com/x1unix/go-playground/pkg/compiler/storage"
1017 "github.com/x1unix/go-playground/pkg/langserver"
1118 "go.uber.org/zap"
12- "log"
13- "net/http"
14- "os"
1519)
1620
21+ type appArgs struct {
22+ packagesFile string
23+ addr string
24+ debug bool
25+ buildDir string
26+ cleanupInterval string
27+ }
28+
29+ func (a appArgs ) getCleanDuration () (time.Duration , error ) {
30+ return time .ParseDuration (a .cleanupInterval )
31+ }
32+
1733func main () {
18- var packagesFile string
19- var addr string
20- var debug bool
21- var buildDir string
22- flag .StringVar (& packagesFile , "f" , "packages.json" , "Path to packages index JSON file" )
23- flag .StringVar (& addr , "addr" , ":8080" , "TCP Listen address" )
24- flag .StringVar (& buildDir , "wasm-build-dir" , os .TempDir (), "Directory for WASM builds" )
25- flag .BoolVar (& debug , "debug" , false , "Enable debug mode" )
34+ args := appArgs {}
35+ flag .StringVar (& args .packagesFile , "f" , "packages.json" , "Path to packages index JSON file" )
36+ flag .StringVar (& args .addr , "addr" , ":8080" , "TCP Listen address" )
37+ flag .StringVar (& args .buildDir , "wasm-build-dir" , os .TempDir (), "Directory for WASM builds" )
38+ flag .StringVar (& args .cleanupInterval , "clean-interval" , "10m" , "Build directory cleanup interval" )
39+ flag .BoolVar (& args .debug , "debug" , false , "Enable debug mode" )
2640
2741 goRoot , ok := os .LookupEnv ("GOROOT" )
2842 if ! ok {
@@ -31,9 +45,9 @@ func main() {
3145 }
3246
3347 flag .Parse ()
34- l := getLogger (debug )
48+ l := getLogger (args . debug )
3549 defer l .Sync ()
36- if err := start (packagesFile , addr , goRoot , buildDir , debug ); err != nil {
50+ if err := start (goRoot , args ); err != nil {
3751 l .Sugar ().Fatal (err )
3852 }
3953}
@@ -55,37 +69,78 @@ func getLogger(debug bool) (l *zap.Logger) {
5569 return l
5670}
5771
58- func start (packagesFile , addr , goRoot , buildDir string , debug bool ) error {
72+ func start (goRoot string , args appArgs ) error {
73+ cleanInterval , err := args .getCleanDuration ()
74+ if err != nil {
75+ return fmt .Errorf ("invalid cleanup interval parameter: %s" , err )
76+ }
77+
5978 zap .S ().Infof ("GOROOT is %q" , goRoot )
60- zap .S ().Infof ("Packages file is %q" , packagesFile )
79+ zap .S ().Infof ("Packages file is %q" , args .packagesFile )
80+ zap .S ().Infof ("Cleanup interval is %s" , cleanInterval .String ())
6181 analyzer .SetRoot (goRoot )
62- packages , err := analyzer .ReadPackagesFile (packagesFile )
82+ packages , err := analyzer .ReadPackagesFile (args . packagesFile )
6383 if err != nil {
64- return fmt .Errorf ("failed to read packages file %q: %s" , packagesFile , err )
84+ return fmt .Errorf ("failed to read packages file %q: %s" , args . packagesFile , err )
6585 }
6686
67- store , err := storage .NewLocalStorage (zap .S (), buildDir )
87+ store , err := storage .NewLocalStorage (zap .S (), args . buildDir )
6888 if err != nil {
6989 return err
7090 }
7191
92+ ctx , _ := app .GetApplicationContext ()
93+ wg := & sync.WaitGroup {}
94+ go store .StartCleaner (ctx , cleanInterval , wg )
95+
7296 r := mux .NewRouter ()
7397 langserver .New (packages , compiler .NewBuildService (zap .S (), store )).
7498 Mount (r .PathPrefix ("/api" ).Subrouter ())
7599 r .PathPrefix ("/" ).Handler (langserver .SpaFileServer ("./public" ))
76100
77- zap .S ().Infof ("Listening on %q" , addr )
78-
79101 var handler http.Handler
80- if debug {
102+ if args . debug {
81103 zap .S ().Info ("Debug mode enabled, CORS disabled" )
82104 handler = langserver .NewCORSDisablerWrapper (r )
83105 } else {
84106 handler = r
85107 }
86108
87- if err := http .ListenAndServe (addr , handler ); err != nil {
88- log .Fatal (err )
109+ server := & http.Server {
110+ Addr : args .addr ,
111+ Handler : handler ,
112+ ReadTimeout : 5 * time .Second ,
113+ WriteTimeout : 10 * time .Second ,
114+ IdleTimeout : 15 * time .Second ,
115+ }
116+
117+ if err := startHttpServer (ctx , wg , server ); err != nil {
118+ return err
119+ }
120+
121+ wg .Wait ()
122+ return nil
123+ }
124+
125+ func startHttpServer (ctx context.Context , wg * sync.WaitGroup , server * http.Server ) error {
126+ logger := zap .S ()
127+ go func () {
128+ <- ctx .Done ()
129+ logger .Info ("Shutting down server..." )
130+ shutdownCtx , cancel := context .WithTimeout (ctx , 30 * time .Second )
131+ defer cancel ()
132+ defer wg .Done ()
133+ server .SetKeepAlivesEnabled (false )
134+ if err := server .Shutdown (shutdownCtx ); err != nil {
135+ logger .Errorf ("Could not gracefully shutdown the server: %v\n " , err )
136+ }
137+ return
138+ }()
139+
140+ wg .Add (1 )
141+ logger .Infof ("Listening on %q" , server .Addr )
142+ if err := server .ListenAndServe (); err != nil && err != http .ErrServerClosed {
143+ return fmt .Errorf ("cannot start server on %q: %s" , server .Addr , err )
89144 }
90145
91146 return nil
0 commit comments