1
0
mirror of https://github.com/labstack/echo.git synced 2024-11-28 08:38:39 +02:00

initial commit

Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
Vishal Rana 2015-03-01 09:45:13 -08:00
commit 7c9a0b6489
17 changed files with 1619 additions and 0 deletions

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 labstack
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.

78
README.md Normal file
View File

@ -0,0 +1,78 @@
# Bolt
Multi transport, multi format REST style network library
## Socket Specification
### Trasport
- WebSocket
- TCP
### Command (*1-byte*)
- INIT (**1**)
- AUTH (**2**)
- HTTP (**3**)
- PUB (**4**)
- MPUB (**5**)
- SUB (**6**)
- USUB (**7**)
### INIT
> Request
```sh
1 # Command (1-byte)
99 # Correlation ID (4-byte)
8 # Config length (2-byte)
{} # Config as JSON (n-byte)
```
> Config
```js
{
"Format":
}
```
> Response
```sh
99 # Correlation ID (4-byte)
200 # Status code (2-byte)
```
### AUTH
> Request
```sh
2 # Command (1-byte)
99 # Correlation ID (4-byte)
30 # Token length (2-byte)
1g42*jMG!a?D3eF>Xwt!dI05]Y9egP # Token (n-byte)
```
> Response
```sh
99 # Correlation ID (4-byte)
200 # Status code (2-byte)
```
### HTTP
> Request
```sh
3 # Command (1-byte)
99 # Correlation ID (4-byte)
GET\n # Method (n-byte)
/users\n # Path (n-byte)
- # Headers
64 # Body length (8-byte)
- # Body (n-byte)
```
> Response
```sh
3 # Command (1-byte)
200 # Status code (2-byte)
# For POST, PUT & PATCH
64 # Body length (8-byte)
- # Body (n-byte)
```

259
bolt.go Normal file
View File

@ -0,0 +1,259 @@
package bolt
import (
"bufio"
"io"
"log"
"net"
"net/http"
"sync"
"golang.org/x/net/websocket"
)
type (
Bolt struct {
Router *router
handlers []HandlerFunc
maxParam byte
notFoundHandler HandlerFunc
methodNotAllowedHandler HandlerFunc
tcpListener *net.TCPListener
// udpConn *net.UDPConn
pool sync.Pool
}
command byte
format byte
transport byte
HandlerFunc func(*Context)
)
const (
CmdINIT command = 1 + iota
CmdAUTH
CmdHTTP
CmdPUB
CmdSUB
CmdUSUB
)
const (
TrnspHTTP transport = 1 + iota
TrnspWS
TrnspTCP
)
const (
FmtJSON format = 1 + iota
FmtMsgPack
FmtBinary = 20
)
const (
MIME_JSON = "application/json"
MIME_MP = "application/x-msgpack"
HdrAccept = "Accept"
HdrContentDisposition = "Content-Disposition"
HdrContentLength = "Content-Length"
HdrContentType = "Content-Type"
)
var MethodMap = map[string]uint8{
"CONNECT": 1,
"DELETE": 2,
"GET": 3,
"HEAD": 4,
"OPTIONS": 5,
"PATCH": 6,
"POST": 7,
"PUT": 8,
"TRACE": 9,
}
func New(opts ...func(*Bolt)) (b *Bolt) {
b = &Bolt{
maxParam: 5,
notFoundHandler: func(c *Context) {
http.Error(c.Writer, http.StatusText(http.StatusNotFound), http.StatusNotFound)
},
methodNotAllowedHandler: func(c *Context) {
http.Error(c.Writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
},
}
b.Router = NewRouter(b)
b.pool.New = func() interface{} {
return &Context{
Writer: NewResponse(nil),
Socket: new(Socket),
params: make(Params, b.maxParam),
store: make(store),
i: -1,
}
}
// Set options
for _, o := range opts {
o(b)
}
return
}
func MaxParam(n uint8) func(*Bolt) {
return func(b *Bolt) {
b.maxParam = n
}
}
func NotFoundHandler(h HandlerFunc) func(*Bolt) {
return func(b *Bolt) {
b.notFoundHandler = h
}
}
func MethodNotAllowedHandler(h HandlerFunc) func(*Bolt) {
return func(b *Bolt) {
b.methodNotAllowedHandler = h
}
}
// Use adds middleware(s) in the chain.
func (b *Bolt) Use(h ...HandlerFunc) {
b.handlers = append(b.handlers, h...)
}
func (b *Bolt) Connect(path string, h ...HandlerFunc) {
b.Handle("CONNECT", path, h)
}
func (b *Bolt) Delete(path string, h ...HandlerFunc) {
b.Handle("DELETE", path, h)
}
func (b *Bolt) Get(path string, h ...HandlerFunc) {
b.Handle("GET", path, h)
}
func (b *Bolt) Head(path string, h ...HandlerFunc) {
b.Handle("HEAD", path, h)
}
func (b *Bolt) Options(path string, h ...HandlerFunc) {
b.Handle("OPTIONS", path, h)
}
func (b *Bolt) Patch(path string, h ...HandlerFunc) {
b.Handle("PATCH", path, h)
}
func (b *Bolt) Post(path string, h ...HandlerFunc) {
b.Handle("POST", path, h)
}
func (b *Bolt) Put(path string, h ...HandlerFunc) {
b.Handle("PUT", path, h)
}
func (b *Bolt) Trace(path string, h ...HandlerFunc) {
b.Handle("TRACE", path, h)
}
func (b *Bolt) Handle(method, path string, h []HandlerFunc) {
h = append(b.handlers, h...)
l := len(h)
b.Router.Add(method, path, func(c *Context) {
c.handlers = h
c.l = l
c.i = -1
c.Next()
})
}
func (b *Bolt) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
// Find and execute handler
h, c, s := b.Router.Find(r.Method, r.URL.Path)
if h != nil {
c.Transport = TrnspHTTP
c.Writer.ResponseWriter = rw
c.Request = r
h(c)
} else {
if s == NotFound {
b.notFoundHandler(c)
} else if s == NotAllowed {
b.methodNotAllowedHandler(c)
}
}
b.pool.Put(c)
}
func (b *Bolt) handleSocket(conn io.ReadWriteCloser, tp transport) {
// TODO: From pool?
defer conn.Close()
s := &Socket{
Transport: tp,
config: Config{},
conn: conn,
Reader: bufio.NewReader(conn),
Writer: bufio.NewWriter(conn),
bolt: b,
}
Loop:
for {
c, err := s.Reader.ReadByte() // Command
if err != nil {
log.Println(err, 222, c)
}
cmd := command(c)
println(cmd)
switch cmd {
case CmdINIT:
s.Init()
case CmdHTTP:
s.HTTP()
default:
break Loop
}
}
}
func (b *Bolt) RunHTTP(addr string) {
log.Fatal(http.ListenAndServe(addr, b))
}
func (b *Bolt) RunWS(addr string) {
http.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
b.handleSocket(ws, TrnspWS)
}))
log.Fatal(http.ListenAndServe(addr, nil))
}
func (b *Bolt) RunTCP(addr string) {
a, _ := net.ResolveTCPAddr("tcp", addr)
l, err := net.ListenTCP("tcp", a)
if err != nil {
log.Fatalf("bolt: %v", err)
}
b.tcpListener = l
go b.serve("tcp")
}
func (b *Bolt) serve(net string) {
switch net {
case "ws":
case "tcp":
for {
conn, err := b.tcpListener.Accept()
if err != nil {
log.Print(err)
return
}
go b.handleSocket(conn, TrnspTCP)
}
default:
// TODO: handle it!
}
}

145
bolt_test.go Normal file
View File

@ -0,0 +1,145 @@
package bolt
import (
"bytes"
"encoding/binary"
"encoding/json"
"io"
"net"
"sync"
"testing"
"time"
)
var u = user{
Id: "1",
Name: "Joe",
}
func startTCPServer() (b *Bolt, addr string) {
var wg sync.WaitGroup
b = New()
a, _ := net.ResolveTCPAddr("tcp", "localhost:0")
wg.Add(1)
go func() {
defer wg.Done()
b.RunTCP(a.String())
}()
wg.Wait()
addr = b.tcpListener.Addr().String()
return
}
func connectTCPServer(addr string) (conn net.Conn, err error) {
conn, err = net.DialTimeout("tcp", addr, time.Second)
return
}
func TestSocketInit(t *testing.T) {
b, addr := startTCPServer()
conn, err := connectTCPServer(addr)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
defer b.tcpListener.Close()
// Request
buf := new(bytes.Buffer)
buf.WriteByte(byte(CmdINIT)) // Command
cfg := &Config{
Format: FmtJSON,
}
bt, err := json.Marshal(cfg)
if err != nil {
t.Fatal(err)
}
binary.Write(buf, binary.BigEndian, uint16(len(bt))) // Config length
buf.Write(bt) // Config
buf.WriteTo(conn)
// Response
var n uint16
err = binary.Read(conn, binary.BigEndian, &n) // Status code
if err != nil {
t.Fatal(err)
}
if n != 200 {
t.Errorf("status code should be 200, found %d", n)
}
}
func TestSocketHTTP(t *testing.T) {
b, addr := startTCPServer()
conn, err := connectTCPServer(addr)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
defer b.tcpListener.Close()
// GET
b.Get("/users", func(c *Context) {
c.Render(200, FmtJSON, u)
})
buf := new(bytes.Buffer)
buf.WriteByte(byte(CmdHTTP)) // Command
buf.WriteString("GET\n") // Method
buf.WriteString("/users\n") // Path
buf.WriteTo(conn)
var n uint16
err = binary.Read(conn, binary.BigEndian, &n) // Status code
if err != nil {
t.Fatal(err)
}
if n != 200 {
t.Errorf("status code should be 200, found %d", n)
}
verifyUser(conn, t)
// POST
b.Post("/users", func(c *Context) {
c.Bind(c.Socket.config.Format, &user{})
c.Render(201, FmtJSON, u)
})
buf.Reset()
buf.WriteByte(byte(CmdHTTP)) // Command
buf.WriteString("POST\n") // Method
buf.WriteString("/users\n") // Path
bt, err := json.Marshal(u)
if err != nil {
t.Fatal(err)
}
binary.Write(buf, binary.BigEndian, int64(len(bt))) // Body length
buf.Write(bt) // Body
buf.WriteTo(conn)
err = binary.Read(conn, binary.BigEndian, &n) // Status code
if err != nil {
t.Fatal(err)
}
if n != 201 {
t.Errorf("status code should be 201, found %d", n)
}
verifyUser(conn, t)
}
func verifyUser(rd io.Reader, t *testing.T) {
var l int64
err := binary.Read(rd, binary.BigEndian, &l) // Body length
if err != nil {
t.Fatal(err)
}
bd := io.LimitReader(rd, l) // Body
u2 := new(user)
dec := json.NewDecoder(bd)
err = dec.Decode(u2)
if err != nil {
t.Fatal(err)
}
if u2.Id != u.Id {
t.Error("user id should be %s, found %s", u.Id, u2.Id)
}
if u2.Name != u.Name {
t.Error("user name should be %s, found %s", u.Name, u2.Name)
}
}

171
client.go Normal file
View File

@ -0,0 +1,171 @@
package bolt
import (
"bufio"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"sync"
)
type (
Client struct {
host string
port string
transport transport
socket io.ReadWriteCloser
reader *bufio.Reader
writer *bufio.Writer
httpClient HttpClient
pool sync.Pool
connected bool
}
HttpClient interface {
Do(*http.Request) (*http.Response, error)
}
)
func NewClient(opts ...func(*Client)) (c *Client) {
c = &Client{
host: "localhost",
port: "80",
transport: TrnspHTTP,
httpClient: &http.Client{},
}
c.pool.New = func() interface{} {
return &Context{
Request: &http.Request{
URL: new(url.URL),
},
// Response: new(http.Response),
// Socket: &Socket{
// Header: SocketHeader{},
// },
client: true,
}
}
// Set options
for _, o := range opts {
o(c)
}
return
}
func Transport(t transport) func(*Client) {
return func(c *Client) {
c.transport = t
}
}
func Host(h string) func(*Client) {
return func(c *Client) {
c.host = h
}
}
func Port(p string) func(*Client) {
return func(c *Client) {
c.port = p
}
}
func (c *Client) Open(cfg *Config) (err error) {
switch c.transport {
case TrnspWS:
case TrnspTCP:
c.socket, err = net.Dial("tcp", net.JoinHostPort(c.host, c.port))
if err != nil {
return fmt.Errorf("bolt: %v", err)
}
default:
return errors.New("bolt: transport not supported")
}
// Request
c.writer = bufio.NewWriter(c.socket)
c.reader = bufio.NewReader(c.socket)
c.writer.WriteByte(byte(CmdINIT)) // Command
var cid uint32 = 98
if err = binary.Write(c.writer, binary.BigEndian, cid); err != nil { // Correlation ID
log.Println(err)
}
b, err := json.Marshal(cfg)
if err != nil {
return fmt.Errorf("bolt: %v", err)
}
if err = binary.Write(c.writer, binary.BigEndian, uint16(len(b))); err != nil { // Config length
}
c.writer.Write(b) // Config
c.writer.Flush()
for {
var cid uint32
binary.Read(c.reader, binary.BigEndian, &cid) // Correlation ID
if err != nil {
return fmt.Errorf("bolt: %v", err)
}
println(cid)
break
}
// Response
var n uint16
err = binary.Read(c.reader, binary.BigEndian, &n) // Status code
if err != nil {
return fmt.Errorf("bolt: %v", err)
}
if n != 200 {
return fmt.Errorf("bolt: status=%d", n)
}
return
}
func (c *Client) Auth() {
}
func (c *Client) Connect(path string) {
}
func (c *Client) Delete() {
}
func (c *Client) Get(path string, hdr Header, hl HandlerFunc) error {
return c.Request("GET", path, hdr, hl)
}
func (c *Client) Request(method, path string, hd Header, hl HandlerFunc) (err error) {
ctx := c.pool.Get().(*Context)
ctx.Transport = c.transport
switch c.transport {
case TrnspHTTP:
ctx.Request.Method = method
// ctx.Request.Header = hd
ctx.Request.URL.Scheme = "http"
ctx.Request.URL.Host = net.JoinHostPort(c.host, c.port)
ctx.Request.URL.Path = path
ctx.Response, err = c.httpClient.Do(ctx.Request)
if err != nil {
return
}
// ctx.Header = ctx.Response.Header
hl(ctx)
case TrnspWS, TrnspTCP:
c.writer.WriteByte(byte(CmdHTTP)) // Command
c.writer.WriteString(method + "\n") // Method
c.writer.WriteString(path + "\n") // Path
if method == "POST" || method == "PUT" || method == "PATCH" {
// binary.Write(c.writer, binary.BigEndian, uint16(len(b))) // Header length
// wt.Write(b) // Header
}
c.writer.Flush()
hl(ctx)
}
return
}

102
client_test.go Normal file
View File

@ -0,0 +1,102 @@
package bolt
import (
"bytes"
"encoding/json"
"io/ioutil"
"net"
"net/http"
"os"
"testing"
"time"
)
type (
httpClient struct{}
user struct {
Id string
Name string
}
)
func mockTcpServer(method, path string) (b *Bolt) {
b = New()
b.Handle(method, path, nil)
b.RunTCP(":9999")
return
}
func mockTcpReq(status uint16, hd Header, body []byte) (c *Client) {
c = NewClient()
c.transport = TrnspTCP
// c.socket = new(bytes.Buffer)
c.socket = os.Stdout
// l := int64(len(body))
// hd.Set("Content-Length", strconv.FormatInt(l, 10))
c.socket.Write(body)
return
}
func (c *httpClient) Do(req *http.Request) (res *http.Response, err error) {
res = &http.Response{}
if req.Method == "GET" {
res.StatusCode = 200
u := &user{"1", "Joe"}
d, _ := json.Marshal(u)
res.Body = ioutil.NopCloser(bytes.NewBuffer(d))
res.Header = make(http.Header)
res.Header.Add("Accept", MIME_JSON)
}
return
}
func TestClientPost(t *testing.T) {
}
func TestClientHttpGet(t *testing.T) {
c := NewClient()
c.httpClient = &httpClient{}
hd := make(Header)
// hd.Set("Accept", MIME_JSON)
if err := c.Get("/users/1", hd, func(ctx *Context) {
u := new(user)
ctx.Decode(u)
if u.Id != "1" {
t.Error()
}
}); err != nil {
t.Error(err)
}
}
func TestTCPClient(t *testing.T) {
b, addr := startTCPServer()
defer b.tcpListener.Close()
// Open
host, port, err := net.SplitHostPort(addr)
if err != nil {
t.Fatal(err)
}
c := NewClient(Transport(TrnspTCP), Host(host), Port(port))
go func() {
err = c.Open(&Config{
Format: FmtJSON,
})
if err != nil {
t.Fatal(err)
}
}()
time.Sleep(32 * time.Millisecond)
// Get
// b.Get("/users", func(c *Context) {
// c.Render(200, FmtJSON, u)
// })
// err = c.Get("/users", nil, func(c *Context) {
// })
// time.Sleep(32 * time.Millisecond)
// if err != nil {
// t.Fatal(err)
// }
}

134
context.go Normal file
View File

@ -0,0 +1,134 @@
package bolt
import (
"encoding/binary"
"encoding/json"
"io"
"log"
"net/http"
"strconv"
)
type (
Context struct {
Transport transport
Request *http.Request
Writer *response
Response *http.Response
Socket *Socket
params Params
handlers []HandlerFunc
store map[string]interface{}
l int // Handlers' length
i int // Current handler index
client bool
}
store map[string]interface{}
)
func (c *Context) P(i uint8) string {
return c.params[i].Value
}
func (c *Context) Param(n string) string {
return c.params.Get(n)
}
func (c *Context) Bind(f format, i interface{}) (err error) {
var bd io.ReadCloser
switch c.Transport {
case TrnspHTTP:
bd = c.Request.Body
case TrnspWS, TrnspTCP:
bd = c.Socket.Body
}
switch f {
case FmtJSON:
dec := json.NewDecoder(bd)
if err = dec.Decode(i); err != nil {
log.Printf("bolt: %s", err)
}
}
return
}
func (c *Context) Decode(i interface{}) (err error) {
var rd io.Reader
switch c.Transport {
case TrnspHTTP:
rd = c.Request.Body
if c.client {
rd = c.Response.Body
defer rd.(io.Closer).Close()
}
case TrnspWS, TrnspTCP:
var cl int64
cl, err = strconv.ParseInt(c.Request.Header.Get(HdrContentLength), 10, 64)
if err != nil {
return
}
rd = io.LimitReader(c.Socket.Reader, cl)
}
t := c.Request.Header.Get("Content-Type")
if c.client {
t = c.Request.Header.Get("Accept")
}
switch t {
case MIME_MP:
default: // JSON
dec := json.NewDecoder(rd)
if err = dec.Decode(i); err != nil {
log.Println(err)
}
}
return
}
// TODO: Streaming?
func (c *Context) Encode(f format, i interface{}) (b []byte, err error) {
switch f {
case FmtJSON:
b, err = json.Marshal(i)
}
return
}
// TODO: return error
func (c *Context) Render(n int, f format, i interface{}) {
bd, err := c.Encode(f, i)
if err != nil {
log.Printf("bolt: %s", err)
}
switch c.Transport {
default:
// c.Writer.Header().Set(HEADER_CONTENT_TYPE, MIME_JSON)
// c.Writer.WriteHeader(int(n))
// c.Writer.Write(body)
case TrnspWS, TrnspTCP:
binary.Write(c.Socket.Writer, binary.BigEndian, uint16(n)) // Status code
binary.Write(c.Socket.Writer, binary.BigEndian, int64(len(bd))) // Body length
c.Socket.Writer.Write(bd) // Body
c.Socket.Writer.Flush()
}
}
// Next executes the next handler in the chain.
func (c *Context) Next() {
c.i++
if c.i < c.l {
c.handlers[c.i](c)
}
}
func (c *Context) Get(key string) interface{} {
return c.store[key]
}
func (c *Context) Set(key string, val interface{}) {
c.store[key] = val
}

24
example/h2o.conf Normal file
View File

@ -0,0 +1,24 @@
# to find out the configuration commands, run: h2o --help
listen: 8080
listen:
port: 8081
ssl:
certificate-file: examples/h2o/server.crt
key-file: examples/h2o/server.key
hosts:
"127.0.0.1.xip.io:8080":
paths:
/:
file.dir: examples/doc_root
access-log: /dev/stdout
"alternate.127.0.0.1.xip.io:8081":
listen:
port: 8081
ssl:
certificate-file: examples/h2o/alternate.crt
key-file: examples/h2o/alternate.key
paths:
/:
file.dir: examples/doc_root.alternate
access-log: /dev/stdout

43
example/main.go Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"net/http"
"github.com/labstack/bolt"
)
type user struct {
Id string
Name string
}
var users map[string]*user
func init() {
users = map[string]*user{
"1": &user{
Id: "1",
Name: "Wreck-It Ralph",
},
}
}
func createUser(c *bolt.Context) {
}
func getUsers(c *bolt.Context) {
c.Render(http.StatusOK, bolt.FMT_JSON, users)
}
func getUser(c *bolt.Context) {
c.Render(http.StatusOK, bolt.FMT_JSON, users[c.P(0)])
}
func main() {
b := bolt.New()
b.Get("/users", getUsers)
b.Get("/users/:id", getUser)
// go b.RunHttp(":8080")
// go b.RunWebSocket(":8081")
b.RunTcp(":8082")
}

69
middleware/auth.go Normal file
View File

@ -0,0 +1,69 @@
package middleware
import (
"encoding/base64"
"errors"
"strings"
"github.com/dgrijalva/jwt-go"
"github.com/labstack/bolt"
)
type (
BasicAuthFunc func(usr, pwd string) bool
AuthorizedHandler bolt.HandlerFunc
UnauthorizedHandler func(c *bolt.Context, err error)
JwtKeyFunc func(kid string) ([]byte, error)
Claims map[string]interface{}
)
var (
ErrBasicAuth = errors.New("bolt: basic auth error")
ErrJwtAuth = errors.New("bolt: jwt auth error")
)
func BasicAuth(ah AuthorizedHandler, uah UnauthorizedHandler, fn BasicAuthFunc) bolt.HandlerFunc {
return func(c *bolt.Context) {
auth := strings.Fields(c.Request.Header.Get("Authorization"))
if len(auth) == 2 {
scheme := auth[0]
s, err := base64.StdEncoding.DecodeString(auth[1])
if err != nil {
uah(c, err)
return
}
cred := strings.Split(string(s), ":")
if scheme == "Basic" && len(cred) == 2 {
if ok := fn(cred[0], cred[1]); ok {
ah(c)
return
}
}
}
uah(c, ErrBasicAuth)
}
}
func JwtAuth(ah AuthorizedHandler, uah UnauthorizedHandler, fn JwtKeyFunc) bolt.HandlerFunc {
return func(c *bolt.Context) {
auth := strings.Fields(c.Request.Header.Get("Authorization"))
if len(auth) == 2 {
t, err := jwt.Parse(auth[1], func(token *jwt.Token) (interface{}, error) {
if kid := token.Header["kid"]; kid != nil {
return fn(kid.(string))
}
return fn("")
})
if t.Valid {
c.Set("claims", Claims(t.Claims))
ah(c)
c.Next()
} else {
// TODO: capture errors
uah(c, err)
}
return
}
uah(c, ErrJwtAuth)
}
}

32
middleware/logger.go Normal file
View File

@ -0,0 +1,32 @@
package middleware
import (
"log"
"time"
"github.com/labstack/bolt"
"labstack.com/common/utils"
)
func Logger() bolt.HandlerFunc {
return func(c *bolt.Context) {
start := time.Now()
c.Next()
end := time.Now()
color := utils.Green
m := c.Request.Method
p := c.Request.URL.Path
s := c.Response.Status()
switch {
case s >= 500:
color = utils.Red
case s >= 400:
color = utils.Yellow
case s >= 300:
color = utils.Cyan
}
log.Printf("%s %s %s %s", m, p, color(s), end.Sub(start))
}
}

1
middleware/middleware.go Normal file
View File

@ -0,0 +1 @@
package middleware

67
response.go Normal file
View File

@ -0,0 +1,67 @@
package bolt
import (
"bufio"
"errors"
"net"
"net/http"
)
type (
// ResponseWriter interface {
// }
response struct {
http.ResponseWriter
status int
size int
}
)
func NewResponse(rw http.ResponseWriter) *response {
return &response{
ResponseWriter: rw,
status: http.StatusOK,
}
}
func (r *response) WriteHeader(c int) {
r.status = c
r.ResponseWriter.WriteHeader(c)
}
func (r *response) Write(b []byte) (n int, err error) {
n, err = r.ResponseWriter.Write(b)
r.size += n
return n, err
}
func (r *response) CloseNotify() <-chan bool {
cn, ok := r.ResponseWriter.(http.CloseNotifier)
if !ok {
return nil
}
return cn.CloseNotify()
}
func (r *response) Flusher() {
if f, ok := r.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}
func (r *response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
h, ok := r.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, errors.New("hijacker interface not supported")
}
return h.Hijack()
}
func (r *response) Status() int {
return r.status
}
func (r *response) Size() int {
return r.size
}

305
router.go Normal file
View File

@ -0,0 +1,305 @@
package bolt
import (
"fmt"
"net/http"
"sort"
"sync"
)
type (
router struct {
bolt *Bolt
pool sync.Pool
root *node
}
node struct {
label byte
prefix string
has ntype // Type of node(s)
handlers []HandlerFunc
edges edges
}
edges []*node
ntype byte
param struct {
Name string
Value string
}
Params []param
Status uint16
)
const (
snode ntype = iota // Static node
pnode // Param node
wnode // Wildcard node
)
const (
OK Status = iota
NotFound
NotAllowed
)
func NewRouter(b *Bolt) (r *router) {
r = &router{
bolt: b,
root: &node{
prefix: "",
handlers: make([]HandlerFunc, len(MethodMap)),
edges: edges{},
},
}
r.pool.New = func() interface{} {
return make(Params, 0, b.maxParam)
}
return
}
func (n *node) findEdge(l byte) *node {
for _, e := range n.edges {
if e.label == l {
return e
}
}
return nil
}
func (e edges) Len() int {
return len(e)
}
func (e edges) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func (e edges) Less(i, j int) bool {
return e[i].label < e[j].label
}
func (e edges) Sort() {
sort.Sort(e)
}
func (r *router) Add(method, path string, h HandlerFunc) {
i := 0
l := len(path)
for ; i < l; i++ {
if path[i] == ':' {
r.insert(method, path[:i], nil, pnode)
for ; i < l && path[i] != '/'; i++ {
}
if i == l {
r.insert(method, path[:i], h, snode)
return
} else {
r.insert(method, path[:i], nil, snode)
}
}
}
r.insert(method, path, h, snode)
}
func (r *router) insert(method, path string, h HandlerFunc, has ntype) {
cn := r.root // Current node
search := path
for {
sl := len(search)
pl := len(cn.prefix)
l := lcp(search, cn.prefix)
if l == 0 {
// At root node
cn.label = search[0]
cn.prefix = search
cn.has = has
if h != nil {
cn.handlers[MethodMap[method]] = h
}
return
} else if l < pl {
// Split the node
n := newNode(cn.prefix[l:], cn.has, cn.handlers, cn.edges)
cn.edges = edges{n} // Add to parent
// Reset parent node
cn.label = cn.prefix[0]
cn.prefix = cn.prefix[:l]
cn.has = snode
cn.handlers = make([]HandlerFunc, len(MethodMap))
if l == sl {
// At parent node
cn.handlers[MethodMap[method]] = h
} else {
// Need to fork a node
n = newNode(search[l:], has, nil, nil)
n.handlers[MethodMap[method]] = h
cn.edges = append(cn.edges, n)
}
break
} else if l < sl {
search = search[l:]
e := cn.findEdge(search[0])
if e == nil {
n := newNode(search, has, nil, nil)
if h != nil {
n.handlers[MethodMap[method]] = h
}
cn.edges = append(cn.edges, n)
break
} else {
cn = e
}
} else {
// Node already exists
if h != nil {
cn.handlers[MethodMap[method]] = h
}
break
}
}
}
func newNode(pfx string, has ntype, h []HandlerFunc, e edges) (n *node) {
n = &node{
label: pfx[0],
prefix: pfx,
has: has,
handlers: h,
edges: e,
}
if h == nil {
n.handlers = make([]HandlerFunc, len(MethodMap))
}
if e == nil {
n.edges = edges{}
}
return
}
func (r *router) Find(method, path string) (handler HandlerFunc, c *Context, s Status) {
c = r.bolt.pool.Get().(*Context)
cn := r.root // Current node
search := path
n := 0 // Param count
for {
if search == "" || search == cn.prefix {
// Node found
h := cn.handlers[MethodMap[method]]
if h != nil {
// Handler found
handler = h
} else {
s = NotAllowed
}
return
}
pl := len(cn.prefix)
l := lcp(search, cn.prefix)
if l == pl {
search = search[l:]
switch cn.has {
case pnode:
cn = cn.edges[0]
i := 0
l = len(search)
for ; i < l && search[i] != '/'; i++ {
}
p := c.params[:n+1]
p[n].Name = cn.prefix[1:]
p[n].Value = search[:i]
n++
search = search[i:]
if i == l {
// All param read
continue
}
}
e := cn.findEdge(search[0])
if e == nil {
// Not found
s = NotFound
return
} else {
cn = e
continue
}
} else {
// Not found
s = NotFound
return
}
}
}
// Length of longest common prefix
func lcp(a, b string) (i int) {
max := len(a)
l := len(b)
if l < max {
max = l
}
for ; i < max && a[i] == b[i]; i++ {
}
return
}
func (r *router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
h, c, rep := r.Find(req.Method, req.URL.Path)
c.Writer.ResponseWriter = rw
if h != nil {
h(c)
} else {
if rep == NotFound {
r.bolt.notFoundHandler(c)
} else if rep == NotAllowed {
r.bolt.methodNotAllowedHandler(c)
}
}
r.bolt.pool.Put(c)
}
func (ps Params) Get(n string) (v string) {
for _, p := range ps {
if p.Name == n {
v = p.Value
}
}
return
}
func (r *router) printTree() {
r.root.printTree("", true)
}
func (n *node) printTree(pfx string, tail bool) {
p := prefix(tail, pfx, "└── ", "├── ")
fmt.Printf("%s%s has=%d, len=%d\n", p, n.prefix, n.has, len(n.handlers))
nodes := n.edges
l := len(nodes)
p = prefix(tail, pfx, " ", "│ ")
for i := 0; i < l-1; i++ {
nodes[i].printTree(p, false)
}
if l > 0 {
nodes[l-1].printTree(p, true)
}
}
func prefix(tail bool, p, on, off string) string {
if tail {
return fmt.Sprintf("%s%s", p, on)
} else {
return fmt.Sprintf("%s%s", p, off)
}
}

46
router_test.go Normal file
View File

@ -0,0 +1,46 @@
package bolt
import "testing"
func TestStatic(t *testing.T) {
r := New().Router
r.Add("GET", "/users/joe/books", func(c *Context) {})
h, _, _ := r.Find("GET", "/users/joe/books")
if h == nil {
t.Fatal("handle not found")
}
}
func TestParam(t *testing.T) {
r := New().Router
r.Add("GET", "/users/:name", func(c *Context) {})
h, c, _ := r.Find("GET", "/users/joe")
if h == nil {
t.Fatal("handle not found")
}
p := c.Param("name")
if p != "joe" {
t.Errorf("name should be equal to joe, found %s", p)
}
}
func TestMicroParam(t *testing.T) {
r := New().Router
r.Add("GET", "/:a/:b/:c", func(c *Context) {})
h, c, _ := r.Find("GET", "/a/b/c")
if h == nil {
t.Fatal("handle not found")
}
p1 := c.P(0)
if p1 != "a" {
t.Errorf("p1 should be equal to a, found %s", p1)
}
p2 := c.P(1)
if p2 != "b" {
t.Errorf("p2 should be equal to b, found %s", p2)
}
p3 := c.P(2)
if p3 != "c" {
t.Errorf("p3 should be equal to c, found %s", p3)
}
}

101
socket.go Normal file
View File

@ -0,0 +1,101 @@
package bolt
import (
"bufio"
"encoding/binary"
"encoding/json"
"io"
"io/ioutil"
"log"
)
type (
Socket struct {
Transport transport
Header Header
Body io.ReadCloser
config Config
conn io.ReadWriteCloser
Reader *bufio.Reader
Writer *bufio.Writer
bolt *Bolt
initialized bool
}
Config struct {
Format format `json:"format,omitempty"`
}
Header map[string]string
)
func (s *Socket) Init() {
// Request
var cid uint32 // Correlation ID
err := binary.Read(s.Reader, binary.BigEndian, &cid)
if err != nil {
log.Println(err)
}
var l uint16 // Config length
err = binary.Read(s.Reader, binary.BigEndian, &l)
if err != nil {
log.Println(err)
}
rd := io.LimitReader(s.Reader, int64(l)) // Config
dec := json.NewDecoder(rd)
if err = dec.Decode(&s.config); err != nil {
log.Println(err)
}
// Response
if err = binary.Write(s.Writer, binary.BigEndian, cid); err != nil { // Correlation ID
log.Println(err)
}
if err = binary.Write(s.Writer, binary.BigEndian, uint16(200)); err != nil { // Status code
log.Println(err)
}
s.Writer.Flush()
s.initialized = true
}
func (s *Socket) Auth() {
}
func (s *Socket) HTTP() {
// Method
m, err := s.Reader.ReadString('\n')
if err != nil {
log.Println(err)
}
m = m[:len(m)-1]
// Path
p, err := s.Reader.ReadString('\n')
if err != nil {
log.Println(err)
}
p = p[:len(p)-1]
if m == "POST" || m == "PUT" || m == "PATCH" {
var l int64
err = binary.Read(s.Reader, binary.BigEndian, &l) // Body length
if err != nil {
log.Println(err)
}
s.Body = ioutil.NopCloser(io.LimitReader(s.Reader, l)) // Body
}
h, c, st := s.bolt.Router.Find(m, p)
c.Socket = s
c.Transport = s.Transport
if h != nil {
h(c)
} else {
if st == NotFound {
s.bolt.notFoundHandler(c)
} else if st == NotAllowed {
s.bolt.methodNotAllowedHandler(c)
}
}
s.bolt.pool.Put(c)
}

20
utils.go Normal file
View File

@ -0,0 +1,20 @@
package bolt
import (
"bytes"
"io"
)
type nopCloser struct {
*bytes.Buffer
}
func (nopCloser) Close() error {
return nil
}
// NopCloser returns a ReadWriteCloser with a no-op Close method wrapping
// the provided Buffer.
func NopCloser(b *bytes.Buffer) io.ReadWriteCloser {
return nopCloser{b}
}