devnull
devnull

Reputation: 2862

Net/http Simple Dynamic Routes

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

Answers (3)

Ezward
Ezward

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

Z. Kosanovic
Z. Kosanovic

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

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

Related Questions