Simplifying route handler to handle parameters without framework

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?

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)
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.