Feat: done tcp
This commit is contained in:
45
tcp/client.go
Normal file
45
tcp/client.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
)
|
||||
|
||||
func Do(addr, route string, method Method, body []byte) ([]byte, error) {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
defer conn.Close()
|
||||
header := RequestHeader{
|
||||
Method: method,
|
||||
Route: route,
|
||||
}
|
||||
|
||||
rawHeader, err := json.Marshal(header)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
if err := sendFrame(conn, rawHeader); err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
if err := sendFrame(conn, body); err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
b, err := readFrame(conn)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Get(addr, route string) ([]byte, error) {
|
||||
return Do(addr, route, MethodGET, []byte{})
|
||||
}
|
||||
|
||||
func Post(addr, route string, b []byte) ([]byte, error) {
|
||||
return Do(addr, route, MethodPOST, b)
|
||||
}
|
||||
53
tcp/request.go
Normal file
53
tcp/request.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Method string
|
||||
|
||||
const (
|
||||
MethodGET = "GET"
|
||||
MethodPOST = "POST"
|
||||
MethodPUT = "PUT"
|
||||
MethodDELETE = "DELETE"
|
||||
|
||||
MethodSOCKET = "SOCKET"
|
||||
)
|
||||
|
||||
type RequestHeader struct {
|
||||
Method Method `json:"method"`
|
||||
Route string `json:"route"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Method Method
|
||||
Route string
|
||||
|
||||
RemoteAddr string
|
||||
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (self *Request) Header() ([]byte, error) {
|
||||
b, err := json.Marshal(RequestHeader{
|
||||
Method: self.Method,
|
||||
Route: self.Route,
|
||||
})
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func NewRequest(conn net.Conn, header RequestHeader, body []byte) *Request {
|
||||
return &Request{
|
||||
Method: header.Method,
|
||||
Route: header.Route,
|
||||
|
||||
RemoteAddr: conn.RemoteAddr().String(),
|
||||
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
90
tcp/response.go
Normal file
90
tcp/response.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusOK int = 200
|
||||
|
||||
StatusBadRequest = 400
|
||||
StatusUnauthorized = 401
|
||||
StatusForbidden = 403
|
||||
StatusNotFound = 404
|
||||
|
||||
StatusInternalServerError = 500
|
||||
)
|
||||
|
||||
type ResponseHeader struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
|
||||
Body []byte
|
||||
|
||||
RemoteAddr string
|
||||
}
|
||||
|
||||
func (self *Response) Header() ([]byte, error) {
|
||||
b, err := json.Marshal(ResponseHeader{
|
||||
StatusCode: self.StatusCode,
|
||||
})
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type ResponseWriter interface {
|
||||
Write([]byte) (int, error)
|
||||
WriteHeader(statusCode int)
|
||||
SocketSend([]byte) error
|
||||
SocketSendString(string) error
|
||||
}
|
||||
|
||||
type ResponseBuilder struct {
|
||||
// For socket to send without closing connection
|
||||
conn net.Conn
|
||||
|
||||
b strings.Builder
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func NewResponseBuilder(conn net.Conn) *ResponseBuilder {
|
||||
return &ResponseBuilder{
|
||||
conn: conn,
|
||||
|
||||
statusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ResponseBuilder) Write(b []byte) (int, error) {
|
||||
return self.b.Write(b)
|
||||
}
|
||||
|
||||
func (self *ResponseBuilder) WriteHeader(statusCode int) {
|
||||
self.statusCode = statusCode
|
||||
}
|
||||
|
||||
func (self *ResponseBuilder) Build(req *Request) *Response {
|
||||
return &Response{
|
||||
StatusCode: self.statusCode,
|
||||
|
||||
RemoteAddr: req.RemoteAddr,
|
||||
|
||||
Body: []byte(self.b.String()),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ResponseBuilder) SocketSend(b []byte) error {
|
||||
return sendFrame(self.conn, b)
|
||||
}
|
||||
|
||||
func (self *ResponseBuilder) SocketSendString(s string) error {
|
||||
return self.SocketSend([]byte(s))
|
||||
}
|
||||
114
tcp/router.go
Normal file
114
tcp/router.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Handler func(w ResponseWriter, req *Request) error
|
||||
type Middleware func(next Handler) Handler
|
||||
|
||||
type Router struct {
|
||||
middlewares []Middleware
|
||||
routes map[Method]map[string]Handler
|
||||
}
|
||||
|
||||
func NewRouter() *Router {
|
||||
return &Router{
|
||||
routes: make(map[Method]map[string]Handler),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Router) Use(middleware Middleware) {
|
||||
self.middlewares = append(self.middlewares, middleware)
|
||||
}
|
||||
|
||||
func (self *Router) Register(method Method, route string, handler Handler) {
|
||||
_, ok := self.routes[method]
|
||||
if !ok {
|
||||
self.routes[method] = make(map[string]Handler)
|
||||
}
|
||||
|
||||
for _, middleware := range self.middlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
self.routes[method][route] = handler
|
||||
}
|
||||
|
||||
func (self *Router) run(conn net.Conn, req *Request) {
|
||||
handler, ok := self.routes[req.Method][req.Route]
|
||||
if !ok {
|
||||
zap.L().Warn("route not exist",
|
||||
zap.String("route", req.Route))
|
||||
return
|
||||
}
|
||||
|
||||
w := NewResponseBuilder(conn)
|
||||
err := handler(w, req)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to run handler",
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
res := w.Build(req)
|
||||
header, err := res.Header()
|
||||
if err != nil {
|
||||
zap.L().Error("failed to marshal header",
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err := sendFrame(conn, header); err != nil {
|
||||
zap.L().Error("failed to write header",
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err := sendFrame(conn, res.Body); err != nil {
|
||||
zap.L().Error("failed to write body",
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Router) Listen(addr string) error {
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawHeader, err := readFrame(conn)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := readFrame(conn)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var header RequestHeader
|
||||
if err := json.Unmarshal(rawHeader, &header); err != nil {
|
||||
return err
|
||||
}
|
||||
req := NewRequest(conn, header, body)
|
||||
go self.run(conn, req)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
51
tcp/socket.go
Normal file
51
tcp/socket.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ShutdownFunc func() error
|
||||
|
||||
type SocketConn struct {
|
||||
conn net.Conn
|
||||
Shutdown ShutdownFunc
|
||||
}
|
||||
|
||||
func (self *SocketConn) Read() ([]byte, error) {
|
||||
return readFrame(self.conn)
|
||||
}
|
||||
|
||||
func (self *SocketConn) Write(b []byte) error {
|
||||
return sendFrame(self.conn, b)
|
||||
}
|
||||
|
||||
func Dial(addr, route string) (*SocketConn, error) {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header := RequestHeader{
|
||||
Method: MethodSOCKET,
|
||||
Route: route,
|
||||
}
|
||||
|
||||
rawHeader, err := json.Marshal(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := sendFrame(conn, rawHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Empty body
|
||||
if err := sendFrame(conn, []byte{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SocketConn{
|
||||
conn: conn,
|
||||
Shutdown: func() error {
|
||||
return conn.Close()
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
44
tcp/utils.go
Normal file
44
tcp/utils.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
LengthPrefixSize = 4
|
||||
)
|
||||
|
||||
func readFrame(conn net.Conn) ([]byte, error) {
|
||||
lengthBytes := make([]byte, LengthPrefixSize)
|
||||
_, err := io.ReadFull(conn, lengthBytes)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
messageLength := binary.BigEndian.Uint32(lengthBytes)
|
||||
|
||||
payload := make([]byte, messageLength)
|
||||
_, err = io.ReadFull(conn, payload)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func sendFrame(conn net.Conn, data []byte) error {
|
||||
messageLength := uint32(len(data))
|
||||
|
||||
lengthBytes := make([]byte, LengthPrefixSize)
|
||||
binary.BigEndian.PutUint32(lengthBytes, messageLength)
|
||||
|
||||
_, err := conn.Write(lengthBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(data)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user