Skip to content

Commit

Permalink
new: method handlers made easy - example and test included. Internals…
Browse files Browse the repository at this point in the history
…: small helpers for http testing using the native library
  • Loading branch information
kataras committed Oct 18, 2018
1 parent d6935f6 commit 3c4b632
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 88 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<div align="center">
<!-- Release -->
<a href="https://github.com/kataras/muxie/releases">
<img src="https://img.shields.io/badge/release%20-v1.0-0077b3.svg?style=flat-squaree"
<img src="https://img.shields.io/badge/release%20-v1.0.2-0077b3.svg?style=flat-squaree"
alt="Release/stability" />
</a>
<!-- Build Status -->
Expand Down Expand Up @@ -45,9 +45,9 @@
<a href="https://twitter.com/MakisMaropoulos">Gerasimos Maropoulos</a>
</div>

<!-- [![Benchmark chart between muxie, httprouter, gin, gorilla mux, echo, vestigo and chi](_benchmarks/chart-17-oct-2018.png)](_benchmarks)
[![Benchmark chart between muxie, httprouter, gin, gorilla mux, echo, vestigo and chi](_benchmarks/chart-17-oct-2018.png)](_benchmarks)

_Last updated on October 17, 2018._ Click [here](_benchmarks/README.md) to read more details. -->
_Last updated on October 17, 2018._ Click [here](_benchmarks/README.md) to read more details.

## Features

Expand Down
2 changes: 1 addition & 1 deletion _examples/1_hello_world/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {
mux.HandleFunc("/index", indexHandler)
mux.HandleFunc("/about", aboutHandler)

fmt.Println(`Server started at :8080
fmt.Println(`Server started at http://localhost:8080
Open your browser or any other HTTP Client and navigate to:
http://localhost:8080
http://localhost:8080/index and
Expand Down
2 changes: 1 addition & 1 deletion _examples/2_parameterized/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func main() {
// you can use it as root wildcard as well (/*myparam).
// Navigate to the next example to learn how you can add your own 404 not found handler.

fmt.Println("Server started at :8080")
fmt.Println("Server started at http://localhost:8080")
http.ListenAndServe(":8080", mux)
}

Expand Down
4 changes: 2 additions & 2 deletions _examples/3_root_wildcard_and_custom_404/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ func main() {
// request: http://localhost:8080/profile/kataras/what
// response:
// Profile Page Custom 404 Error Message
// Profile Page of: '/kataras/what' was unable to be found
// Profile Page of: 'kataras/what' was unable to be found
mux.HandleFunc("/profile/*path", func(w http.ResponseWriter, r *http.Request) {
path := muxie.GetParam(w, "path")
fmt.Fprintf(w, "Profile Page Custom 404 Error Message\nProfile Page of: '%s' was unable to be found", path)
})

fmt.Println("Server started at :8080")
fmt.Println("Server started at http://localhost:8080")
http.ListenAndServe(":8080", mux)
}

Expand Down
4 changes: 2 additions & 2 deletions _examples/4_grouping/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ func main() {
// request: http://localhost:8080/profile/kataras/what
// response:
// Profile Page Custom 404 Error Message
// Profile Page of: '/kataras/what' was unable to be found
// Profile Page of: kataras/what' was unable to be found
profileRouter.HandleFunc("/*path", func(w http.ResponseWriter, r *http.Request) {
path := muxie.GetParam(w, "path")
fmt.Fprintf(w, "Profile Page Custom 404 Error Message\nProfile Page of: '%s' was unable to be found", path)
})

fmt.Println("Server started at :8080")
fmt.Println("Server started at http://localhost:8080")
http.ListenAndServe(":8080", mux)
}
3 changes: 1 addition & 2 deletions _examples/5_internal_route_node_info/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,13 @@ func main() {
}
})

fmt.Println("Server started at :8080")

// http://localhost:8080
// http://localhost:8080/index
// http://localhost:8080/about
// http://localhost:8080/v1/users
// http://localhost:8080/v1/users/42
// http://localhost:8080/nodes/v1
fmt.Println("Server started at http://localhost:8080")
http.ListenAndServe(":8080", mux)
}

Expand Down
1 change: 1 addition & 0 deletions _examples/6_middleware/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func main() {
// http://localhost:8080
// http://localhost:8080/inheritor
// http://localhost:8080/orphan
log.Println("Server started at http://localhost:8080")
http.ListenAndServe(":8080", mux)
}

Expand Down
51 changes: 51 additions & 0 deletions _examples/7_by_methods/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"fmt"
"log"
"net/http"

"github.com/kataras/muxie"
)

func main() {
mux := muxie.NewMux()
mux.PathCorrection = true

mux.HandleFunc("/users", listUsersWithoutMuxieMethods)

mux.Handle("/user/:id", muxie.Methods().
HandleFunc(http.MethodGet, getUser).
HandleFunc(http.MethodPost, saveUser).
HandleFunc(http.MethodDelete, deleteUser))

log.Println("Server started at http://localhost:8080\nGET: http://localhost:8080/users\nGET, POST, DELETE: http://localhost:8080/user/:id")
log.Fatal(http.ListenAndServe(":8080", mux))
}

// The `muxie.Methods()` is just a helper for this common matching.
//
// However, you definitely own your route handlers,
// therefore you can easly make these checks manually
// by matching the `r.Method`.
func listUsersWithoutMuxieMethods(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Allow", http.MethodGet)
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}

fmt.Fprintf(w, "GET: List all users\n")
}

func getUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "GET: User details by user ID: %s\n", muxie.GetParam(w, "id"))
}

func saveUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "POST: save user with ID: %s\n", muxie.GetParam(w, "id"))
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "DELETE: remove user with ID: %s\n", muxie.GetParam(w, "id"))
}
84 changes: 84 additions & 0 deletions method_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package muxie

import (
"net/http"
"strings"
)

// Methods returns a MethodHandler which caller can use
// to register handler for specific HTTP Methods inside the `Mux#Handle/HandleFunc`.
// Usage:
// mux := muxie.NewMux()
// mux.Handle("/user", muxie.Methods().
// Handle("GET", getUserHandler).
// Handle("POST", saveUserHandler))
func Methods() *MethodHandler {
//
// Design notes, the latest one is selected:
//
// mux := muxie.NewMux()
//
// 1. mux.Handle("/user", muxie.ByMethod("GET", getHandler).And/AndFunc("POST", postHandlerFunc))
//
// 2. mux.Handle("/user", muxie.ByMethods{
// "GET": getHandler,
// "POST" http.HandlerFunc(postHandlerFunc),
// }) <- the only downside of this is that
// we lose the "Allow" header, which is not so important but it is RCF so we have to follow it.
//
// 3. mux.Handle("/user", muxie.Method("GET", getUserHandler).Method("POST", saveUserHandler))
//
// 4. mux.Handle("/user", muxie.Methods().
// Handle("GET", getHandler).
// HandleFunc("POST", postHandler))
//
return &MethodHandler{handlers: make(map[string]http.Handler)}
}

// MethodHandler implements the `http.Handler` which can be used on `Mux#Handle/HandleFunc`
// to declare handlers responsible for specific HTTP method(s).
//
// Look `Handle` and `HandleFunc`.
type MethodHandler struct {
// origin *Mux

handlers map[string]http.Handler // method:handler

methodsAllowedStr string
}

// Handle adds a handler to be responsible for a specific HTTP Method.
// Returns this MethodHandler for further calls.
func (m *MethodHandler) Handle(method string, handler http.Handler) *MethodHandler {
method = strings.ToUpper(method)
if m.methodsAllowedStr == "" {
m.methodsAllowedStr = method
} else {
m.methodsAllowedStr += ", " + method
}

m.handlers[method] = handler

return m
}

// HandleFunc adds a handler function to be responsible for a specific HTTP Method.
// Returns this MethodHandler for further calls.
func (m *MethodHandler) HandleFunc(method string, handlerFunc func(w http.ResponseWriter, r *http.Request)) *MethodHandler {
m.Handle(method, http.HandlerFunc(handlerFunc))
return m
}

func (m *MethodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if handler, ok := m.handlers[r.Method]; ok {
handler.ServeHTTP(w, r)
return
}

// RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// The response MUST include an Allow header containing a list of valid methods for the requested resource.
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Allow#Examples
w.Header().Set("Allow", m.methodsAllowedStr)
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
}
52 changes: 52 additions & 0 deletions method_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package muxie

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

func TestMethodHandler(t *testing.T) {
mux := NewMux()
mux.PathCorrection = true

mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Allow", http.MethodGet)
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}

fmt.Fprintf(w, "GET: List all users\n")
})

mux.Handle("/user/:id", Methods().
HandleFunc(http.MethodGet, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "GET: User details by user ID: %s\n", GetParam(w, "id"))
}).
HandleFunc(http.MethodPost, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "POST: save user with ID: %s\n", GetParam(w, "id"))
}).
HandleFunc(http.MethodDelete, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "DELETE: remove user with ID: %s\n", GetParam(w, "id"))
}))

srv := httptest.NewServer(mux)
defer srv.Close()

expect(t, http.MethodGet, srv.URL+"/users").statusCode(http.StatusOK).
bodyEq("GET: List all users\n")
expect(t, http.MethodPost, srv.URL+"/users").statusCode(http.StatusMethodNotAllowed).
bodyEq("Method Not Allowed\n").headerEq("Allow", "GET")

expect(t, http.MethodGet, srv.URL+"/user/42").statusCode(http.StatusOK).
bodyEq("GET: User details by user ID: 42\n")
expect(t, http.MethodPost, srv.URL+"/user/42").statusCode(http.StatusOK).
bodyEq("POST: save user with ID: 42\n")
expect(t, http.MethodDelete, srv.URL+"/user/42").statusCode(http.StatusOK).
bodyEq("DELETE: remove user with ID: 42\n")

expect(t, http.MethodPut, srv.URL+"/user/42").statusCode(http.StatusMethodNotAllowed).
bodyEq("Method Not Allowed\n").headerEq("Allow", "GET, POST, DELETE")
}
Loading

0 comments on commit 3c4b632

Please sign in to comment.