53 lines
2.3 KiB
Go
53 lines
2.3 KiB
Go
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)
|
|
}
|