From 564f041305f04faddb78235bdccefe2f3108a0fc Mon Sep 17 00:00:00 2001 From: Joel Smith Date: Fri, 12 Jan 2024 17:01:27 -0600 Subject: [PATCH] Code Cleanup & Refactoring --- app/app.go | 3 +- app/webhost/_scripts.sh | 2 +- app/webhost/console.go | 150 +++++++++++++++++++++++++++++-------- app/webhost/file_server.go | 148 ------------------------------------ 4 files changed, 119 insertions(+), 184 deletions(-) delete mode 100644 app/webhost/file_server.go diff --git a/app/app.go b/app/app.go index 1b6f793bb..cfb382846 100644 --- a/app/app.go +++ b/app/app.go @@ -595,8 +595,7 @@ func (app *App) RegisterAPIRoutes(apiSvr *api.Server, _ config.APIConfig) { // register app's OpenAPI routes. apiSvr.Router.Handle("/static/openapi.yml", http.FileServer(http.FS(docs.Docs))) apiSvr.Router.HandleFunc("/", openapiconsole.Handler(Name, "/static/openapi.yml")) - apiSvr.Router.HandleFunc("/webhost", webhost.Handler()) - apiSvr.Router.HandleFunc("/fs/{contract}/{name}/{path:.*}", webhost.Handler2()) + apiSvr.Router.HandleFunc("/webhost/{contract}/{name}/{path:.*}", webhost.Handler()) } // RegisterTxService implements the Application.RegisterTxService method. diff --git a/app/webhost/_scripts.sh b/app/webhost/_scripts.sh index 0f245c227..e821a7527 100644 --- a/app/webhost/_scripts.sh +++ b/app/webhost/_scripts.sh @@ -19,4 +19,4 @@ sleep 3 junod q wasm contract-state smart $ADDRESS '{"get_website":{"name":"test"}}' --home=/home/joel/.juno1 -# Website should be available at: http://localhost:1317/fs/juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8/test/ \ No newline at end of file +# Website should be available at: http://localhost:1317/webhost/juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8/test/ \ No newline at end of file diff --git a/app/webhost/console.go b/app/webhost/console.go index 0676b8920..04e38cc21 100644 --- a/app/webhost/console.go +++ b/app/webhost/console.go @@ -1,19 +1,16 @@ package webhost import ( + "archive/zip" + "bytes" "encoding/base64" "encoding/json" "fmt" "io" "net/http" - "os" "strings" ) -// TODO: -// https://github.com/rogchap/v8go ? -// Allow ZIP file upload into the contract? (easier to use images, else you need to use IPFS or direct links off chain) - const ( address = ":8787" restServer = "http://0.0.0.0:1317/cosmwasm/wasm/v1/contract/" @@ -28,53 +25,140 @@ type Website struct { func Handler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - params := r.URL.Query() - if len(params["contract"]) == 0 || len(params["name"]) == 0 { - w.WriteHeader(http.StatusBadRequest) - if _, err := w.Write([]byte("contract and name are required")); err != nil { - fmt.Println("error writing response: ", err) - } - return - } + // Get request route + route := strings.Split(r.URL.Path, "/")[2:] - contract := params["contract"][0] - name := params["name"][0] + // Pull out contract & name from route + contract := route[0] + name := route[1] - b64Query := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"get_website":{"name":"%s"}}`, name))) + // Determine file path, default to index.html if none specified + path := strings.Join(route[2:], "/") + if path == "" { + path = "index.html" + } - res, err := http.Get(restServer + contract + "/smart/" + b64Query) + // Send request to CosmWasm contract, retrieve website + website, err := makeRequest(contract, name, path) if err != nil { w.WriteHeader(http.StatusInternalServerError) - writeResponse(w, "error creating request: "+err.Error()) + writeResponse(w, "error making request: "+err.Error()) return } - defer res.Body.Close() - resBody, err := io.ReadAll(res.Body) + // Decode & unzip website source files + zr, err := decodeAndUnzip(website.Data.Source) if err != nil { - fmt.Printf("client: could not read response body: %s\n", err) - os.Exit(1) + w.WriteHeader(http.StatusInternalServerError) + writeResponse(w, "error decoding and unzipping file: "+err.Error()) + return } - if len(resBody) > 0 && strings.Contains(string(resBody), "data") { - var website Website - err = json.Unmarshal(resBody, &website) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - writeResponse(w, "error unmarshalling response body: "+err.Error()) - return + // Find file in zip at path + for _, f := range zr.File { + if f.Name == path { + rc, err := f.Open() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + writeResponse(w, "error opening file: "+err.Error()) + return + } + defer rc.Close() + + // Read file into buffer + buf := new(bytes.Buffer) + if _, err := io.Copy(buf, rc); err != nil { + w.WriteHeader(http.StatusInternalServerError) + writeResponse(w, "error reading file: "+err.Error()) + return + } + + // Set content type + setContentType(w, path) + + // Write buffer to response + if _, err := w.Write(buf.Bytes()); err != nil { + w.WriteHeader(http.StatusInternalServerError) + writeResponse(w, "error writing response: "+err.Error()) + } + + break } + } + } +} - writeResponse(w, website.Data.Source) - return +// Make request to contract through CosmWasm REST server +func makeRequest(contract, name, path string) (Website, error) { + var website Website + + b64Query := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"get_website":{"name":"%s"}}`, name))) + url := restServer + contract + "/smart/" + b64Query + + res, err := http.Get(url) + if err != nil { + return website, err + } + defer res.Body.Close() + + resBody, err := io.ReadAll(res.Body) + if err != nil { + return website, err + } + + if len(resBody) > 0 && strings.Contains(string(resBody), "data") { + if err = json.Unmarshal(resBody, &website); err != nil { + return website, err } + } + + return website, nil +} + +// Decode from base 64 and unzip file, return zip reader +func decodeAndUnzip(source string) (*zip.Reader, error) { + // Decode from base 64 + decodedBytes, err := base64.StdEncoding.DecodeString(source) + if err != nil { + return nil, err + } + + // Unzip + reader := bytes.NewReader(decodedBytes) + zr, err := zip.NewReader(reader, int64(len(decodedBytes))) + if err != nil { + return nil, err + } + + return zr, nil +} - w.WriteHeader(http.StatusNotFound) - writeResponse(w, "no website found") +// Set the content type of the response based on the file's extension +func setContentType(w http.ResponseWriter, filePath string) { + // Set content type + if strings.HasSuffix(filePath, ".html") { + w.Header().Set("Content-Type", "text/html") + } else if strings.HasSuffix(filePath, ".css") { + w.Header().Set("Content-Type", "text/css") + } else if strings.HasSuffix(filePath, ".js") { + w.Header().Set("Content-Type", "text/javascript") + } else if strings.HasSuffix(filePath, ".png") { + w.Header().Set("Content-Type", "image/png") + } else if strings.HasSuffix(filePath, ".jpg") || strings.HasSuffix(filePath, ".jpeg") { + w.Header().Set("Content-Type", "image/jpeg") + } else if strings.HasSuffix(filePath, ".gif") { + w.Header().Set("Content-Type", "image/gif") + } else if strings.HasSuffix(filePath, ".svg") { + w.Header().Set("Content-Type", "image/svg+xml") + } else if strings.HasSuffix(filePath, ".ico") { + w.Header().Set("Content-Type", "image/x-icon") + } else { + w.Header().Set("Content-Type", "text/plain") } } +// Helper function for writing response func writeResponse(w http.ResponseWriter, resBody string) { if _, err := w.Write([]byte(resBody)); err != nil { fmt.Println("error writing response: ", err) diff --git a/app/webhost/file_server.go b/app/webhost/file_server.go deleted file mode 100644 index 4800af3da..000000000 --- a/app/webhost/file_server.go +++ /dev/null @@ -1,148 +0,0 @@ -package webhost - -import ( - "archive/zip" - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" -) - -func Handler2() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - // Get route - route := strings.Split(r.URL.Path, "/")[2:] - - // Pull out contract - contract := route[0] - - // Pull out name - name := route[1] - - // Pull out path to file on contract - path := strings.Join(route[2:], "/") - if path == "" { - path = "index.html" - } - - website, err := makeRequest(contract, name, path) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - writeResponse(w, "error making request: "+err.Error()) - return - } - - // Unzip website.Data.Source - zr, err := decodeAndUnzip(website.Data.Source) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - writeResponse(w, "error decoding and unzipping file: "+err.Error()) - return - } - - // Find file in zip at path - for _, f := range zr.File { - if f.Name == path { - rc, err := f.Open() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - writeResponse(w, "error opening file: "+err.Error()) - return - } - defer rc.Close() - - // Read file into buffer - buf := new(bytes.Buffer) - if _, err := io.Copy(buf, rc); err != nil { - w.WriteHeader(http.StatusInternalServerError) - writeResponse(w, "error reading file: "+err.Error()) - return - } - - // Set content type - setContentType(w, path) - - // Write buffer to response - if _, err := w.Write(buf.Bytes()); err != nil { - w.WriteHeader(http.StatusInternalServerError) - writeResponse(w, "error writing response: "+err.Error()) - } - - break - } - } - } -} - -// Make request to contract -func makeRequest(contract, name, path string) (Website, error) { - var website Website - - b64Query := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"get_website":{"name":"%s"}}`, name))) - url := restServer + contract + "/smart/" + b64Query - - res, err := http.Get(url) - if err != nil { - return website, err - } - defer res.Body.Close() - - resBody, err := io.ReadAll(res.Body) - if err != nil { - return website, err - } - - if len(resBody) > 0 && strings.Contains(string(resBody), "data") { - if err = json.Unmarshal(resBody, &website); err != nil { - return website, err - } - } - - return website, nil -} - -// Decode from base 64 and unzip file -func decodeAndUnzip(source string) (*zip.Reader, error) { - // Decode from base 64 - decodedBytes, err := base64.StdEncoding.DecodeString(source) - if err != nil { - return nil, err - } - - // Unzip - reader := bytes.NewReader(decodedBytes) - zr, err := zip.NewReader(reader, int64(len(decodedBytes))) - if err != nil { - return nil, err - } - - return zr, nil -} - -// Function to set the header -func setContentType(w http.ResponseWriter, path string) { - // Set content type - if strings.HasSuffix(path, ".html") { - w.Header().Set("Content-Type", "text/html") - } else if strings.HasSuffix(path, ".css") { - w.Header().Set("Content-Type", "text/css") - } else if strings.HasSuffix(path, ".js") { - w.Header().Set("Content-Type", "text/javascript") - } else if strings.HasSuffix(path, ".png") { - w.Header().Set("Content-Type", "image/png") - } else if strings.HasSuffix(path, ".jpg") || strings.HasSuffix(path, ".jpeg") { - w.Header().Set("Content-Type", "image/jpeg") - } else if strings.HasSuffix(path, ".gif") { - w.Header().Set("Content-Type", "image/gif") - } else if strings.HasSuffix(path, ".svg") { - w.Header().Set("Content-Type", "image/svg+xml") - } else if strings.HasSuffix(path, ".ico") { - w.Header().Set("Content-Type", "image/x-icon") - } else { - w.Header().Set("Content-Type", "text/plain") - } -}