Skip to content

Commit

Permalink
Say hello to 'Muxie'. HTTP Router that brings a lot to the game while…
Browse files Browse the repository at this point in the history
… keeping the tradition of being the person who designs for the tomorrow, offering uncomparable performance and features
  • Loading branch information
kataras committed Oct 15, 2018
0 parents commit f91153b
Show file tree
Hide file tree
Showing 20 changed files with 1,654 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.go linguist-language=Go
* text=auto
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
language: go
os:
- linux
- osx
go:
- 1.9.x
- 1.10.x
- 1.11.x
go_import_path: github.com/kataras/muxie
install:
- go get ./...
script:
- go test -v -cover ./...
after_script:
# examples
- cd ./_examples
- go get ./...
- go test -v -cover ./...
- cd ../
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2018 Gerasimos Maropoulos <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
190 changes: 190 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<h1 align="center">Muxie</h1>

<div align="center">
:steam_locomotive::train::train::train::train::train:
</div>
<div align="center">
<strong>Fast trie implementation designed from scratch specifically for HTTP</strong>
</div>
<div align="center">
A <code>small and light</code> router for creating sturdy backend <a href="https://golang.org" alt="Go Programming Language">Go</a> applications
</div>

<br />

<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"
alt="Release/stability" />
</a>
<!-- Build Status -->
<a href="https://travis-ci.org/kataras/muxie">
<img src="https://img.shields.io/travis/kataras/muxie/master.svg?style=flat-square"
alt="Build Status" />
</a>
<!-- Report Card -->
<a href="https://goreportcard.com/report/github.com/kataras/muxie">
<img src="https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square"
alt="Report Card" />
</a>
<!-- Examples -->
<a href="https://github.com/kataras/muxie/tree/master/_examples">
<img src="https://img.shields.io/badge/learn%20by-examples-yellow.svg?style=flat-square"
alt="Example" />
</a>
<!-- Built for Iris -->
<a href="https://github.com/kataras/iris">
<img src="https://img.shields.io/badge/built%20for-iris-0077b3.svg?style=flat-square"
alt="Built for Iris" />
</a>
</div>


<div align="center">
<sub>The little router that could. Built with ❤︎ by
<a href="https://twitter.com/MakisMaropoulos">Gerasimos Maropoulos</a>
</div>

## Features

- __trie based:__ performance and useness are first class citizens, Muxie is based on the prefix tree data structure, designed from scratch and built for HTTP, and it is among the fastest outhere, if not the fastest one
- __grouping:__ group common routes based on their path prefixes
- __no external dependencies:__ weighing `30kb`, Muxie is a tiny little library without external dependencies
- __closet wildcard resolution and custom 404:__ wildcards, named parameters and static paths can all live and play together nice and fast in the same path prefix(!)
- __small api:__ with only 3 main methods for HTTP there's not much to learn
- __compatibility:__ built to be 100% compatible with the `net/http` standard package

## Installation

The only requirement is the [Go Programming Language](https://golang.org/dl/)

```sh
$ go get -u github.com/kataras/muxie
```

## Example

```go
package main

import (
"fmt"
"net/http"

"github.com/kataras/muxie"
)

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

mux.HandleFunc("/", indexHandler)
// Root wildcards, can be used for site-level custom not founds(404).
mux.HandleFunc("/*path", notFoundHandler)

// Grouping.
profile := mux.Of("/profile")
profile.HandleFunc("/:name", profileHandler)
profile.HandleFunc("/:name/photos", profilePhotosHandler)
// Wildcards can be used for prefix-level custom not found handler as well,
// order does not matter.
profile.HandleFunc("/*path", profileNotFoundHandler)

// Dynamic paths with named parameters and wildcards or all together!
mux.HandleFunc("/uploads/*file", listUploadsHandler)

mux.HandleFunc("/uploads/:uploader", func(w http.ResponseWriter, r *http.Request) {
uploader := muxie.GetParam(w, "uploader")
fmt.Fprintf(w, "Hello Uploader: '%s'", uploader)
})

mux.HandleFunc("/uploads/info/*file", func(w http.ResponseWriter, r *http.Request) {
file := muxie.GetParam(w, "file")
fmt.Fprintf(w, "File info of: '%s'", file)
})

mux.HandleFunc("/uploads/totalsize", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Uploads total size is 4048")
})

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

func notFoundHandler(w http.ResponseWriter, r *http.Request) {
requestPath := muxie.GetParam(w, "path")
// or r.URL.Path, we are in the root so it doesn't really matter.

fmt.Fprintf(w, "Global Site Page of: '%s' not found", requestPath)
}

func profileNotFoundHandler(w http.ResponseWriter, r *http.Request) {
requestSubPath := muxie.GetParam(w, "path")
// requestSubPath = everyhing else after "http://localhost:8080/profile/..."
// but not /profile/:name or /profile/:name/photos because those will
// be handled by the above route handlers we registered previously.

fmt.Fprintf(w, "Profile Page of: '%s' not found", requestSubPath)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf8")
fmt.Fprintf(w, "This is the <strong>%s</strong>", "index page")
}

func profileHandler(w http.ResponseWriter, r *http.Request) {
name := muxie.GetParam(w, "name")
fmt.Fprintf(w, "Profile of: '%s'", name)
}

func profilePhotosHandler(w http.ResponseWriter, r *http.Request) {
name := muxie.GetParam(w, "name")
fmt.Fprintf(w, "Photos of: '%s'", name)
}

func listUploadsHandler(w http.ResponseWriter, r *http.Request) {
file := muxie.GetParam(w, "file")
fmt.Fprintf(w, "Showing file: '%s'", file)
}

```
Want to see more examples and documentation? Check out the [examples](_examples).

## Philosophy

I believe that providing the right tools for the right job represents my best self
and I really enjoy writing small libraries and even frameworks that can be used and learnt by thousands like me.
I do it for the past two and a half years and I couldn't be more happy and proud for myself.

[Iris](https://github.com/kataras/iris) is a web backend framework for Go that is well-known in the Go community,
some of you hated it due to a "battle" between "competitors" followed by a single article written almost three years ago but the majority of you really love it so much that you recommend it to your co-workers, use it inside your companies, startups or your client's projects or even write your postgraduate dissertation based on your own experience with Iris. Both categories of fans
gave me enough reasons to continue and overcome myself day by day.

It was about the first days of September(2018) that I decided to start working on the next Iris release(version 11) and all job interviews postponed indefinitely.
If you have ever seen or hear about Iris, you already know that Iris is one of the fastest and easy-to-use frameworks, this is why it became so famous in so little time after all.

A lot improvements were pushed over a month+ working full-time on Iris.
I have never seen a router or a framework supports so many patterns as the current Iris' internal router that is exposed by a beautiful API. However, I couldn't release it for the public yet, I felt that something was missing, I believed that I could do its router smarter and even faster(!) and that ate my guts. And then...in early October, after a lot of testing(and drinking) I found the missing part, it was that the routes' parameterized paths, wildcards and statics all-together for the same path prefix cannot play as fast as possible and good as they should, also I realised that the internal router's code was not the best ever written (it was written to be extremely fast and I didn't care about readability so much back then, when I finally made it to work faster than the rest I forgot to "prettify" it due to my excitement!)

Initially the `trie.go` and `node.go` were written for the Iris web framework's version 11 as you can understand by now, I believe that programming should be fun and not stressful, especially for new Gophers. So here we are, introducing a new autonomous Go-based mux(router) that it is light, fast and easy to use for all Gophers, not just for Iris users/developers.

The `kataras/muxie` repository contains the full source code of my trie implementation and the HTTP component(`muxie.NewMux()`) which is fully compatible with the `net/http` package. Users of this package are not limited on HTTP, they can use it to store and search simple key-value data into their programs (`muxie.NewTrie()`).


- The trie implementation is easy to read, and if it is not for you please send me a message to explain to you personally
- The API is simple, just three main methods and the two of them are the well-known `Handle` and `HandleFunc`, identically to the std package's `net/http#ServeMux`
- Implements a way to store parameters without touching the `*http.Request` and change the standard handler definition by introducing a new type such as a Context or slow the whole HTTP serve process because of it, look the `GetParam` function and the internal `paramsWriter` structure that it is created and used inside the `Mux#ServeHTTP`
- Besides the HTTP main functionality that this package offers, users should be able to use it for other things as well, the API is exposed as much as you walk through to
- Supports named parameters and wildcards of course
- Supports static path segments(parts, nodes) and named parameters and wildcards for the same path prefix without losing a bit of performance, unlike others that by-design they can't even do it

For the hesitants: There is a [public repository](https://github.com/kataras/trie-examples-to-remember-again) (previously private) that you can follow the whole process of coding and designing until the final result of `kataras/muxie`'s.

And... never forget to put some fun in your life ❤︎

Yours,<br />
Gerasimos Maropoulos ([@MakisMaropoulos](https://twitter.com/MakisMaropoulos))

## License
[MIT](https://tldrlegal.com/license/mit-license)
36 changes: 36 additions & 0 deletions _examples/1_hello_world/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"fmt"
"net/http"

"github.com/kataras/muxie"
)

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

// if it is true the /about/ will be permantly redirected to /about and served from the aboutHandler.
// mux.PathCorrection = true

mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/index", indexHandler)
mux.HandleFunc("/about", aboutHandler)

fmt.Println(`Server started at :8080
Open your browser or any other HTTP Client and navigate to:
http://localhost:8080
http://localhost:8080/index and
http://localhost:8080/about`)

http.ListenAndServe(":8080", mux)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf8")
fmt.Fprintf(w, "This is the <strong>%s</strong>", "index page")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Simple example to show how easy is to add routes with static paths.\nVisit the 'parameterized' example folder for more...\n"))
}
85 changes: 85 additions & 0 deletions _examples/2_parameterized/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"fmt"
"net/http"

"github.com/kataras/muxie"
)

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

// static root, matches http://localhost:8080
// or http://localhost:8080/ (even if PathCorrection is false).
mux.HandleFunc("/", indexHandler)

// named parameter, matches /profile/$something_here
// but NOT /profile/anything/here neither /profile
// and /profile/ (if PathCorrection is true).
mux.HandleFunc("/profile/:name", profileHandler)

// named parameter followed by static segmet, matches /profile/$something_here/photos
// but NOT /profile/photos neither /profile/$somethinng_here
// and /profile/$something_here/photos/ (if PathCorrection is false).
mux.HandleFunc("/profile/:name/photos", profilePhotosHandler)

// wildcard, matches everything else after /uploads or /uploads/,
// the param value of the "file" is all the path segments but without the first slash.
mux.HandleFunc("/uploads/*file", listUploadsHandler)

// named parameter in the same prefix as our previous registered wildcard
// followed by static part (yes, this is also possible here!),
// this has a priority over the /uploads/*file,
// so if only /uploads/$something_here without other path segments
// then it will fire the below handler:
mux.HandleFunc("/uploads/:uploader", func(w http.ResponseWriter, r *http.Request) {
uploader := muxie.GetParam(w, "uploader")
fmt.Fprintf(w, "Hello Uploader: '%s'", uploader)
})

// static part followed by another wildcard in the same prefix as our previous registered wildcard,
// and... yes, it is possible when you use muxie!
mux.HandleFunc("/uploads/info/*file", func(w http.ResponseWriter, r *http.Request) {
file := muxie.GetParam(w, "file")
fmt.Fprintf(w, "File info of: '%s'", file)
})

// static part in the same path prefix as our previous registered wildcard and named parameter
// (yes, this ia also possible here!).
// This has priority over everyhing else after /uploads and /uploads/ (if PathCorrection is true).
mux.HandleFunc("/uploads/totalsize", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Uploads total size is 4048")
})

// At the last 4 routes you see that this customized trie-based mux
// has noumerous features and it is fast in the same time, if not the fastest!
// You also learnt that you can use the "closest wildcard resolution" (/path/*myparam)
// to do actions like custom 404 pages if nothing else found,
// 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")
http.ListenAndServe(":8080", mux)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf8")
fmt.Fprintf(w, "This is the <strong>%s</strong>", "index page")
}

func profileHandler(w http.ResponseWriter, r *http.Request) {
name := muxie.GetParam(w, "name")
fmt.Fprintf(w, "Profile of: '%s'", name)
}

func profilePhotosHandler(w http.ResponseWriter, r *http.Request) {
name := muxie.GetParam(w, "name")
fmt.Fprintf(w, "Photos of: '%s'", name)
}

func listUploadsHandler(w http.ResponseWriter, r *http.Request) {
file := muxie.GetParam(w, "file")
fmt.Fprintf(w, "Showing file: '%s'", file)
}
Loading

0 comments on commit f91153b

Please sign in to comment.