Tooling
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package studio
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Sharing the studio's files with another tool on the same machine.
|
||||
//
|
||||
// World Orogen (Tools/Orogen, D-66) reads the same painting and the same legends this studio edits, and until
|
||||
// now the only way to get them there was a file picker: choose the PNG, choose the legend, choose the overlay,
|
||||
// choose its legend, retype the manifest's numbers. The studio already holds every one of those - the painting
|
||||
// in memory, exactly as the next plan will read it - so it serves them, and Orogen loads a planet with one
|
||||
// button.
|
||||
//
|
||||
// Two rules keep this from turning the studio into something a web page can drive:
|
||||
//
|
||||
// - **Only GET is shared.** The CORS header goes on GET responses and nothing else, and no preflight is ever
|
||||
// answered. A cross-origin POST with a JSON body needs a preflight, so every endpoint that paints, saves,
|
||||
// plans or bakes stays reachable from this page and from nothing else. The studio listens on loopback, but
|
||||
// a browser on the same machine visits other origins all day, and "any tab can start a two-hour bake" is
|
||||
// not a property to give away for a convenience.
|
||||
// - **The files are served as they are on disk.** The legend and the manifest are text somebody wrote, with
|
||||
// commentary; Orogen reads the same keys the plan does and ignores the rest. Nothing is re-marshalled, so
|
||||
// what Orogen sees is byte for byte what `terrain plan` will see.
|
||||
|
||||
// readOnlyCORS lets any origin *read* the API and touches nothing else.
|
||||
func readOnlyCORS(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet || r.Method == http.MethodHead {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// serveJSONFile answers a GET for one of the planet's JSON files, or says there is none. The POST handlers
|
||||
// call it first and return, so an endpoint that edits a file also hands the file out.
|
||||
func serveJSONFile(w http.ResponseWriter, path string) {
|
||||
if path == "" {
|
||||
http.Error(w, "the manifest names no such file", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
Reference in New Issue
Block a user