swaggo / http-swagger

Default net/http wrapper to automatically generate RESTful API documentation with Swagger 2.0.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Getting 404 for CSS and JS files

sandeepkangude opened this issue · comments

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

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.

Got the same issue, I will update if resolved

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

@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.

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

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

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

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)
}

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"
)

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.

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?

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)

@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 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!

Hey everyone! I had the same issue but looks like the version 2.0.1 fixes it removing the singleton (dce10a8#diff-44aa764656674984f26bf27a7bcd56f8c03ea4f6b1ee8b02c48bd7e6c6c8206fL172)

I upgraded and now it's under test.