// Package server implements the Monkey Lang Web Server that implements // a web-based Playground for testing out and trying Monkey on the Web. package server import ( "context" "embed" "errors" "fmt" "io/fs" "net/http" "time" log "github.com/sirupsen/logrus" "go.mills.io/logger" "go.mills.io/router" ) const ( // DefaultBind is the default [
]: to bind to DefaultBind = ":8000" ) //go:embed static/* var staticFS embed.FS // Option is a function type that configures the server type Option func(svr *Server) error // WithBind configures the server with a bind interface and port in the form of [
]: // For example: WithBind(":8000") func WithBind(bind string) Option { return func(svr *Server) error { svr.bind = bind return nil } } type Server struct { bind string routes *router.Router } func NewServer(opts ...Option) (*Server, error) { routes := router.New() routes.Use(router.Middleware(func(next router.Handle) router.Handle { return func(w http.ResponseWriter, r *http.Request, p router.Params) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "*") next(w, r, p) } })) svr := &Server{ bind: DefaultBind, routes: routes, } for _, opt := range opts { if err := opt(svr); err != nil { return nil, fmt.Errorf("error configuring server: %w", err) } } svr.initRoutes() return svr, nil } func (s *Server) initRoutes() { fs, err := fs.Sub(staticFS, "static") if err != nil { log.Fatal(err) } s.routes.ServeFiles("/*filepath", http.FS(fs)) s.routes.POST("/api/run", s.runHandler()) s.routes.POST("/api/format", s.formatHandler()) } // Run runs the server with the provided context and shuts down when the context is cancelled func (s *Server) Run(ctx context.Context) error { svr := &http.Server{ Addr: s.bind, Handler: logger.New(logger.Options{ Prefix: "monkey", RemoteAddressHeaders: []string{"X-Forwarded-For"}, }).Handler(s.routes), } go func() { if err := svr.ListenAndServe(); err != nil { if !errors.Is(err, http.ErrServerClosed) { log.WithError(err).Error("svr.ListenAndServe error") } } }() select { case <-ctx.Done(): ctx, cancel := context.WithTimeout(ctx, time.Second*3) defer cancel() if err := svr.Shutdown(ctx); err != nil { return fmt.Errorf("error shutting down server: %w", err) } return nil } }