swagger.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. // Copyright 2020 gorse Project Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package server
  15. import (
  16. "fmt"
  17. "github.com/zhenghaoz/gorse/base/log"
  18. "net/http"
  19. "net/url"
  20. "os"
  21. "path/filepath"
  22. "strconv"
  23. "strings"
  24. "go.uber.org/zap"
  25. "github.com/haxii/go-swagger-ui/static"
  26. jsoniter "github.com/json-iterator/go"
  27. "gopkg.in/yaml.v2"
  28. )
  29. const (
  30. querySwaggerURLKey string = "url"
  31. querySwaggerFileKey string = "file"
  32. querySwaggerHost string = "host"
  33. localSwaggerDir string = "/swagger"
  34. apiDocsPath string = "/apidocs/"
  35. )
  36. var swaggerFile string
  37. func serveLocalFile(localFilePath string, w http.ResponseWriter, r *http.Request) {
  38. newHost := r.URL.Query().Get("host")
  39. if newHost == "" {
  40. http.ServeFile(w, r, localFilePath)
  41. return
  42. }
  43. isJSON := false
  44. switch filepath.Ext(localFilePath) {
  45. case ".json":
  46. isJSON = true
  47. case ".yml", ".yaml":
  48. isJSON = false
  49. default:
  50. http.Error(w, "unknown swagger file: "+localFilePath, http.StatusBadRequest)
  51. return
  52. }
  53. // open file
  54. file, err := os.Open(localFilePath)
  55. if err != nil {
  56. if os.IsNotExist(err) {
  57. http.Error(w, "file not exists", http.StatusNotFound)
  58. return
  59. }
  60. http.Error(w, err.Error(), http.StatusInternalServerError)
  61. return
  62. }
  63. defer file.Close()
  64. swg := new(map[string]interface{})
  65. if isJSON {
  66. dec := jsoniter.NewDecoder(file)
  67. err = dec.Decode(swg)
  68. } else {
  69. dec := yaml.NewDecoder(file)
  70. err = dec.Decode(swg)
  71. }
  72. if err != nil {
  73. http.Error(w, err.Error(), http.StatusInternalServerError)
  74. return
  75. }
  76. (*swg)["host"] = newHost
  77. var resp []byte
  78. if isJSON {
  79. resp, err = jsoniter.Marshal(swg)
  80. } else {
  81. resp, err = yaml.Marshal(swg)
  82. }
  83. if err != nil {
  84. http.Error(w, err.Error(), http.StatusInternalServerError)
  85. return
  86. }
  87. w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
  88. w.Header().Set("Pragma", "no-cache")
  89. if _, err = w.Write(resp); err != nil {
  90. log.Logger().Error("failed to write response", zap.Error(err))
  91. }
  92. }
  93. func handler(w http.ResponseWriter, r *http.Request) {
  94. source := r.URL.Path[len(apiDocsPath):]
  95. if source == "" {
  96. source = "index.html"
  97. }
  98. // serve the local file
  99. localFile := ""
  100. if strings.HasPrefix(source, "swagger/") {
  101. // we treat path started with swagger as a direct request of a local swagger file
  102. localFile = filepath.Join(localSwaggerDir, source[len("swagger/"):])
  103. }
  104. if len(localFile) > 0 {
  105. serveLocalFile(localFile, w, r)
  106. return
  107. }
  108. // server the swagger UI
  109. //
  110. // find the in-memory static files
  111. staticFile, exists := static.Files[source]
  112. if !exists {
  113. w.WriteHeader(http.StatusNotFound)
  114. return
  115. }
  116. // set up the content type
  117. switch filepath.Ext(source) {
  118. case ".html":
  119. w.Header().Set("Content-Type", "text/html")
  120. case ".js":
  121. w.Header().Set("Content-Type", "application/javascript")
  122. case ".css":
  123. w.Header().Set("Content-Type", "text/css")
  124. default:
  125. w.Header().Set("Content-Type", "application/octet-stream")
  126. }
  127. // return back the non-index files
  128. if source != "index.html" {
  129. w.Header().Set("Content-Length", strconv.Itoa(len(staticFile)))
  130. if _, err := w.Write(staticFile); err != nil {
  131. log.Logger().Error("failed to write file", zap.Error(err))
  132. }
  133. return
  134. }
  135. // set up the index page
  136. targetSwagger := swaggerFile
  137. if f := r.URL.Query().Get(querySwaggerFileKey); len(f) > 0 {
  138. // requesting a local file, join it with a `swagger/` prefix
  139. base, err := url.Parse("swagger/")
  140. if err != nil {
  141. return
  142. }
  143. target, err := url.Parse(f)
  144. if err != nil {
  145. return
  146. }
  147. targetSwagger = base.ResolveReference(target).String()
  148. if h := r.URL.Query().Get(querySwaggerHost); len(h) > 0 {
  149. targetSwagger += "?host=" + h
  150. }
  151. } else if _url := r.URL.Query().Get(querySwaggerURLKey); len(_url) > 0 {
  152. // deal with the query swagger firstly
  153. targetSwagger = _url
  154. }
  155. // replace the target swagger file in index
  156. indexHTML := string(staticFile)
  157. indexHTML = strings.ReplaceAll(indexHTML, "https://petstore.swagger.io/v2/swagger.json", targetSwagger)
  158. w.Header().Set("Content-Length", strconv.Itoa(len(indexHTML)))
  159. if _, err := fmt.Fprint(w, indexHTML); err != nil {
  160. log.Logger().Error("failed to print HTML", zap.Error(err))
  161. }
  162. }