// Copyright 2020 gorse Project Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package server import ( "fmt" "github.com/zhenghaoz/gorse/base/log" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "go.uber.org/zap" "github.com/haxii/go-swagger-ui/static" jsoniter "github.com/json-iterator/go" "gopkg.in/yaml.v2" ) const ( querySwaggerURLKey string = "url" querySwaggerFileKey string = "file" querySwaggerHost string = "host" localSwaggerDir string = "/swagger" apiDocsPath string = "/apidocs/" ) var swaggerFile string func serveLocalFile(localFilePath string, w http.ResponseWriter, r *http.Request) { newHost := r.URL.Query().Get("host") if newHost == "" { http.ServeFile(w, r, localFilePath) return } isJSON := false switch filepath.Ext(localFilePath) { case ".json": isJSON = true case ".yml", ".yaml": isJSON = false default: http.Error(w, "unknown swagger file: "+localFilePath, http.StatusBadRequest) return } // open file file, err := os.Open(localFilePath) if err != nil { if os.IsNotExist(err) { http.Error(w, "file not exists", http.StatusNotFound) return } http.Error(w, err.Error(), http.StatusInternalServerError) return } defer file.Close() swg := new(map[string]interface{}) if isJSON { dec := jsoniter.NewDecoder(file) err = dec.Decode(swg) } else { dec := yaml.NewDecoder(file) err = dec.Decode(swg) } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } (*swg)["host"] = newHost var resp []byte if isJSON { resp, err = jsoniter.Marshal(swg) } else { resp, err = yaml.Marshal(swg) } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") w.Header().Set("Pragma", "no-cache") if _, err = w.Write(resp); err != nil { log.Logger().Error("failed to write response", zap.Error(err)) } } func handler(w http.ResponseWriter, r *http.Request) { source := r.URL.Path[len(apiDocsPath):] if source == "" { source = "index.html" } // serve the local file localFile := "" if strings.HasPrefix(source, "swagger/") { // we treat path started with swagger as a direct request of a local swagger file localFile = filepath.Join(localSwaggerDir, source[len("swagger/"):]) } if len(localFile) > 0 { serveLocalFile(localFile, w, r) return } // server the swagger UI // // find the in-memory static files staticFile, exists := static.Files[source] if !exists { w.WriteHeader(http.StatusNotFound) return } // set up the content type switch filepath.Ext(source) { case ".html": w.Header().Set("Content-Type", "text/html") case ".js": w.Header().Set("Content-Type", "application/javascript") case ".css": w.Header().Set("Content-Type", "text/css") default: w.Header().Set("Content-Type", "application/octet-stream") } // return back the non-index files if source != "index.html" { w.Header().Set("Content-Length", strconv.Itoa(len(staticFile))) if _, err := w.Write(staticFile); err != nil { log.Logger().Error("failed to write file", zap.Error(err)) } return } // set up the index page targetSwagger := swaggerFile if f := r.URL.Query().Get(querySwaggerFileKey); len(f) > 0 { // requesting a local file, join it with a `swagger/` prefix base, err := url.Parse("swagger/") if err != nil { return } target, err := url.Parse(f) if err != nil { return } targetSwagger = base.ResolveReference(target).String() if h := r.URL.Query().Get(querySwaggerHost); len(h) > 0 { targetSwagger += "?host=" + h } } else if _url := r.URL.Query().Get(querySwaggerURLKey); len(_url) > 0 { // deal with the query swagger firstly targetSwagger = _url } // replace the target swagger file in index indexHTML := string(staticFile) indexHTML = strings.ReplaceAll(indexHTML, "https://petstore.swagger.io/v2/swagger.json", targetSwagger) w.Header().Set("Content-Length", strconv.Itoa(len(indexHTML))) if _, err := fmt.Fprint(w, indexHTML); err != nil { log.Logger().Error("failed to print HTML", zap.Error(err)) } }