server
This commit is contained in:
110
internal/server/server.go
Normal file
110
internal/server/server.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// 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 [<address>]:<port> 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 [<address>]:<port>
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user