How do I add the username of the current user, or other variables, to the end of the URL path in Go?
I tried using a http.Redirect(w, "/home/"+user, http.StatusFound), but that would create an infinite redirect loop.
Go:
func homeHandler(w http.ResponseWriter, r *http.Request) {
randIndex = rand.Intn(len(cityLibrary))
ImageDisplay()
WeatherDisplay()
dispdata = AllApiData{Images: imagesArray, Weather: &WeatherData{Temp: celsiusNum, City: cityLibrary[randIndex], RainShine: rainOrShine}}
//http.Redirect(w, "/"+cityLibrary[randIndex], http.StatusFound) --> infinite redirect loop
renderTemplate(w, "home", dispdata)
}
func main() {
http.HandleFunc("/", homeHandler)
http.Handle("/layout/", http.StripPrefix("/layout/", http.FileServer(http.Dir("layout"))))
http.ListenAndServe(":8000", nil)
}
In this code, I'm trying to append the value of "City" to the end of the root URL. Should I be using regexp?
It seems this is happening because you don't have a /{city}/ handler and the / handler is matching all the requests being redirected to cities :
Patterns name fixed, rooted paths, like "/favicon.ico", or rooted subtrees, like "/images/" (note the trailing slash). Longer patterns take precedence over shorter ones, so that if there are handlers registered for both "/images/" and "/images/thumbnails/", the latter handler will be called for paths beginning "/images/thumbnails/" and the former will receive requests for any other paths in the "/images/" subtree.
Note that since a pattern ending in a slash names a rooted subtree, the pattern "/" matches all paths not matched by other registered patterns, not just the URL with Path == "/".
What you need to do is put in the city url handler, if you need to handle RegEx patterns take a look at this or you could use a more powerful router like gorilla's mux.
what just using Go without gorilla? I know how to do it in Gorilla but I'm just wondering how its done in Go.
You make your own custom handlers with as mentioned in the previously mentioned answer :
https://stackoverflow.com/a/6565407/220710
type route struct {
pattern *regexp.Regexp
handler http.Handler
}
type RegexpHandler struct {
routes []*route
}
func (h *RegexpHandler) Handler(pattern *regexp.Regexp, handler http.Handler) {
h.routes = append(h.routes, &route{pattern, handler})
}
func (h *RegexpHandler) HandleFunc(pattern *regexp.Regexp, handler func(http.ResponseWriter, *http.Request)) {
h.routes = append(h.routes, &route{pattern, http.HandlerFunc(handler)})
}
func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, route := range h.routes {
if route.pattern.MatchString(r.URL.Path) {
route.handler.ServeHTTP(w, r)
return
}
}
// no pattern matched; send 404 response
http.NotFound(w, r)
}
To use this then you would do something like this :
import regexp
...
rex := RegexpHandler{}
rex.HandlerFunc(regexp.MustCompile("^/(\w+?)/?"), cityHandler) // cityHandler is your regular handler function
rex.HandlerFunc(regexp.MustCompile("^/"), homeHandler)
http.Handle("/", &rex)
http.ListenAndServe(":8000", nil)
Related
Is it possible to get the base path from the http.HandleFunc in the http.Request or http.ResponseWriter as a variable (first argument in http.HandleFunc) ?
http.HandleFunc("/the-base-path/", func(w http.ResponseWriter, r *http.Request){
// get "/the-base-path/" here as a variable
...
})
Short answer: no.
Longer answer.
The function that serves requests is
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
The function mux.Handler that looks up the handler for the request returns both the handler and the corresponding patter (aka "/the-base-path/")
But as you can see mux.ServeHTTP drops the pattern returned by mux.Handler. There is no direct way for the handler to identify the pattern that was matched against the request.
I am making simple web app using http/server and I use following code for handling routes (credit to this post):
package retable
import (
"context"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
)
var routes = []route{
newRoute("GET", "/", home),
}
func newRoute(method, pattern string, handler http.HandlerFunc) route {
return route{method, regexp.MustCompile("^" + pattern + "$"), handler}
}
type route struct {
method string
regex *regexp.Regexp
handler http.HandlerFunc
}
func Serve(w http.ResponseWriter, r *http.Request) {
var allow []string
for _, route := range routes {
matches := route.regex.FindStringSubmatch(r.URL.Path)
if len(matches) > 0 {
if r.Method != route.method {
allow = append(allow, route.method)
continue
}
ctx := context.WithValue(r.Context(), ctxKey{}, matches[1:])
route.handler(w, r.WithContext(ctx))
return
}
}
if len(allow) > 0 {
w.Header().Set("Allow", strings.Join(allow, ", "))
http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
return
}
http.NotFound(w, r)
}
type ctxKey struct{}
func getField(r *http.Request, index int) string {
fields := r.Context().Value(ctxKey{}).([]string)
return fields[index]
}
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "home\n")
}
How to serve static files from local "static/" folder on "/" endpoint if other route registration on this endpoint already exists?
As written, your code expects an exact match for the pattern provided when constructing a route. (See the ^ and $ when constructing the regex.) So you will not be able to handle /static/ requests in the /-pattern's handler.
You may be able to achieve what you want if you make changes to your existing code though. Some options below.
Option 1
Include the static pattern in routes:
var routes = []route{
newRoute("GET", "/", home),
newRoute("GET", "/static/(.+)", static),
}
Define an example static HTTP handler function:
func static(w http.ResponseWriter, r *http.Request) {
log.Println("received static request for ", getField(r, 0))
}
You may want to use a combination of the following to faciliate serving static files:
http.StripPrefix
http.FileServer
http.Dir / http.FS, which embed plays well with
Option 2
Modify newRoute to not use ^ and $ when constructing the regex. However, this may affect expectations elsewhere in your code. Particularly, the / pattern will match all requests, so the ordering of the routes slice becomes important.
return route{method, regexp.MustCompile(pattern), handler}
Then in home:
func home(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/static/") {
log.Println("received static request", r.URL.Path)
// TODO: actually respond with file
return
}
fmt.Fprint(w, "home\n")
}
Footnote
As a footnote, I would recommend using http.ServeMux / http.DefaultServeMux instead of your Serve implementation. These are battle-tested, will likely be more performant, and may likely have less-surprising behavior than your code.
For instance, http.ServeMux/http.DefaultServeMux clean paths, which the code in the question does not do. So, for example, with the original code as in the question, a request for
curl localhost:8080//
will result in a 404 due to the double slash instead of reaching the home handler.
This is part of my main function that I use for my end points
r := mux.NewRouter()
r.StrictSlash(true)
r.HandleFunc("/", test)
r.HandleFunc("/feature/list/", a.FeatureListHandler)
log.Fatal(http.ListenAndServe(":8080", r))
but when I curl localhost:8080/feature/list I get
<a hef="/feature/list">Moved Permanently</a>
However, when I curl localhost:8080/feature/list/ I get my json.
How do I make it so that both routes will return the json I want.
From the docs, it seems this is the expected behaviour for when StrictSlash is true:
http://www.gorillatoolkit.org/pkg/mux#Router.StrictSlash
Perhaps you can set it to false and then define both routes separately?
r.StrictSlash(false)
r.HandleFunc("/feature/list", a.FeatureListHandler)
r.HandleFunc("/feature/list/", a.FeatureListHandler)
I use the following middleware to deal with this
func suffixMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// remove the trailing slash from our URL Path if it's not root
if r.URL.Path != "/" {
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
}
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
Then you pass your router through such as:
(suffixMiddleware(router))
I want to send an url such as "Documents/folder1/folder2/file.txt" or it can have less slashes such as "Documents/folder1/file.txt", and I want to pass this url as a path parameter, such as router.HandleFunc("/myproject/v1/image/{url}", GetImage)
but when doing this it treats to go to the url for ex: /myproject/v1/image/Documents/folder1/file.txt and it does not find it so it returns 404.
I am using gorilla mux:
func main(){
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/myproject/v1/image/{url}", GetImage)
}
I thought that it was from the strictslash but when I set it false I remain getting 404
StrictSlashes has to do with a single trailing slash, not with whether or not slashes are matched inside of a parameter (they aren't). You need to use PathPrefix:
const (
imagesPrefix = "/myproject/v1/image/" // note no {url}
)
func main() {
router := mux.NewRouter()
router.PathPrefix(imagesPrefix).Handler(
http.StripPrefix(imagesPrefix, GetHandler),
)
}
func GetImage (w http.ResponseWriter, r *http.Request) {
// r.URL.Path should contain the image path.
}
Is there a way to get the current route that triggered an http.HandleFunc? Maybe something like this?
http.HandleFunc("/foo/", serveFoo)
func serveFoo(rw http.ResponseWriter, req *http.Request) {
fmt.Println(http.CurrentRoute())
// Should print "/foo/"
}
The reason I want to get the current route is because I find myself writing code like this often.
if req.URL.Path != "/some-route/" {
http.NotFound(resp, req)
return
}
// or
key := req.URL.Path[len("/some-other-route/"):]
It would be nice if the code was a bit more copy-pastable, modular, and DRY like this.
if req.URL.Path != http.CurrentRoute() {
http.NotFound(resp, req)
return
}
// or
key := req.URL.Path[http.CurrentRoute():]
This is really just a small thing, so I'd rather not bring a whole other dependency into my project (Gorilla Mux).
It is not possible to get the current route that matched, but it is possible to eliminate the duplicate code in your scenario. Write a handler that checks the path before calling through to another handler:
func HandleFuncExact(mux *http.ServeMux, pattern string, handler func(http.ResponseWriter, *http.Request) {
mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
if req.URL.Path != pattern {
http.NotFound(w, r)
return
}
handler(w, r)
})
}
In your application, call the wrapper instead of HandlFunc:
HandleFuncExact(http.DefaultServeMux, "/some-route/", serveSomeRoute)
The function serveSomeRoute can assume that the path is exactly "/some-route/".