How to use swaggo (swagger doc) in GoLang lang with http.ServeMux? - go

In documentation https://github.com/swaggo/swag is using gin to initialize server, but in my application i'm using http.ServeMux and how initialize swaggo without use gin server
in Docs use
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
how can I use something like ...
mu.Handle("/swagger/*any", swaggerFiles.Handler)
......
follows as my initial idea, but don't work... rsrs
func Server() *http.ServeMux {
docs.SwaggerInfo.Title = "Swagger Example API"
docs.SwaggerInfo.Description = "This is a sample server Petstore server."
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.Host = "petstore.swagger.io"
mu := http.NewServeMux()
mu.Handle("/metrics", promhttp.Handler())
mu.Handle("/swagger/*any", swaggerFiles.Handler)
mu.HandleFunc("/helloWorld", handlers.NewHelloWorldHandler().HelloWorldHandler)
mu.HandleFunc("/production/", handlers.NewProductionHandler().ProductionHandler)
return mu
}

If you have your swagger files built for distribution (i.e. static files) and are in say the directory: /some/dir/path/static/swagger
This should work with go's http router:
staticFilesPath := "/some/dir/path/static"
staticRoute := "/static/"
h := http.NewServeMux()
// static file handler for route '/static/*'
h.Handle(
staticRoute,
http.StripPrefix(
staticRoute,
http.FileServer(http.Dir(staticFilesPath)),
),
)
I find it helpful to add this also:
// (redirect a route if not recognized - remove in production)
//
// unrecognized routes will redirect to Swagger API docs
h.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, staticRoute + "swagger/", http.StatusSeeOther)
})

Related

How to extract path from user request in golang grpc-gateway

i have a question. Is it possible to extract via metadata path from user request.
Here i have my proto file with defined method.
rpc AllPath(google.protobuf.Empty) returns (google.protobuf.Empty) {
option (google.api.http) = {
get: "/*",
};
}
rpc Auth(google.protobuf.Empty) returns (TokenRender) {
option (google.api.http) = {
get: "/auth"
};
}
}
In AllPath function in my server file im using something like this, found on grpc-gateway ecosystem website.
path := make(map[string]string)
if pattern, ok := runtime.HTTPPathPattern(ctx); ok {
path["pattern"] = pattern // /v1/example/login
}
fmt.Printf("Current path is: %v", path["pattern"])
but my current pattern/path is like i defined in proto file: Current path is: /*
If anyone have idea how to deal with this thing i would appreciate it :)
Best, Kacper
gRPC-Gateway passes various bits of information from the originating HTTP request via gRPC metadata. I don't believe the raw path is supplied, however. It is still possible to get the path passed through by registering a metadata annotator.
When calling github.com/grpc-ecosystem/grpc-gateway/v2/runtime.NewServeMux(), leverage the WithMetadata option func:
mux := runtime.NewServeMux(runtime.WithMetadata(func(_ context.Context, req *http.Request) metadata.MD {
return metadata.New(map[string]string{
"grpcgateway-http-path": req.URL.Path,
})
}))
Then in your gRPC service implementation, you can retrieve the value via the incoming context:
func (s *server) AllPath(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
md, _ := metadata.FromIncomingContext(ctx)
log.Printf("path: %s", md["grpcgateway-http-path"][0])
return &emptypb.Empty{}, nil
}
When hitting, e.g. /foo, this should log:
2022/10/25 15:31:42 path: /foo

Why Google Logging client libraries not logging inside Google cloud functions?

I'm trying to implement a google cloud function to test Google Logging client library. below is my code
// Package p contains an HTTP Cloud Function.
package loggingclient
import (
"cloud.google.com/go/logging"
"net/http"
"context"
"fmt"
)
// HelloWorld prints the JSON encoded "message" field in the body
// of the request or "Hello, World!" if there isn't one.
func HelloWorld(w http.ResponseWriter, r *http.Request) {
label := map[string]string{"priority": "High"}
var projectName = "my-project-id"
ctx := context.Background()
client, err := logging.NewClient(ctx, projectName)
if err != nil {
fmt.Printf("client not created: %v", err)
}
lg := client.Logger("MY-LOGGER")
lg.Log(logging.Entry{
Payload: "Hello, This is error!!",
Severity: logging.Error,
Labels: label,
})
client.Close()
}
Here, I'm expecting a log entry with a message:"Hello, This is error!!" and with a lable:"priority": "High" and severirty "ERROR"
But actually, when I trigger this Cloud Function, I didn't get any new log entries. Therefore don't client logging libraries work inside cloud functions?, How to resolve this?
Thanks
It works on cloud functions. I have done the exact same thing in a cloud function before. You can use google's official documenation with cloud function logging here
Also ensure that the service account have necessary permissions for logging
https://cloud.google.com/logging/docs/access-control

go Unable to get login user information

Hello. I'm using the go language in Google App Engine. I'm having trouble getting the logged-in user's information. Similarly, the login URL and logout URL cannot be obtained. All nil will be returned. user.IsAdmin (c) returns false. please help me.
admin.go
func Entry(w http.ResponseWriter, r *http.Request) {
...
c := appengine.NewContext(r)
inUrl, err := user.LoginURL(c, "/admin/top/")
...
}
func AdminTop(w http.ResponseWriter, r *http.Request) {
...
c := appengine.NewContext(r)
booisadmin := user.IsAdmin(c)
u := user.Current(c)
outUrl, err := user.LogoutURL(c, "/")
...
}
app.yaml
runtime: go116
app_engine_apis: true
handlers:
- url: /assets/css
mime_type: text/css
static_dir: assets/css
- url: /assets/html
mime_type: text/html
static_dir: assets/html
- url: /assets/img
static_dir: assets/img
- url: /admin/.*
login: require
script: _go_app
- url: /.*
script: _go_app
When you use login: required in app.yaml, you can get the logged in users information via the following headers -
X-Appengine-User-Id
X-Appengine-User-Nickname
X-Appengine-User-Email
I confirmed the above works in Go (ran it on my local machine)
I believe the same headers should work when you use the Users API but you can always dump all the headers to figure out the values that you need.
Regarding using the User API to get login/logout urls, I also got blank values when I tried it on my local machine but I'm a novice when it comes to Go. You might want to try and see if the calls work when you deploy to Production
Thank you. I was able to solve it. It was because I was using Gorillamux. I solved it with the following code.
import (
...
"github.com/gorilla/mux"
"net/http"
...
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", indexHandler)
r.HandleFunc("/admin/", admin.Entry)
http.Handle("/", r)
}
The last http.Handle ("/", r) was missing.
I wrote the details here.
https://uubaago.blogspot.com/
Thank you very much NoCommandLine!

How to end to end/integration test a Go app that use a reverse proxy to manage subdomain?

I have a Go app that use Gin gonic and a Nginx reverse proxy that send trafic to another app on domain.com and send all the *.domain.com subdomains traffic directly to my go app.
My Go app then has a middleware that will read the hostname that nginx passes to it from Context and allow my handlers to know what subdomain is being request and return the proper data and cookies for said subdomain.
It's a pretty simple setup and it seems to work fine from my test in postman as all my routes are the same across all my subdomains so this way i can only use one router for all of them instead of one router per subodmain.
Now my big problem come when i'm trying to do end to end testing.
I'm setting up my test like this :
router := initRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/login", bytes.NewBuffer(jsonLogin))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
with initRouter() returning a gin engine with all my routes and middlewares loaded and the rest as a basic test setup.
Obviously the test will fail as the gin Context won't ever receive a subdomain from context and act as if everything is coming from localhost:8000.
Is there a way to either :
"Mock" a subdomain so that the router think the call is coming from foo.localhost.com instead of localhost
Setup my test suit so that the test request are routed thought nginx.. i'd prefer solution 1 as this would be a mess to setup / maintain.
Edit :
As per the httptest doc i've tried to hard code foo.localhost as the param of the NewRequest but it doesn't really behave as i need it to behave :
NewRequest returns a new incoming server Request, suitable for passing to an http.Handler for testing.
The target is the RFC 7230 "request-target": it may be either a path or an absolute URL. If target is an absolute URL, the host name from the URL is used. Otherwise, "example.com" is used.
When hardcoding http://foo.localhost.com/api/login or foo.localhost.com/api/login as the request target it directly passes it to my router under "foo.localhost.com/api/login" while nginx would just hit the /api/login directly and parse from c.Request.Host
Edit 2:
I'm currently exploring setting the host manually using :
req.Header.Set("Host", "foo.localhost")
The request returned by http.NewRequest isn't suitable for passing directly to ServeHTTP. Use one returned by httptest.NewRequest instead.
Simply set the Host field directly:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHelloWorld(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Host != "foobar" {
t.Errorf("Host is %q, want foobar", r.Host)
}
})
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/login", nil)
r.Host = "foobar"
mux.ServeHTTP(w, r)
}

golang static stop index.html redirection

package main
import (
"log"
"net/http"
)
func main() {
fs := http.FileServer(http.Dir("."))
http.Handle("/", fs)
log.Println("Listening...")
http.ListenAndServe(":3000", nil)
}
So I have a index.html file and want server to stop showing it.
The docs for FileServer state that:
As a special case, the returned file server redirects any request
ending in "/index.html" to the same path, without the final
"index.html".
So /index.html is redirected to /, /foo/bar/index.html is redirected to /foo/bar/.
To avoid this register an additional handler for the special case.
http.HandleFunc("/index.html", func(w http.ResponseWriter, r *http.Request) {
f, err := os.Open("index.html")
if err != nil {
// handle error
return
}
http.ServeContent(w, r, "index.html", time.Now(), f)
})
Please note I'm using ServeContent insead of ServeFile because ServeFile handles /index.html requests the same way as FileServer.
There's no redirection going on, the default file to render when requesting a directory is index.html. The directory listing is a fallback for when this file isn't found, so you can't get a directory listing without removing the index.html file.
If you want a directory listing, you'll have to write it out yourself, which you can then format and style however you choose. The basic structure is very simple if you want to write it directly, take the internal dirList function for example:
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<pre>\n")
for _, d := range dirs {
name := d.Name()
if d.IsDir() {
name += "/"
}
url := url.URL{Path: name}
fmt.Fprintf(w, "%s\n", url.String(), htmlReplacer.Replace(name))
}
fmt.Fprintf(w, "</pre>\n")
I think there are 3 ways:
Make a PR which allow accessing /index.html without redirect to golang's net/http/fs library. https://github.com/golang/go/issues/53870
Hack your code: replace /index.html with / to stop 301 redirect, e.g., https://github.com/ahuigo/go-lib/commit/1a99191d5c01cf9025136ce8ddb9668f123de05c#diff-67e8621fbb99281a50c089bae53d4874663d3d21ca5e90809ec207c070029351R44
Customize your own http fs handler instead of official tools.

Resources