Created
February 10, 2018 21:43
-
-
Save xeoncross/d78444640efb90a4520e3295d239e12f to your computer and use it in GitHub Desktop.
building github.com/mustafaakin/gongular from scratch using httprouter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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