Skip to content

Commit

Permalink
finalize the RequestHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Oct 20, 2018
1 parent 2167f3d commit 1d43c7b
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 71 deletions.
17 changes: 10 additions & 7 deletions _examples/9_subdomains_and_matchers/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,35 @@ func main() {
mySubdomain.HandleFunc("/", handleMySubdomainIndex)
mySubdomain.HandleFunc("/about", aboutHandler)

mux.Match(muxie.Host("mysubdomain.localhost:8080"), mySubdomain)
mux.HandleRequest(muxie.Host("mysubdomain.localhost:8080"), mySubdomain)
//

// mysubsubdomain.mysubdomain
// This is a fully featured muxie Mux,
// it could have its own request handlers as well but this is not part of the exercise.
mySubSubdomain := muxie.NewMux()

mySubSubdomain.HandleFunc("/", handleMySubSubdomainIndex)
mySubSubdomain.HandleFunc("/about", aboutHandler)

mux.Match(muxie.Host("mysubsubdomain.mysubdomain.localhost:8080"), mySubSubdomain)
mux.HandleRequest(muxie.Host("mysubsubdomain.mysubdomain.localhost:8080"), mySubSubdomain)
//

// any other subdomain
myWildcardSubdomain := muxie.NewMux()
myWildcardSubdomain.HandleFunc("/", handleMyWildcardSubdomainIndex)
// Catch any other host that ends with .localhost:8080.
mux.Match(muxie.Host(".localhost:8080"), myWildcardSubdomain)
mux.HandleRequest(muxie.Host(".localhost:8080"), myWildcardSubdomain)
/*
Or add a custom match func that validates if the router
should proceed with this subdomain handler:
This one is extremely useful for apps that may need dynamic subdomains based on a database,
usernames for example.
mux.MatchFunc(func(r *http.Request) bool{
mux.HandleRequest(muxie.MatcherFunc(func(r *http.Request) bool{
return userRepo.Exists(...use of http.Request)
}, myWildcardSubdomain)
}), myWildcardSubdomain)
Or
mux.AddMatcher(_value_of_a_struct_which_completes_the_muxie.MatcherHandler)
mux.AddRequestHandler(_value_of_a_struct_which_completes_the_muxie.RequestHandler with Match and ServeHTTP funcs)
*/

// Chrome-based browsers will automatically work but to test with
Expand All @@ -53,7 +56,7 @@ func main() {
// You may run your own virtual domain if you change the listening addr ":8080"
// to something like "mydomain.com:80".
//
// Read more at godocs of `Mux#AddMatcher`.
// Read more at godocs of `Mux#AddRequestHandler`.
fmt.Println(`Server started at http://localhost:8080
Open your browser and navigate through:
http://mysubdomain.localhost:8080
Expand Down
4 changes: 4 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Installation
The only requirement is the Go Programming Language
$ go get -u github.com/kataras/muxie
Examples
https://github.com/kataras/muxie/tree/master/_examples
*/

package muxie
51 changes: 22 additions & 29 deletions mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ type Mux struct {
paramsPool *sync.Pool

// per mux
root string
matchers []MatcherHandler
beginHandlers []Wrapper
root string
requestHandlers []RequestHandler
beginHandlers []Wrapper
}

// NewMux returns a new HTTP multiplexer which uses a fast, if not the fastest
Expand All @@ -48,40 +48,33 @@ func NewMux() *Mux {
}
}

// AddMatcher adds a full `MatcherHandler` which is responsible
// to validate a handler before execute, if handler is executed
// then the router stops searching for this Mux' routes.
// AddRequestHandler adds a full `RequestHandler` which is responsible
// to check if a handler should be executed via its `Matcher`,
// if the handler is executed
// then the router stops searching for this Mux' routes,
// RequestHandelrs have priority over the routes and the middlewares.
//
// The "matcherHandler"'s Handler can be any http.Handler
// The "requestHandler"'s Handler can be any http.Handler
// and a new `muxie.NewMux` as well. The new Mux will
// be not linked to this Mux by-default, if you want to share
// middlewares then you have to use the `muxie.Pre` to declare
// the shared middlewares and register them via the `Mux#Use` function.
func (m *Mux) AddMatcher(matcherHandler MatcherHandler) {
m.matchers = append(m.matchers, matcherHandler)
func (m *Mux) AddRequestHandler(requestHandler RequestHandler) {
m.requestHandlers = append(m.requestHandlers, requestHandler)
}

// Match adds a matcher and a request handler for that matcher if passed.
// HandleRequest adds a matcher and a (conditinal) handler to be executed when "matcher" passed.
// If the "matcher" passed then the "handler" will be executed
// and this Mux' routes will be ignored.
//
// Look the `Mux#AddMatcher` for further details.
func (m *Mux) Match(matcher Matcher, handler http.Handler) {
m.AddMatcher(&simpleMatcherHandler{
// Look the `Mux#AddRequestHandler` for further details.
func (m *Mux) HandleRequest(matcher Matcher, handler http.Handler) {
m.AddRequestHandler(&simpleRequestHandler{
Matcher: matcher,
Handler: handler,
})
}

// MatchFunc adds a matcher function and a request handler for that matcher if passed.
// If the "matcher" passed then the "handler" will be executed
// and this Mux' routes will be ignored.
//
// Look the `Mux#AddMatcher` for further details.
func (m *Mux) MatchFunc(matcherFunc func(*http.Request) bool, handler http.Handler) {
m.Match(MatcherFunc(matcherFunc), handler)
}

// Use adds middleware that should be called before each mux route's main handler.
// Should be called before `Handle/HandleFunc`. Order matters.
//
Expand Down Expand Up @@ -149,9 +142,9 @@ func (m *Mux) HandleFunc(pattern string, handlerFunc func(http.ResponseWriter, *

// ServeHTTP exposes and serves the registered routes.
func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, matcher := range m.matchers {
if matcher.Match(r) {
matcher.ServeHTTP(w, r)
for _, h := range m.requestHandlers {
if h.Match(r) {
h.ServeHTTP(w, r)
return
}
}
Expand Down Expand Up @@ -250,9 +243,9 @@ func (m *Mux) Of(prefix string) SubMux {
return &Mux{
Routes: m.Routes,

root: prefix,
matchers: m.matchers[0:],
beginHandlers: m.beginHandlers[0:],
root: prefix,
requestHandlers: m.requestHandlers[0:],
beginHandlers: m.beginHandlers[0:],
}
}

Expand Down Expand Up @@ -284,7 +277,7 @@ breaking changes and etc. This `Unlink`, which will return the same SubMux, it c
// v1 := mux.Of("/v1").Unlink() // v1 will no longer have the "myLoggerMiddleware" or any Matchers.
// v1.HandleFunc("/users", myHandler)
func (m *Mux) Unlink() SubMux {
m.matchers = m.matchers[0:0]
m.requestHandlers = m.requestHandlers[0:0]
m.beginHandlers = m.beginHandlers[0:0]

return m
Expand Down
32 changes: 21 additions & 11 deletions mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,44 +35,54 @@ func expectWithBody(t *testing.T, method, url string, body string, headers http.
}

func testReq(t *testing.T, req *http.Request) *testie {
res, err := http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}

return &testie{t: t, res: res}
resp.Request = req
return &testie{t: t, resp: resp}
}

func testHandler(t *testing.T, handler http.Handler, method, url string) *testie {
w := httptest.NewRecorder()
req := httptest.NewRequest(method, url, nil)
handler.ServeHTTP(w, req)
resp := w.Result()
resp.Request = req
return &testie{t: t, resp: resp}
}

type testie struct {
t *testing.T
res *http.Response
t *testing.T
resp *http.Response
}

func (te *testie) statusCode(expected int) *testie {
if got := te.res.StatusCode; expected != got {
te.t.Fatalf("%s: expected status code: %d but got %d", te.res.Request.URL, expected, got)
if got := te.resp.StatusCode; expected != got {
te.t.Fatalf("%s: expected status code: %d but got %d", te.resp.Request.URL, expected, got)
}

return te
}

func (te *testie) bodyEq(expected string) *testie {
b, err := ioutil.ReadAll(te.res.Body)
te.res.Body.Close()
b, err := ioutil.ReadAll(te.resp.Body)
te.resp.Body.Close()
if err != nil {
te.t.Fatal(err)
}

if got := string(b); expected != got {
te.t.Fatalf("%s: expected to receive '%s' but got '%s'", te.res.Request.URL, expected, got)
te.t.Fatalf("%s: expected to receive '%s' but got '%s'", te.resp.Request.URL, expected, got)
}

return te
}

func (te *testie) headerEq(key, expected string) *testie {
if got := te.res.Header.Get(key); expected != got {
te.t.Fatalf("%s: expected header value of %s to be: '%s' but got '%s'", te.res.Request.URL, key, expected, got)
if got := te.resp.Header.Get(key); expected != got {
te.t.Fatalf("%s: expected header value of %s to be: '%s' but got '%s'", te.resp.Request.URL, key, expected, got)
}

return te
Expand Down
6 changes: 1 addition & 5 deletions params_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package muxie
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

Expand All @@ -15,8 +14,5 @@ func TestGetParam(t *testing.T) {
fmt.Fprintf(w, "Hello %s", name)
})

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

expect(t, http.MethodGet, srv.URL+"/hello/kataras").bodyEq("Hello kataras")
testHandler(t, mux, http.MethodGet, "/hello/kataras").bodyEq("Hello kataras")
}
32 changes: 15 additions & 17 deletions matcher.go → request_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@ import (
)

type (
// RequestHandler is the matcher and handler link interface.
// It is used inside the `Mux` to handle requests based on end-developer's custom logic.
// If a "Matcher" passed then the "Handler" is executing and the rest of the Mux' routes will be ignored.
RequestHandler interface {
http.Handler
Matcher
}

simpleRequestHandler struct {
http.Handler
Matcher
}

// Matcher is the interface that all Matchers should be implemented
// in order to be registered into the Mux via the `Mux#AddMatcher/Match/MatchFunc` functions.
// in order to be registered into the Mux via the `Mux#AddRequestHandler/Match/MatchFunc` functions.
//
// Look the `Mux#AddMatcher` for more.
// Look the `Mux#AddRequestHandler` for more.
Matcher interface {
Match(*http.Request) bool
}
Expand All @@ -36,18 +49,3 @@ func (h Host) Match(r *http.Request) bool {
s := string(h)
return r.Host == s || (s[0] == '.' && strings.HasSuffix(r.Host, s)) || s == WildcardParamStart
}

type (
// MatcherHandler is the matcher and handler link interface.
// It is used inside the `Mux` to handle requests based on end-developer's custom logic.
// If a "Matcher" passed then the "Handler" is executing and the rest of the Mux' routes will be ignored.
MatcherHandler interface {
http.Handler
Matcher
}

simpleMatcherHandler struct {
http.Handler
Matcher
}
)
59 changes: 59 additions & 0 deletions request_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package muxie

import (
"net/http"
"testing"
)

func TestRequestHandler(t *testing.T) {
const (
domain = "localhost.suff"
domainResponse = "Hello from root domain"
subdomain = "mysubdomain." + domain
subdomainResponse = "Hello from " + subdomain
subdomainAboutResposne = "About the " + subdomain
wildcardSubdomain = "." + domain
wildcardSubdomainResponse = "Catch all subdomains"

customMethod = "CUSTOM"
)

mux := NewMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(domainResponse))
})

subdomainHandler := NewMux() // can have its own request handlers as well.
subdomainHandler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(subdomainResponse))
})
subdomainHandler.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(subdomainAboutResposne))
})

mux.HandleRequest(Host(subdomain), subdomainHandler)
mux.HandleRequest(Host(wildcardSubdomain), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(wildcardSubdomainResponse))
}))

mux.HandleRequest(MatcherFunc(func(r *http.Request) bool {
return r.Method == customMethod
}), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(customMethod))
}))

testHandler(t, mux, http.MethodGet, "http://"+domain).
statusCode(http.StatusOK).bodyEq(domainResponse)

testHandler(t, mux, http.MethodGet, "http://"+subdomain).
statusCode(http.StatusOK).bodyEq(subdomainResponse)

testHandler(t, mux, http.MethodGet, "http://"+subdomain+"/about").
statusCode(http.StatusOK).bodyEq(subdomainAboutResposne)

testHandler(t, mux, http.MethodGet, "http://anysubdomain.here.for.test"+subdomain).
statusCode(http.StatusOK).bodyEq(wildcardSubdomainResponse)

testHandler(t, mux, customMethod, "http://"+domain).
statusCode(http.StatusOK).bodyEq(customMethod)
}
5 changes: 3 additions & 2 deletions trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,12 @@ type ParamsSetter interface {
// 4. closest wildcard if not found, if any
// 5. root wildcard
func (t *Trie) Search(q string, params ParamsSetter) *Node {
if len(q) == 1 && q[0] == pathSepB {
end := len(q)

if end == 0 || (end == 1 && q[0] == pathSepB) {
return t.root.getChild(pathSep)
}

end := len(q)
n := t.root
start := 1
i := 1
Expand Down

0 comments on commit 1d43c7b

Please sign in to comment.