How to make a path parameter optional in Go Echo Routes? [duplicate] - go

I want to have an optional URL variable in route. I can't seem to find a way using mux package. Here's my current route:
func main() {
r := mux.NewRouter()
r.HandleFunc("/view/{id:[0-9]+}", MakeHandler(ViewHandler))
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
It works when the url is localhost:8080/view/1. I want it to accept even if there's no id so that if I enter localhost:8080/view it'll still work. Thoughts?

Register the handler a second time with the path you want:
r.HandleFunc("/view", MakeHandler(ViewHandler))
Just make sure when you are getting your vars that you check for this case:
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
// directory listing
return
}
// specific view

You could define a new HandleFunc for the root /view path:
r.HandleFunc("/view", MakeHandler(RootHandler))
And have the RootHandler function do whatever you require for that path.

Related

Golang Static folder path returns all files

i have a this code to open static folder
fs := http.FileServer(http.Dir("./static/"))
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs))
but when i'm going to /static path it returns a list of files in this folder
eg
license.txt
logo.png
but i want to return a blank page
You can add blank index.html in directory ./static/, it will be rendered as blank page.
Something like this
<html>
<head><title>Nothing here</title></head>
<body><h1>Nothing here</h1></body>
</html>
vodolaz095's answer is the simplest, but if you really must do it in Go, you could wrap the FileServer to catch the specific case of a request for the root:
func noIndex(fs http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "" || strings.HasSuffix(r.URL.Path, "/") {
w.Write("")
return
}
fs.ServeHTTP(w, r)
}
}
Note that this doesn't catch requests for subdirectories without a trailing slash in the URL; to trap this you'd need a much more detailed custom implementation that was filesystem-aware to know what is a directory and what is a file.
It looks like you are using gorilla/mux - but if you don't mind using the standard library's http.ServeMux you can achieve this in Go code like so:
func main() {
fs := http.FileServer(http.Dir("./static/"))
h := http.NewServeMux()
blank := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})
h.Handle("/static/", http.StripPrefix("/static/", fs)) // will match "/static" with a redirect ...
h.Handle("/static", blank) // ... unless you create a hanlder explicitly
http.ListenAndServe(":8080", h)
}
from http.ServeMux docs:
...if a subtree has been registered and a request is received naming
the subtree root without its trailing slash, ServeMux redirects that
request to the subtree root (adding the trailing slash). This behavior
can be overridden with a separate registration for the path without
the trailing slash. For example, registering "/images/" causes
ServeMux to redirect a request for "/images" to "/images/", unless
"/images" has been registered separately.

How can I use the new context package from Go like I would use gorilla context library?

Go has come with a new package called context and with recent versions (Go 1.7 I think) we should be able to use it in the same way as gorilla/context package:
http://www.gorillatoolkit.org/pkg/context
With gorilla context you can very easily set and get variables that are relevant to a request, it's handlers, and middlewares.
To set a value in gorilla context is really easy:
func handleFunc(w http.ResponseWriter, r *http.Request) {
context.Set(r, "foo", "bar")
}
To get the value we can do:
func handleFunc(w http.ResponseWriter, r *http.Request) {
val := context.Get(r, "foo")
}
I understand that we can use this in middlewares so that the next middleware can use variables that were set in previous middleware. I would like to be able to do this with Go context package.
I understand that to get a value is quite simple like this:
func handleFunc(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.Context().Value("foo"))
}
But I have no idea how to set the value. It was not very intuitive for me and I don't really understand how to do it.
See "Exploring the context package", using WithValue and the context associated to the Request:
Middleware
Middleware in Go refers to an http handler which wraps around a multiplexer. There are several 3rd party middleware solutions (such as negroni), but really the standard library supports a very similar pattern. The use of a Context in the request allows us to hold data in the request.
See the example code for invocation and definition.
func putClientIPIntoContext(r *http.Request) context.Context {
ci := r.RemoteAddr
fwd := r.Header.Get("X-Forwarded-For")
if fwd != "" {
ci = fwd
}
ctx := context.WithValue(r.Context(), ClientIPKey, ci)
return ctx
}
The Context can store request-scoped variables.
It’s useful when writing ‘middleware’, but it’s a little bit ‘anti-pattern’ — it’s a bit magical, because it’s not type-safe.
See more at "Pitfalls of context values and how to avoid or mitigate them in Go".
The example below only shows how you might use the authentication logic from above to verify that when a user is logged in when visiting any page with a path prefix of /dashboard/.
A similar approach could be used to verify that a user is an admin before allowing them access to any page with a path prefix of /admin/.
func requireUser(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := lookupUser(r)
if user == nil {
// No user so redirect to login
http.Redirect(w, r, "/login", http.StatusFound)
return
}
ctx := context.WithValue(r.Context(), "user", user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func main() {
dashboard := http.NewServeMux()
dashboard.HandleFunc("/dashboard/hi", printHi)
dashboard.HandleFunc("/dashboard/bye", printBye)
mux := http.NewServeMux()
// ALL routes that start with /dashboard/ require that a
// user is authenticated using the requireUser middleware
mux.Handle("/dashboard/", requireUser(dashboard))
mux.HandleFunc("/", home)
http.ListenAndServe(":3000", addRequestID(mux))
}
As kostix comments, use Context wisely, like Dave Cheney suggest in "Context is for cancelation"

How to make templates work with gin framework?

I am newbie to golang.
To learn it I have started with a simple web app using gin framework.
I have followed the gin doc & configured template file but not able to make it work. I am getting an error -
panic: html/template: pattern matches no files: `templates/*`
goroutine 1 [running]:
html/template.Must
/usr/local/Cellar/go/1.5.2/libexec/src/html/template/template.go:330
github.com/gin-gonic/gin.(*Engine).LoadHTMLGlob
/Users/ameypatil/deployment/go/src/github.com/gin-gonic/gin/gin.go:126
main.main()
/Users/ameypatil/deployment/go/src/github.com/ameykpatil/gospike/main.go:17
Below is my code -
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
//os.Setenv("GIN_MODE", "release")
//gin.SetMode(gin.ReleaseMode)
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
router.LoadHTMLGlob("templates/*")
//router.LoadHTMLFiles("templates/index.tmpl")
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "GoSpike",
})
})
// By default it serves on :8080 unless a
// PORT environment variable was defined.
router.Run(":4848")
}
My directory structure is
- gospike
--- templates
------index.tmpl
--- main.go
go install command does not give any error
but on actually running, it gives the above error. I searched & there were similar issues logged on gin's github repo but they are closed now.
I have tried various things but I guess I am missing something obvious. What am I missing?
I'm guessing the issue is that you're using a relative filepath to access your templates.
If I compile and run your code from the gospike directory, it works fine. But if I run gospike from any other directory, I get the same error you were seeing.
So either you need to always run gospike in the parent directory of templates, or you need to use the absolute path. You could either hard code it:
router.LoadHTMLGlob("/go/src/github.com/ameykpatil/gospike/templates/*")
or you could do something like
router.LoadHTMLGlob(filepath.Join(os.Getenv("GOPATH"),
"src/github.com/ameykpatil/gospike/templates/*"))
but that will fail if you have multiple paths set in your GOPATH. A better long-term solution might be setting a special environment variable like TMPL_DIR, and then just using that:
router.LoadHTMLGlob(filepath.Join(os.Getenv("TMPL_DIR"), "*"))
use relative path glob will working, you can try to code
router.LoadHTMLGlob("./templates/*")
notice the . dot sign which meaning current directory, gin.Engine will load template
base on templates subdirectory of current directory .
Here is how I do it. This walks through the directory and collects the files marked with my template suffix which is .html & then I just include all of those. I haven't seen this answer anywhere so I thought Id post it.
// START UP THE ROUTER
router := gin.Default()
var files []string
filepath.Walk("./views", func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".html") {
files = append(files, path)
}
return nil
})
router.LoadHTMLFiles(files...)
// SERVE STATICS
router.Use(static.Serve("/css", static.LocalFile("./css", true)))
router.Use(static.Serve("/js", static.LocalFile("./js", true)))
router.Use(static.Serve("/images", static.LocalFile("./images", true)))
routers.LoadBaseRoutes(router)
routers.LoadBlog(router)
router.Run(":8080")
There is a multitemplate HTML render to support multi tempaltes.
You can use AddFromFiles to add multi files:
r.AddFromFiles("index", "templates/base.html", "templates/index.html")
r.AddFromFiles("article", "templates/base.html", "templates/index.html", "templates/article.html")
Or load multi files under a directory:
package main
import (
"path/filepath"
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.HTMLRender = loadTemplates()
// ...
}
func loadTemplates() multitemplate.Render {
files, err := filepath.Glob("template/*.tmpl")
if err != nil {
panic("failed to load html templates: " + err.Error())
}
r := multitemplate.New()
// Generate our templates map from our template/ directories
for _, file := range files {
r.AddFromFiles(filepath.Base(file), file)
}
// add other html templates directly
r.Add("test.tmpl", someTemplate)
return r
}
You could see more APIs in this repo you want, hope this post help :)

How to create a common function for all requests in Go?

I am writing a web application with Go to get better with it. My use case is pretty simple. I want to have a common function that will be executed for every request and will generate the navigation bar depending on the user status.
init method looks like (will also give you the idea of my implementation of handler methods):
func init() {
initDB()
gob.Register(user.User{})
r := mux.NewRouter()
r.HandleFunc("/", handleHome)
http.Handle("/", r)
}
I am using the following method to execute templates.
func executeTemplate(w http.ResponseWriter, name string, status int, data map[string]interface{}) error {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(status)
data["User"] = getUser(r)
return tpls[name].ExecuteTemplate(w, "base", data)
}
I am using Gorilla toolkit to store the session but as of my understanding, I need the http.Request instance every time to access the cookie store. Now I don't want to change the signature of executeTemplate method. Is there any way I can add a function to generate the navigation bar without changing signature of any of the existing methods?
What are some good ways to do it (even with changing the existing methods)?
Basic common approach to create middleware in Gorillatoolkit is to wrap top-level mux. Something like
func Middleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//You do something here using request
h.ServeHTTP(w, r)
})
}
And then
r := mux.NewRouter()
r.HandleFunc("/", handleHome)
http.Handle("/", Middleware(r))

How to create a route with optional url var using gorilla mux?

I want to have an optional URL variable in route. I can't seem to find a way using mux package. Here's my current route:
func main() {
r := mux.NewRouter()
r.HandleFunc("/view/{id:[0-9]+}", MakeHandler(ViewHandler))
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
It works when the url is localhost:8080/view/1. I want it to accept even if there's no id so that if I enter localhost:8080/view it'll still work. Thoughts?
Register the handler a second time with the path you want:
r.HandleFunc("/view", MakeHandler(ViewHandler))
Just make sure when you are getting your vars that you check for this case:
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
// directory listing
return
}
// specific view
You could define a new HandleFunc for the root /view path:
r.HandleFunc("/view", MakeHandler(RootHandler))
And have the RootHandler function do whatever you require for that path.

Resources