swaggo / http-swagger

Default net/http wrapper to automatically generate RESTful API documentation with Swagger 2.0.
MIT License
437 stars 73 forks source link

Getting 404 for CSS and JS files #10

Open sandeepkangude opened 5 years ago

sandeepkangude commented 5 years ago

I am new to go lang. My go lang version: 1.11.3 I am using Gorilla mux for router.

Somehow I cannot able to integrate swaggo/swag in to my Go Web application. Please see the steps I have taken.

Step 1: Added comments to my API (main.go) source code (Refer: https://github.com/swaggo/http-swagger#canonical-example)

Step 2: Download swag and http-swagger go get github.com/swaggo/swag/cmd/swag go get -u github.com/swaggo/http-swagger

Step 3: Run swag (it will generate some files in a docs/ directory) swag init

Add the following code in my main.go import ( "github.com/swaggo/http-swagger" _"mylocation/docs" "net/http" "github.com/gorilla/mux" )

func main() { router := mux.NewRouter() router.PathPrefix("/documentation/").Handler(httpSwagger.WrapHandler) http.ListenAndServe(":8000", router) }

I can able to redirect to "/documentation" route but page is blank. And when I check network logs, following files are missing/ getting 404 1) swagger-ui.css 2) swagger-ui-bundle.js 3) swagger-ui-standalone-preset.js

I don't know what exactly happening. Please help

ubogdan commented 5 years ago

I'm not able to reproduce your issue. anyway you should try changing to router.PathPrefix("/documentation").Handler(httpSwagger.WrapHandler)

If you got things working out, please , leave a comment about how you solved it and close the issue.

chiptus commented 3 years ago

Got the same issue, I will update if resolved

chiptus commented 3 years ago

my problem was that my code was stripping the prefix from r.URL.Path, and http-swagger got the same prefix from r.RequestURI (swagger.go:71), because it wasn't stripped. then in net/webdav.go:195 it looked for this prefix in r.URL.Path which didn't exist and failed. I fixed it by not stripping the prefix in my code

ubogdan commented 3 years ago

@chiptus router.PathPrefix("/documentation/") doesn't strip the prefix. It actually instructs the router when it has a request to "/documentation" path to call the httpSwagger handler. And yes, maybe the WrapHandler may require some adjustments if you want to use a custom route.

chiptus commented 3 years ago

just added my solution for future knowledge. I'm actually using vanilla net/http without frameworks

jvierauy commented 3 years ago

Is there any solution for this problem? I had the same issue, and couldn't fix it the same way as @chiptus.

marianososto commented 3 years ago

any news on this issue ? It's happening the same to me.

fbigand commented 3 years ago

I have the exact same issue (using julienschmidt/http-router). Minimal code to reproduce

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/julienschmidt/httprouter"
    httpSwagger "github.com/swaggo/http-swagger"
)

func main() {
    router := httprouter.New()

    router.ServeFiles("/api/doc/static/*filepath", http.Dir("path/to/swagger/files"))
    router.HandlerFunc(http.MethodGet, "/api/doc/index.html", swaggerHandler)

    fmt.Println("Server on port 8080")
    log.Fatal(http.ListenAndServe(":8080", router))
}

func swaggerHandler(w http.ResponseWriter, r *http.Request) {
    swaggerFileUrl := "http://localhost:8080/api/doc/static/swagger.json"
    handler := httpSwagger.Handler(httpSwagger.URL(swaggerFileUrl))
    handler.ServeHTTP(w, r)
}
xScorp1oNx commented 3 years ago

You can do it like this:

routes := httprouter.New()

routes.GET("/doc/:any", swaggerHandler)

func swaggerHandler(res http.ResponseWriter, req *http.Request, p httprouter.Params) {
     httpSwagger.WrapHandler(res, req)
}

Do not forget import doc files:

import (
     _ "example.project/docs"
)
srPuebla commented 3 years ago

Hi,

I am getting the same issue but using echo-swagger

        echo.GET("/swagger/*", echoSwagger.WrapHandler)
    echo.GET("/test/", h.Status, enforcer.Enforce)
    echo.Start(":8080")

After more than 48 hours working, suddenly the app starts to drop 404 loading css, js that you mentioned.

Failed to load resource: the server responded with a status of 404 (Not Found)
swagger-ui-bundle.js:1 Failed to load resource: the server responded with a status of 404 (Not Found)
swagger-ui-standalone-preset.js:1 Failed to load resource: the server responded with a status of 404 (Not Found)
index.html:75 Uncaught ReferenceError: SwaggerUIBundle is not defined
    at window.onload (index.html:75)
swagger-ui.css:1 Failed to load resource: the server responded with a status of 404 (Not Found)

If i restart app, it starts to work... but i dont know why it happens.

bradleygore commented 1 year ago

Hey @ubogdan I am able to reproduce this. With the most recent swag updates allowing to generate swag docs only for targeted --tags, we split up our docs into separate directories and separate http endpoints. The wiring looks like this:

import (
        // generated swag docs per tag targeted
    swagBridge "pos-api/internal/api/swaggerdocs/bridge"
    swagHttp "pos-api/internal/api/swaggerdocs/http"
    swagPos "pos-api/internal/api/swaggerdocs/pos"
    swagProvisioner "pos-api/internal/api/swaggerdocs/provisioner"
    swagSolo "pos-api/internal/api/swaggerdocs/solo"
    swagSupport "pos-api/internal/api/swaggerdocs/support"
    swagTools "pos-api/internal/api/swaggerdocs/tools"

    // http-swagger for handler func
    httpSwagger "github.com/swaggo/http-swagger"

    // ... other imports
)

var (
    // config options for display: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/#display
    swagUiConfig = httpSwagger.UIConfig(map[string]string{
        "displayOperationId":    "true",
        "defaultModelRendering": `"model"`,
        "filter":          "true",
        "showExtensions":  "true",
        "syntaxHighlight": `{"active":"true","theme":"obsidian"}`,
        "tagSorter":       `"alpha"`,
    })
    ginHandlers = map[string]gin.HandlerFunc{}
)

func setupSwaggerRoutes(r *gin.RouterGroup) {
    r.GET("/swagger/bridge/*any", func(ctx *gin.Context) { swagSpecHandler(swagBridge.SwaggerInfobridge, ctx) })
    r.GET("/swagger/http/*any", func(ctx *gin.Context) { swagSpecHandler(swagHttp.SwaggerInfohttp, ctx) })
    r.GET("/swagger/pos/*any", func(ctx *gin.Context) { swagSpecHandler(swagPos.SwaggerInfopos, ctx) })
    r.GET("/swagger/provisioner/*any", func(ctx *gin.Context) { swagSpecHandler(swagProvisioner.SwaggerInfoprovisioner, ctx) })
    r.GET("/swagger/solo/*any", func(ctx *gin.Context) { swagSpecHandler(swagSolo.SwaggerInfosolo, ctx) })
    r.GET("/swagger/support/*any", func(ctx *gin.Context) { swagSpecHandler(swagSupport.SwaggerInfosupport, ctx) })
    r.GET("/swagger/tools/*any", func(ctx *gin.Context) { swagSpecHandler(swagTools.SwaggerInfotools, ctx) })
}

func swagSpecHandler(spec *swag.Spec, ctx *gin.Context) {
    instName := spec.InstanceName() //custom instances like /bridge, /pos, etc...
    if _, exists := ginHandlers[instName]; !exists {
        cfg := middleware.HTTPRetrieveAPIConfig(ctx)
        spec.Host = cfg.Host
        ginHandlers[instName] = gin.WrapH(httpSwagger.Handler(
            // note: have to have _both_ the InstanceName and the URL to the `doc.json` setup properly.
            // See also: https://github.com/swaggo/http-swagger/blob/6ee8ad96a7f258e1531d470d9e220ee7beee7a16/swagger.go#L178
            httpSwagger.InstanceName(instName),
            httpSwagger.URL(fmt.Sprintf("/api/swagger/%s/doc.json", instName)),
            httpSwagger.DocExpansion("none"),
            swagUiConfig,
        ))
    }
    ginHandlers[instName](ctx)
}

The intent was to only create each handler once, for efficiency. However, the swaggerFiles package seems to have a singleton in place. The swag-http handler tries to set the Prefix of that handler in a sync.Once func:

handler := swaggerFiles.Handler
once.Do(func() {
    handler.Prefix = matches[1]
})

However, if we try to have separate swag doc routes, this singleton poses a problem. As I switch through the routes in the browser, the first run of each endpoint always works. However, once the sync.Once for each http-swagger Handler has run, there is no more setting of the swaggerFiles.Prefix - so whatever the last-loaded Prefix is seems to be the one used.

What I'm going to have to do is create the handler anew for each request, which seems terribly inefficient. I don't see another way around this though.

Maybe I'm missing something or there is a better way to structure this?

umurpza commented 1 year ago

Hi,

I am getting the same issue but using echo-swagger

        echo.GET("/swagger/*", echoSwagger.WrapHandler)
  echo.GET("/test/", h.Status, enforcer.Enforce)
  echo.Start(":8080")

After more than 48 hours working, suddenly the app starts to drop 404 loading css, js that you mentioned.

Failed to load resource: the server responded with a status of 404 (Not Found)
swagger-ui-bundle.js:1 Failed to load resource: the server responded with a status of 404 (Not Found)
swagger-ui-standalone-preset.js:1 Failed to load resource: the server responded with a status of 404 (Not Found)
index.html:75 Uncaught ReferenceError: SwaggerUIBundle is not defined
    at window.onload (index.html:75)
swagger-ui.css:1 Failed to load resource: the server responded with a status of 404 (Not Found)

If i restart app, it starts to work... but i dont know why it happens.

Did you find a solution for this? I'm using Gin and having the same issue you described. After a certain amount of time, I get 404's on swagger files. Much less than 48 hours at times.

Errors: Failed to load resource: the server responded with a status of 404 (Not Found) swagger-ui-standalone-preset.js:1 Failed to load resource: the server responded with a status of 404 (Not Found) swagger-ui-bundle.js:1 Failed to load resource: the server responded with a status of 404 (Not Found) favicon-32x32.png:1 Failed to load resource: the server responded with a status of 404 (Not Found) favicon-16x16.png:1 Failed to load resource: the server responded with a status of 404 (Not Found) index.html:75 Uncaught ReferenceError: SwaggerUIBundle is not defined at window.onload (index.html:75:14) swagger-ui.css:1 Failed to load resource: the server responded with a status of 404 (Not Found)

bradleygore commented 1 year ago

@umurpza this is the solution I came up with, and it has been working fine. It's not ideal because I have to make a new handler internally for each request because of how httpSwagger.Handler works, but it at least got me going.

import (
    "fmt"
    swagGroupA "path/to/swaggerdocs/a" // built targeting tag a
    swagGroupB "path/to/swaggerdocs/b" // built targeting tag b
    swagGroupC "path/to/swaggerdocs/c" // built targeting tag c
    httpSwagger "github.com/swaggo/http-swagger"
    "github.com/swaggo/swag"
    "github.com/gin-gonic/gin"
)

var (
    // config options for display: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/#display
    swagUiConfig = httpSwagger.UIConfig(map[string]string{
        // this map goes into the client-side as raw json, but apparently not using json.Marshal,
        // so "true" -> true, `"model"` -> "model", etc...
        "displayOperationId":    "true",
        "defaultModelRendering": `"model"`,
        "filter":          "true",
        "showExtensions":  "true",
        "syntaxHighlight": `{"active":"true","theme":"obsidian"}`,
        "tagSorter":       `"alpha"`,
    })
)

func setupSwaggerRoutes(r *gin.RouterGroup) {
    r.GET("/swagger/a/*any", func(ctx *gin.Context) { swagSpecHandler(swagGroupA.SwaggerInfobridge, ctx) })
    r.GET("/swagger/b/*any", func(ctx *gin.Context) { swagSpecHandler(swagGroupB.SwaggerInfohttp, ctx) })
    r.GET("/swagger/c/*any", func(ctx *gin.Context) { swagSpecHandler(swagGroupC.SwaggerInfopos, ctx) })
}
func swagSpecHandler(spec *swag.Spec, ctx *gin.Context) {
    instName := spec.InstanceName() //custom instances like /a, /b, etc...

    // Cannot cache the handlers due to an issue I worked through while debugging the http-swagger code.
    // They have a singleton in play under the hood, and their Handler only writes to that singleton in a `sync.Once`.
    // See https://github.com/swaggo/http-swagger/issues/10#issuecomment-1358163317
    // Once it is fixed, we can go back to caching the handler
    spec.Host = "https://some-domain/api"
    gin.WrapH(httpSwagger.Handler(
        // note: have to have _both_ the InstanceName and the URL to the `doc.json` setup properly.
        // See also: https://github.com/swaggo/http-swagger/blob/6ee8ad96a7f258e1531d470d9e220ee7beee7a16/swagger.go#L178
        httpSwagger.InstanceName(instName),
        httpSwagger.URL(fmt.Sprintf("/api/swagger/%s/doc.json", instName)),
        httpSwagger.DocExpansion("none"),
        swagUiConfig,
    ))(ctx)
}
umurpza commented 1 year ago

@umurpza this is the solution I came up with, and it has been working fine. It's not ideal because I have to make a new handler internally for each request because of how httpSwagger.Handler works, but it at least got me going.

import (
  "fmt"
  swagGroupA "path/to/swaggerdocs/a" // built targeting tag a
  swagGroupB "path/to/swaggerdocs/b" // built targeting tag b
  swagGroupC "path/to/swaggerdocs/c" // built targeting tag c
  httpSwagger "github.com/swaggo/http-swagger"
  "github.com/swaggo/swag"
  "github.com/gin-gonic/gin"
)

var (
  // config options for display: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/#display
  swagUiConfig = httpSwagger.UIConfig(map[string]string{
      // this map goes into the client-side as raw json, but apparently not using json.Marshal,
      // so "true" -> true, `"model"` -> "model", etc...
      "displayOperationId":    "true",
      "defaultModelRendering": `"model"`,
      "filter":          "true",
      "showExtensions":  "true",
      "syntaxHighlight": `{"active":"true","theme":"obsidian"}`,
      "tagSorter":       `"alpha"`,
  })
)

func setupSwaggerRoutes(r *gin.RouterGroup) {
  r.GET("/swagger/a/*any", func(ctx *gin.Context) { swagSpecHandler(swagGroupA.SwaggerInfobridge, ctx) })
  r.GET("/swagger/b/*any", func(ctx *gin.Context) { swagSpecHandler(swagGroupB.SwaggerInfohttp, ctx) })
  r.GET("/swagger/c/*any", func(ctx *gin.Context) { swagSpecHandler(swagGroupC.SwaggerInfopos, ctx) })
}
func swagSpecHandler(spec *swag.Spec, ctx *gin.Context) {
  instName := spec.InstanceName() //custom instances like /a, /b, etc...

  // Cannot cache the handlers due to an issue I worked through while debugging the http-swagger code.
  // They have a singleton in play under the hood, and their Handler only writes to that singleton in a `sync.Once`.
  // See https://github.com/swaggo/http-swagger/issues/10#issuecomment-1358163317
  // Once it is fixed, we can go back to caching the handler
  spec.Host = "https://some-domain/api"
  gin.WrapH(httpSwagger.Handler(
      // note: have to have _both_ the InstanceName and the URL to the `doc.json` setup properly.
      // See also: https://github.com/swaggo/http-swagger/blob/6ee8ad96a7f258e1531d470d9e220ee7beee7a16/swagger.go#L178
      httpSwagger.InstanceName(instName),
      httpSwagger.URL(fmt.Sprintf("/api/swagger/%s/doc.json", instName)),
      httpSwagger.DocExpansion("none"),
      swagUiConfig,
  ))(ctx)
}

This seems to be working for me. Thanks!

alexandredantas commented 1 year ago

Hey everyone! I had the same issue but looks like the version 2.0.1 fixes it removing the singleton (https://github.com/swaggo/http-swagger/commit/dce10a857ace56c9b2ecb27333988e3c4a559787#diff-44aa764656674984f26bf27a7bcd56f8c03ea4f6b1ee8b02c48bd7e6c6c8206fL172)

I upgraded and now it's under test.