package http import ( "io/ioutil" "net/http" "sync" "forge.cadoles.com/arcad/edge/pkg/app" "forge.cadoles.com/arcad/edge/pkg/bundle" "forge.cadoles.com/arcad/edge/pkg/bus" "github.com/go-chi/chi/v5" "github.com/igm/sockjs-go/v3/sockjs" "github.com/pkg/errors" ) const ( sockJSPathPrefix = "/edge/sock" serverMainScript = "server/main.js" ) type Handler struct { bundle bundle.Bundle public http.Handler router chi.Router sockjs http.Handler bus bus.Bus sockjsOpts sockjs.Options uploadMaxFileSize int64 server *app.Server serverModuleFactories []app.ServerModuleFactory httpClient *http.Client mutex sync.RWMutex } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.router.ServeHTTP(w, r) } func (h *Handler) Load(bdle bundle.Bundle) error { h.mutex.Lock() defer h.mutex.Unlock() file, _, err := bdle.File(serverMainScript) if err != nil { return errors.Wrap(err, "could not open server main script") } mainScript, err := ioutil.ReadAll(file) if err != nil { return errors.Wrap(err, "could not read server main script") } server := app.NewServer(h.serverModuleFactories...) if err := server.Load(serverMainScript, string(mainScript)); err != nil { return errors.WithStack(err) } fs := bundle.NewFileSystem("public", bdle) public := HTML5Fileserver(fs) sockjs := sockjs.NewHandler(sockJSPathPrefix, h.sockjsOpts, h.handleSockJSSession) if h.server != nil { h.server.Stop() } if err := server.Start(); err != nil { return errors.WithStack(err) } h.bundle = bdle h.server = server h.public = public h.sockjs = sockjs return nil } func NewHandler(funcs ...HandlerOptionFunc) *Handler { opts := defaultHandlerOptions() for _, fn := range funcs { fn(opts) } router := chi.NewRouter() handler := &Handler{ uploadMaxFileSize: opts.UploadMaxFileSize, sockjsOpts: opts.SockJS, router: router, serverModuleFactories: opts.ServerModuleFactories, httpClient: opts.HTTPClient, bus: opts.Bus, } router.Route("/edge", func(r chi.Router) { r.Route("/sdk", func(r chi.Router) { r.Get("/client.js", handler.handleSDKClient) r.Get("/client.js.map", handler.handleSDKClientMap) }) r.Route("/api", func(r chi.Router) { r.Post("/v1/upload", handler.handleAppUpload) r.Get("/v1/download/{bucket}/{blobID}", handler.handleAppDownload) r.Get("/v1/fetch", handler.handleAppFetch) }) for _, fn := range opts.HTTPMounts { r.Group(func(r chi.Router) { fn(r) }) } r.HandleFunc("/sock/*", handler.handleSockJS) }) router.Get("/*", handler.handleAppFiles) return handler }