From f30e49eba1fc456094953a950190422b649a0323 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 30 Jul 2021 01:38:01 +0200 Subject: [PATCH] Add basic setup for serving a MUD via HTTP --- cmd/view.go | 63 ++++++++++++++++++++++++++++++++++++++++---- internal/read.go | 1 + web/build.go | 43 ++++++++++++++++++++++++++++++ web/build/index.html | 9 +++++++ 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 web/build.go create mode 100644 web/build/index.html diff --git a/cmd/view.go b/cmd/view.go index ce5968b..c7a2cce 100644 --- a/cmd/view.go +++ b/cmd/view.go @@ -16,8 +16,13 @@ limitations under the License. package cmd import ( - "fmt" + "log" + "net/http" + "time" + "github.com/hslatman/mud-cli/internal" + "github.com/hslatman/mud-cli/web" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -25,14 +30,62 @@ import ( var viewCmd = &cobra.Command{ Use: "view", Short: "Provides a graphical view of a MUD file", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("view called") - // TODO: check if file exists locally or download temp copy - // TODO: serve the file locally; temporarily + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + filepath := args[0] + mudfile, err := internal.ReadMUDFileFrom(filepath) + if err != nil { + return errors.Wrap(err, "could not get contents") + } + + json, err := internal.JSON(mudfile) + if err != nil { + return errors.Wrap(err, "getting JSON representation of MUD file failed") + } + // TODO: open browser; show MUD Visualizer with the chosen MUD file + // TODO: provide option to show it in terminal? + + mudHandler := newMUDHandler(json) + + // Strip / and prepend build, so that a file `a/b.js` would be + // found in web/build/a/b.js, but served from localhost:8080/a/b.js. + webHandler := web.AssetHandler("/", "build") + + mux := http.NewServeMux() + mux.Handle("/mud", mudHandler) + mux.Handle("/", webHandler) + mux.Handle("/*filepath", webHandler) + + s := &http.Server{ + Addr: ":8080", + Handler: mux, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + + log.Fatal(s.ListenAndServe()) // TODO: add some logging? + + return nil }, } +type mudHandler struct { + json string +} + +func (m *mudHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(m.json)) +} + +func newMUDHandler(json string) http.Handler { + return &mudHandler{ + json: json, + } +} + func init() { rootCmd.AddCommand(viewCmd) } diff --git a/internal/read.go b/internal/read.go index be59004..c3742da 100644 --- a/internal/read.go +++ b/internal/read.go @@ -17,6 +17,7 @@ func Read(filepath string) ([]byte, error) { // TODO: provide additional parameters for filetype to retrieve (or use a MUD client abstraction?) // so that the right headers can be provided in the requests (and check these in the response?) // TODO: RFC states that MUD URL should be HTTPS; fail on that here if not? + // TODO: get rid of the temp file? I think it's relatively safe to keep the content bytes in memory f, err := ioutil.TempFile(os.TempDir(), "") if err != nil { return []byte{}, err diff --git a/web/build.go b/web/build.go new file mode 100644 index 0000000..1344968 --- /dev/null +++ b/web/build.go @@ -0,0 +1,43 @@ +// web/build.go +package web + +import ( + "embed" + "io/fs" + "net/http" + "os" + "path" +) + +//go:embed build/* +var Assets embed.FS + +// fsFunc is short-hand for constructing a http.FileSystem +// implementation +type fsFunc func(name string) (fs.File, error) + +func (f fsFunc) Open(name string) (fs.File, error) { + return f(name) +} + +// AssetHandler returns an http.Handler that will serve files from +// the Assets embed.FS. When locating a file, it will strip the given +// prefix from the request and prepend the root to the filesystem +// lookup: typical prefix might be /web/, and root would be build. +func AssetHandler(prefix, root string) http.Handler { + handler := fsFunc(func(name string) (fs.File, error) { + assetPath := path.Join(root, name) + + // If we can't find the asset, return the default index.html content + f, err := Assets.Open(assetPath) + if os.IsNotExist(err) { + return Assets.Open("build/index.html") + } + + // Otherwise assume this is a legitimate request routed + // correctly + return f, err + }) + + return http.StripPrefix(prefix, http.FileServer(http.FS(handler))) +} diff --git a/web/build/index.html b/web/build/index.html new file mode 100644 index 0000000..8abc4fc --- /dev/null +++ b/web/build/index.html @@ -0,0 +1,9 @@ + + + + Test + + + Testing ... + + \ No newline at end of file