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
- swagger-ui.css
- swagger-ui-bundle.js
- 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.