Feat: done tcp

This commit is contained in:
2025-11-10 10:02:55 +08:00
parent 78d89595a1
commit 9a39bcda40
17 changed files with 689 additions and 0 deletions

45
tcp/client.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
}