Home > OS >  Simplifying route handler to handle parameters without framework
Simplifying route handler to handle parameters without framework

Time:11-08

In rocket.rs, we've this simple route code:

#[get("/hello/<name>/<age>")]
fn hello(name: &str, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

where if you were to visit http://localhost:8000/hello/John/58 in the browser, you’d see: Hello, 58 year old named John!

I read this, but the accepted answer is about a way to do Go url parameters mapping for single route, that could read http://localhost:8080/blob/123/test as /blob/{id}/test and display the required route.

I know there are some great routers/frameworks there, but looking to build simple code myself to understand http route handlers in a better way.

Let's say I've:

type Tender struct {
    tenderReference string
    venderCode      int
}

func (t Tender) readWrite() {
    fmt.Printf("Tender %s is ready for vendor %d to review and submit\n", t.tenderReference, t.venderCode)
}

func (t Tender) readOnly(w http.ResponseWriter, r *http.Request) {
    fmt.Printf("Tender %s already submitted by vender %d\n", t.tenderReference, t.venderCode)
}

And want my routes to be something like:

  1. /api/tender/readWrite/{tenderReference}/vendor/{venderCode} that is calling func (t Tender) readWrite(){}

  2. /api/tender/readOnly/{tenderReference}/vendor/{venderCode} that is calling func (t Tender) readOnly(){}

How many route handler do I have to build?

CodePudding user response:

I solved it as below, other thoughts are welcomed:

  1. 404.go
package main

import (
    "fmt"
    "net/http"
)

func handle404(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "mmm, it looks you are playing around, page is not available :)\n")
}
  1. getField.go
package main

import "net/http"

type ctxKey struct{}

func getField(r *http.Request, index int) string {
    fields := r.Context().Value(ctxKey{}).([]string)
    return fields[index]
}
  1. routes.go
package main

import (
    "net/http"
    "regexp"
)

type route struct {
    method  string
    regex   *regexp.Regexp
    handler http.HandlerFunc
}

var routes = []route{
    newRoute("GET", "/api/tender/(rw|r)/([^/] )/vendor/([0-9] )", apiTenders),
}

func newRoute(method, pattern string, handler http.HandlerFunc) route {
    return route{method, regexp.MustCompile("^"   pattern   "$"), handler}
}
  1. tendor.go
package main

import (
    "fmt"
    "net/http"
    "strconv"
)

type Tender struct {
    tenderReference string
    venderCode      int
}

// Handles GET /api/tender/(rw|r)/([^/] )/vendor/([0-9] )
func apiTenders(w http.ResponseWriter, r *http.Request) {
    action := getField(r, 0)
    tenderReference := getField(r, 1)
    venderCode, _ := strconv.Atoi(getField(r, 2))
    tender := Tender{tenderReference, venderCode}
    switch action {
    case "rw":
        tender.readWrite(w, r) // Display tender and allow vendor to submit feedback
    case "r":
        tender.readOnly(w, r) // Display readOnly copy of the tender
    default:
        fmt.Fprintf(w, "Tendert ERROR\n")
    }
}

func (t Tender) readWrite(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Tender %s is ready for vendor %d to review and submit\n", t.tenderReference, t.venderCode)
}

func (t Tender) readOnly(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Tender %s already submitted by vender %d\n", t.tenderReference, t.venderCode)
}
  1. server.go
package main

import (
    "context"
    "fmt"
    "net/http"
    "strings"
)

type apiHandler struct{}

func main() {
    http.Handle("/api/", apiHandler{})
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        // The "/" pattern matches everything, so we need to check
        // that we're at the root here.
        if req.URL.Path != "/" {
            http.NotFound(w, req)
            return
        }
        fmt.Fprintf(w, "Welcome to the home page!")
    })
    http.ListenAndServe(":8000", nil)
}

func (apiHandler) ServeHTTP(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
    }
    handle404(w, r)
    //http.NotFound(w, r)
}
  •  Tags:  
  • go
  • Related