Reputation: 2862
I am looking for a simple way to create dynamic routes with net/http (no routers like mux etc.) Here is my current code:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
pages := r.URL.Query()["q"]
if len(pages) == 0 {
fmt.Fprintf(w, "§§§§§§§§§§ You need to specify a page §§§§§§§§§§")
return
}
page := pages[0]
var a Page
err := db.QueryRow("SELECT * FROM pages where page = ?", page).Scan(&a.Page, &a.Date, &a.Url)
a.Year = time.Now().UTC().Year()
if err != nil {
if err == sql.ErrNoRows {
fmt.Fprintf(w, "Page %s not found", page)
return
} else {
fmt.Fprintf(w, "Some error happened")
return
}
}
http.Redirect(w, r, a.Url, 301)
})
So now the URL sample.com/?q= works dynamically. My objective is to work without having to use r.URL.Query()["q"] so directly /pagename
This is not a duplicate of Go url parameters mapping because it is a single level (not nested levels) AND many answers in that question refer to using an external library.
Upvotes: 2
Views: 3769
Reputation: 17702
You can do this in go 1.22 without having to parse the path yourself, as suggested in the accepted answer. As of go 1.22 go will parse placeholders in a ServeMux matched url. From the release notes, "The new method Request.PathValue returns path wildcard values from a request and the new method Request.SetPathValue sets path wildcard values on a request." See the Go blog article routing-enhancements. So for your case with a simple url ,
http.Handle("GET /{page}", handlePost)
which will handle a GET or HEAD request to paths like /1
or /index
. Further, the request handler can retrieve the value in the matched placeholder {page}
using the handler's Request argument and the PathValue method; request.PathValue("page")
would yield "1" and "index" in the examples respectively.
See the go docs for PathValue and ServeMux for more details.
So your example could be written:
http.HandleFunc("GET /{page}", func(w http.ResponseWriter, r *http.Request){
page := r.PathValue("page")
var a Page
err := db.QueryRow("SELECT * FROM pages where page = ?", page).Scan(&a.Page, &a.Date, &a.Url)
a.Year = time.Now().UTC().Year()
if err != nil {
if err == sql.ErrNoRows {
fmt.Fprintf(w, "Page %s not found", page)
return
} else {
fmt.Fprintf(w, "Some error happened")
return
}
}
http.Redirect(w, r, a.Url, 301)
})
I think the PathValue approach works better than the accepted solution using page := r.URL.Path[1:]
because 1) it will not match urls with extra path segments, like "/page1/foo/bar" and 2) it can be target the GET method specifically without any extra code.
Upvotes: 0
Reputation: 787
If you don't want to use any third-party libraries, you have to handle the parsing of the path yourself.
For start, you can do this:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
page := r.URL.Path[1:]
// do whatever logic you want
// mind that the page could be "multi/level/path/" as well
})
Upvotes: 4
Reputation: 559
You can use http.HandleFunc. In this function, a pattern ending in a slash defines a subtree. You can register a handler function with the pattern "/page/" like the below example.
package main
import (
"net/http"
"fmt"
)
func handler(w http.ResponseWriter, r *http.Request) {
if is_valid_page(r.URL) {
fmt.Fprint(w, "This is a valid page")
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Error 404 - Page not found")
}
}
func is_valid_page(page string) {
// check here if page is valid from url
}
func main() {
http.HandleFunc("/page/", handler)
http.ListenAndServe(":8080", nil)
}
more info you can find here: https://golang.org/pkg/net/http/#ServeMux
Upvotes: 1