diff --git a/README.md b/README.md index c5ad8ea7..e740b2e7 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,2 @@ # 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) -``` +Coming soon... diff --git a/bolt.go b/bolt.go index abf23de1..9ec7b97a 100644 --- a/bolt.go +++ b/bolt.go @@ -1,14 +1,9 @@ package bolt import ( - "bufio" - "io" "log" - "net" "net/http" "sync" - - "golang.org/x/net/websocket" ) type ( @@ -18,37 +13,15 @@ type ( maxParam byte notFoundHandler HandlerFunc methodNotAllowedHandler HandlerFunc - tcpListener *net.TCPListener - // udpConn *net.UDPConn - pool sync.Pool + pool sync.Pool } - - command byte - format byte - transport byte - HandlerFunc func(*Context) + Format byte ) const ( - CmdINIT command = 1 + iota - CmdAUTH - CmdHTTP - CmdPUB - CmdSUB - CmdUSUB -) - -const ( - TrnspHTTP transport = 1 + iota - TrnspWS - TrnspTCP -) - -const ( - FmtJSON format = 1 + iota + FmtJSON Format = 1 + iota FmtMsgPack - FmtBinary = 20 ) const ( @@ -87,7 +60,6 @@ func New(opts ...func(*Bolt)) (b *Bolt) { b.pool.New = func() interface{} { return &Context{ Writer: NewResponse(nil), - Socket: new(Socket), params: make(Params, b.maxParam), store: make(store), i: -1, @@ -176,7 +148,6 @@ 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) @@ -190,70 +161,6 @@ func (b *Bolt) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 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) { +func (b *Bolt) Run(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! - } -} diff --git a/bolt_test.go b/bolt_test.go index 5958246c..b8efa68d 100644 --- a/bolt_test.go +++ b/bolt_test.go @@ -1,14 +1,17 @@ package bolt import ( - "bytes" "encoding/binary" "encoding/json" "io" - "net" - "sync" "testing" - "time" +) + +type ( + user struct { + Id string + Name string + } ) var u = user{ @@ -16,113 +19,6 @@ var u = user{ 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 diff --git a/client.go b/client.go deleted file mode 100644 index 8b438844..00000000 --- a/client.go +++ /dev/null @@ -1,171 +0,0 @@ -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 -} diff --git a/client_test.go b/client_test.go deleted file mode 100644 index 1ee773c2..00000000 --- a/client_test.go +++ /dev/null @@ -1,102 +0,0 @@ -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) - // } -} diff --git a/context.go b/context.go index 0e44d6a4..36fa453d 100644 --- a/context.go +++ b/context.go @@ -1,27 +1,22 @@ package bolt import ( - "encoding/binary" "encoding/json" - "io" + "fmt" "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 + Request *http.Request + Writer *response + Response *http.Response + params Params + handlers []HandlerFunc + store map[string]interface{} + l int // Handlers' length + i int // Current handler index } store map[string]interface{} ) @@ -34,17 +29,10 @@ 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 - } +func (c *Context) Bind(f Format, i interface{}) (err error) { switch f { case FmtJSON: - dec := json.NewDecoder(bd) + dec := json.NewDecoder(c.Request.Body) if err = dec.Decode(i); err != nil { log.Printf("bolt: %s", err) } @@ -52,69 +40,20 @@ func (c *Context) Bind(f format, i interface{}) (err error) { 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) { +// TODO: return error, streaming? +func (c *Context) Render(n int, f Format, i interface{}) (err error) { + var body []byte switch f { case FmtJSON: - b, err = json.Marshal(i) + body, 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() + return fmt.Errorf("bolt: %s", err) } + // c.Writer.Header().Set(HEADER_CONTENT_TYPE, MIME_JSON) + c.Writer.WriteHeader(n) + _, err = c.Writer.Write(body) + return } // Next executes the next handler in the chain. diff --git a/example/h2o.conf b/example/h2o.conf deleted file mode 100644 index 52749c51..00000000 --- a/example/h2o.conf +++ /dev/null @@ -1,24 +0,0 @@ -# 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 diff --git a/socket.go b/socket.go deleted file mode 100644 index 3e39a82a..00000000 --- a/socket.go +++ /dev/null @@ -1,101 +0,0 @@ -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) -} diff --git a/utils.go b/utils.go deleted file mode 100644 index 5e022ff0..00000000 --- a/utils.go +++ /dev/null @@ -1,20 +0,0 @@ -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} -}