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:
commit
7c9a0b6489
22
LICENSE
Normal file
22
LICENSE
Normal 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
78
README.md
Normal 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
259
bolt.go
Normal 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
145
bolt_test.go
Normal 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
171
client.go
Normal 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
102
client_test.go
Normal 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
134
context.go
Normal 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
24
example/h2o.conf
Normal 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
43
example/main.go
Normal 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
69
middleware/auth.go
Normal 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
32
middleware/logger.go
Normal 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
1
middleware/middleware.go
Normal file
@ -0,0 +1 @@
|
||||
package middleware
|
67
response.go
Normal file
67
response.go
Normal 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
305
router.go
Normal 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
46
router_test.go
Normal 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
101
socket.go
Normal 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
20
utils.go
Normal 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}
|
||||
}
|
Loading…
Reference in New Issue
Block a user