Skip to content

Commit

Permalink
subdomains and matchers final design, upgrade version to 1.0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Oct 19, 2018
1 parent aa57354 commit 4eae530
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 124 deletions.
2 changes: 1 addition & 1 deletion 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.3-0077b3.svg?style=flat-squaree"
<img src="https://img.shields.io/badge/release%20-v1.0.4-0077b3.svg?style=flat-squaree"
alt="Release/stability" />
</a>
<!-- Godocs -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"fmt"
"net/http"
"strings"

"github.com/kataras/muxie"
)
Expand All @@ -17,29 +16,34 @@ func main() {
mySubdomain.HandleFunc("/", handleMySubdomainIndex)
mySubdomain.HandleFunc("/about", aboutHandler)

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

// mysubsubdomain.mysubdomain
// mySubSubdomain := muxie.NewMux()
// mySubSubdomain.HandleFunc("/", handleMySubSubdomainIndex)
// mySubSubdomain.HandleFunc("/about", aboutHandler)
// mux.MatchHost("mysubsubdomain.mysubdomain.localhost:8080", mySubSubdomain)

// the advandage of the below is that we are able to continue with mySubSubdomain.If()..., it can be embedded
// but the same for the above, user is able to add matchers.
// Keep both on the v1 branch and not master, until I decide the final design.
mySubSubdomain := mux.If(muxie.Host("mysubsubdomain.mysubdomain.localhost:8080"))
mySubSubdomain := muxie.NewMux()
mySubSubdomain.HandleFunc("/", handleMySubSubdomainIndex)
mySubSubdomain.HandleFunc("/about", aboutHandler)

mux.Match(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)
/*
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{
return userRepo.Exists(...use of http.Request)
}, myWildcardSubdomain)
Or
mux.AddMatcher(_value_of_a_struct_which_completes_the_muxie.MatcherHandler)
*/

// Catch any other host that it is not our main localhost:8080.
// Extremely useful for apps that may need dynamic subdomains based on a database,
// usernames for example.
mux.Match(func(r *http.Request) bool { return strings.HasSuffix(r.Host, ".localhost:8080") }, myWildcardSubdomain)
// Chrome-based browsers will automatically work but to test with
// firefox or a custom http client or POSTMAN you may want to edit your hosts,
// i.e on windows is going like this:
Expand All @@ -61,7 +65,7 @@ http://any.subdomain.can.be.handled.by.asterix.localhost:8080`)
}

func handleRootDomainIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "[0] Hello from the root: %s\n", r.Host)
fmt.Fprintf(w, "[0] Hello from the root domain: %s\n", r.Host)
}

func handleMySubdomainIndex(w http.ResponseWriter, r *http.Request) {
Expand Down
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Source code and other details for the project are available at GitHub:
Current Version
1.0.3
1.0.4
Installation
Expand Down
53 changes: 53 additions & 0 deletions matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package muxie

import (
"net/http"
"strings"
)

type (
// 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.
//
// Look the `Mux#AddMatcher` for more.
Matcher interface {
Match(*http.Request) bool
}

// MatcherFunc is a shortcut of the Matcher, as a function.
// See `Matcher`.
MatcherFunc func(*http.Request) bool
)

// Match returns the result of the "fn" matcher.
// Implementing the `Matcher` interface.
func (fn MatcherFunc) Match(r *http.Request) bool {
return fn(r)
}

// Host is a Matcher for hostlines.
// It can accept exact hosts line like "mysubdomain.localhost:8080"
// or a suffix, i.e ".localhost:8080" will work as a wildcard subdomain for our root domain.
// The domain and the port should match exactly the request's data.
type Host string

// Match validates the host, implementing the `Matcher` interface.
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
}
)
134 changes: 29 additions & 105 deletions mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,69 +23,14 @@ import (
//
// See `NewMux`.
type Mux struct {
/* Hosts map[string]http.Handler
:: Design notes, none accepted ::
[1]
mySubdomain := mux.WithHost("mysubdomain")
mySubdomain.HandleFunc("/", handleMySubdomainIndex)
mySubSubdomain := mySubdomain.WithHost("mysubsubdomain")
mySubSubdomain.HandleFunc("/", handleSubMySubdomainIndex)
[2]
mux.HandleHost("should_be_exactly_the_host_and_domain_and_port", anHttpHandler)
... mySubdomain := NewMux()
... mySubdomain.Handle/HandleFunc(path, handler)
... mux.HandleHost("mysubdomain.localhost:8080", mySubdomain)
[3]
mux.Hosts["should_be_exactly_the_host_and_domain_and_port"] = anHttpHandler
... mySubdomain := NewMux()
... mySubdomain.Handle/HandleFunc(path, handler)
... mux.Hosts["mysubdomain.localhost:8080"] = mySubdomain
With [2] and [3] we lose the link between the parent mux,
i.e middlewares, although these can be shared by `muxie.Pre` very easly, so
this is not problem.
With [2] and [3] we win simplicity and subdomains are not even coupled to this library,
end-developer can use any http.Handler to serve those.
Simplest: [3], choose that.
^ Discarded because on advanced scenarios the end-developer may want to fetch the data
at real-time, so we need a way to execute a validator before the host handler execution.
Replaced with the `Matcher`, `Mux#Match` and `Mux#MatchHost`.
:: Usage::
Hosts is the optional map of hosts to routers.
The key is the exactly host line, i.e mysubdomain.mydomain.com:8080
including the domain and the port OR an asterix "*" to match any subdomain.
The value is any http.Handler, let's say a new muxie.NewMux() which will build
the subdomain's API.
These hosts are not sharing begin handlers registered via `Use`,
that is a choice that end-developer's have to take.
You can share middlewares between different Mux instances
by using the `muxie.Pre` to define the list of handlers that should ran everywhere
and add them to each Mux instance, i.e:
... mySharingMiddlewares := muxie.Pre(myGlobalRootLogMiddleware, ...)
... mux := muxie.NewMux()
... mux.Use(mySharingMiddlewares...)
...
... mySubdomain := muxie.NewMux()
... mySubdomain.Use(mySharingMiddlewares...)
...
... mux.Hosts["mysubdomain.localhost:8080"] = mySubdomain
*/

PathCorrection bool
Routes *Trie

paramsPool *sync.Pool

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

Expand All @@ -103,58 +48,38 @@ func NewMux() *Mux {
}
}

type Matcher interface {
http.Handler
Match(*http.Request) bool
}

type simpleMatcher struct {
matchFunc func(*http.Request) bool
handler http.Handler
}

func (m *simpleMatcher) Match(r *http.Request) bool {
return m.matchFunc(r)
}

func (m *simpleMatcher) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m.handler.ServeHTTP(w, r)
}

/* TODO THINKING:
mux.If(matcher(request)bool) -> returns a SubMux, or "when":
mux.When(muxie.Host("mysubdomain.localhost:8080")).Handle(mysubdomainHandler)
muxie.Host -> a func(string) which will implement the matcher func(request)bool
*/

func (m *Mux) If(matcher func(*http.Request) bool) SubMux {
subUnlinkedMux := NewMux()
m.matchers = append(m.matchers, &simpleMatcher{matcher, subUnlinkedMux})
return subUnlinkedMux
}

// type Host string

// func (s Host) Match(r *http.Request) bool {
// return r.Host == string(s) || string(s) == WildcardParamStart
// }

func Host(host string) func(r *http.Request) bool {
return func(r *http.Request) bool {
return r.Host == host || host == WildcardParamStart
}
// 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.
//
// The "matcherHandler"'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) Match(matchFunc func(*http.Request) bool, handler http.Handler) {
m.matchers = append(m.matchers, &simpleMatcher{matchFunc, handler})
// Match adds a matcher 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) Match(matcher Matcher, handler http.Handler) {
m.AddMatcher(&simpleMatcherHandler{
Matcher: matcher,
Handler: handler,
})
}

func (m *Mux) MatchHost(host string, handler http.Handler) {
m.Match(func(r *http.Request) bool {
return r.Host == host || host == WildcardParamStart
}, 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.
Expand Down Expand Up @@ -290,7 +215,6 @@ func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
type SubMux interface {
Of(prefix string) SubMux
Unlink() SubMux
Match(matchFunc func(*http.Request) bool, handler http.Handler)
Use(middlewares ...Wrapper)
Handle(pattern string, handler http.Handler)
HandleFunc(pattern string, handlerFunc func(http.ResponseWriter, *http.Request))
Expand Down
35 changes: 34 additions & 1 deletion request_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,67 @@ import (
)

var (
// Charset is the default content type charset for Request Processors .
Charset = "utf-8"

// JSON implements the full `Processor` interface.
// It is responsible to dispatch JSON results to the client and to read JSON
// data from the request body.
//
// Usage:
// To read from a request:
// muxie.Bind(r, muxie.JSON, &myStructValue)
// To send a response:
// muxie.Dispatch(w, muxie.JSON, mySendDataValue)
JSON = &jsonProcessor{Prefix: nil, Indent: "", UnescapeHTML: false}
XML = &xmlProcessor{Indent: ""}

// XML implements the full `Processor` interface.
// It is responsible to dispatch XML results to the client and to read XML
// data from the request body.
//
// Usage:
// To read from a request:
// muxie.Bind(r, muxie.XML, &myStructValue)
// To send a response:
// muxie.Dispatch(w, muxie.XML, mySendDataValue)
XML = &xmlProcessor{Indent: ""}
)

func withCharset(cType string) string {
return cType + "; charset=" + Charset
}

// Binder is the interface which `muxie.Bind` expects.
// It is used to bind a request to a go struct value (ptr).
type Binder interface {
Bind(*http.Request, interface{}) error
}

// Bind accepts the current request and any `Binder` to bind
// the request data to the "ptrOut".
func Bind(r *http.Request, b Binder, ptrOut interface{}) error {
return b.Bind(r, ptrOut)
}

// Dispatcher is the interface which `muxie.Dispatch` expects.
// It is used to send a response based on a go struct value.
type Dispatcher interface {
// no io.Writer because we need to set the headers here,
// Binder and Processor are only for HTTP.
Dispatch(http.ResponseWriter, interface{}) error
}

// Dispatch accepts the current response writer and any `Dispatcher`
// to send the "v" to the client.
func Dispatch(w http.ResponseWriter, d Dispatcher, v interface{}) error {
return d.Dispatch(w, v)
}

// Processor implements both `Binder` and `Dispatcher` interfaces.
// It is used for implementations that can `Bind` and `Dispatch`
// the same data form.
//
// Look `JSON` and `XML` for more.
type Processor interface {
Binder
Dispatcher
Expand Down

0 comments on commit 4eae530

Please sign in to comment.