Skip to content

Instantly share code, notes, and snippets.

@xeoncross
Created February 10, 2018 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xeoncross/d78444640efb90a4520e3295d239e12f to your computer and use it in GitHub Desktop.
Save xeoncross/d78444640efb90a4520e3295d239e12f to your computer and use it in GitHub Desktop.
building github.com/mustafaakin/gongular from scratch using httprouter
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/asaskevich/govalidator"
"github.com/gorilla/schema"
"github.com/julienschmidt/httprouter"
)
// This is a test of writing github.com/mustafaakin/gongular from scratch to support things like
// templates, middlewares, and mutating of responses. It's also fun for learning purposes to do this.
func indexHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}
func main() {
router := httprouter.New()
router.GET("/", indexHandler)
router.GET("/hello/:Name", Mid(&MyHandler{TemplatePath: "about.html"}))
// MyType := &MyHandler{}
// var _ Handler = (*MyHandler)(nil)
fmt.Println("started on :8000")
log.Fatal(http.ListenAndServe(":8000", router))
}
//
// My System
//
type MyHandler struct {
Username string
Name string
// Param struct {
// Username string
// }
// Query struct {
// Name string
// Age int
// Level float64
// }
// Body struct {
// Comment string
// Choices []string
// Address struct {
// City string
// Country string
// Hello string
// }
// }
TemplatePath string
}
func (m *MyHandler) Handle(c *Context) interface{} {
// fmt.Printf("%#v\n", m)
return "'Hi' from MyHandler"
}
type Context struct {
r *http.Request
w http.ResponseWriter
Params httprouter.Params
Response interface{}
}
// RequestHandler is a generic handler for gongular2
// type RequestHandler interface {
// Handle(c *Context) error
// }
type Handler interface {
Handle(c *Context) interface{}
}
func Mid(handlers ...interface{}) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, rp httprouter.Params) {
r.ParseForm()
// URL params trump everything
for _, p := range rp {
// fmt.Printf("\tParam: %s => %s\n", p.Key, p.Value)
r.Form[p.Key] = []string{p.Value}
}
// log.Println("http:", r.Method, r.URL.Path) //, r.UserAgent())
c := &Context{r, w, rp, nil}
// Recover from panics
// defer func() {
// if err := recover(); err != nil {
// log.Printf("Caught Panic: %+v", err)
//
// if str, ok := err.(string); ok {
// http.Error(w, str, 500)
// } else if e, ok := err.(error); ok {
// http.Error(w, e.Error(), 500)
// }
// http.Error(w, http.StatusText(500), 500)
// }
// }()
// var response interface{}
for _, handler := range handlers {
// fmt.Println(i, reflect.TypeOf(handler).Name())
// https://play.golang.org/p/4k_RjO1jv_c
// https://play.golang.org/p/vwsohHgJVpc
switch handler := handler.(type) {
case http.Handler:
handler.ServeHTTP(w, r)
case func(http.ResponseWriter, *http.Request):
handler(w, r)
// case *Handler:
// fmt.Println("is *Handler")
// c.Response = (*handler).Handle(c)
// c.Response = handler.Handle(c)
case Handler:
// handler.Param.Username = c.Params.ByName("name")
// r2 := &http.Request{
// PostForm: url.Values{
// "Username": {"Manual"},
// },
// }
// err, errors := ValidateStruct(handler, r)
ValidateStruct(handler, r)
// fmt.Println("is Handler")
// fmt.Println(err, errors)
c.Response = handler.Handle(c)
default:
// fmt.Println(reflect.TypeOf(handler).Name())
fmt.Printf("Invalid Handler %T\n", handler)
// log.Printf("Invalid Handler %T\n\n%#v\n", handler, handler)
}
// fmt.Println("Response", c.Response)
}
json.NewEncoder(w).Encode(c.Response)
}
}
// ValidateStruct provided
func ValidateStruct(s interface{}, r *http.Request) (err error, errors map[string]string) {
// Parse the input
r.ParseForm()
// 1. Try to insert form data into the struct
decoder := schema.NewDecoder()
// A) Developer forgot about a field
// B) Client is messing with the request fields
decoder.IgnoreUnknownKeys(true)
// Even if there is an error, we can still validate what we have
_ = decoder.Decode(s, r.Form)
// err = decoder.Decode(s, form)
// fmt.Println("decoder.Decode", err)
// 2. Validate the struct data rules
_, err = govalidator.ValidateStruct(s)
if err != nil {
errors = govalidator.ErrorsByField(err)
}
return
}
package main
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/julienschmidt/httprouter"
)
func TestMiddleware(t *testing.T) {
req, err := http.NewRequest("GET", "/hello/John", nil)
if err != nil {
t.Fatal(err)
}
req.Form = url.Values{"Username": {"Faker"}}
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
// httprouter satisfies http.Handler, so we can call ServeHTTP directly
// and pass in our Request and ResponseRecorder.
router := httprouter.New()
router.GET("/hello/:Name", Mid(&MyHandler{TemplatePath: "about.html"}))
router.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
t.Error(rr.Body.String())
}
}
func BenchmarkMiddleware(b *testing.B) {
rr := httptest.NewRecorder()
router := httprouter.New()
router.GET("/hello/:Name", Mid(&MyHandler{TemplatePath: "about.html"}))
for n := 0; n < b.N; n++ {
req, err := http.NewRequest("GET", "/hello/John", nil)
if err != nil {
b.Fatal(err)
}
req.Form = url.Values{"Username": {"Faker"}}
router.ServeHTTP(rr, req)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment