Feat: done tcp
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
inp2025
|
||||||
45
cmds/client.go
Normal file
45
cmds/client.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package cmds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"inp2025/tcp"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ping() {
|
||||||
|
msgs := []string{"hello, world", "goodbye"}
|
||||||
|
for _, msg := range msgs {
|
||||||
|
fmt.Printf("client sending: '%s'\n", msg)
|
||||||
|
|
||||||
|
b, err := tcp.Post(":8080", "/", []byte(msg))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("server reply: '%s'\n", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pull() {
|
||||||
|
socket, err := tcp.Dial(":8080", "/")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer socket.Shutdown()
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
b, err := socket.Read()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("server send: '%s'\n", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientCmd = &cobra.Command{
|
||||||
|
Use: "client",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
pull()
|
||||||
|
},
|
||||||
|
}
|
||||||
34
cmds/root.go
Normal file
34
cmds/root.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package cmds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
|
Use: "lab4",
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||||
|
viper.BindPFlags(cmd.PersistentFlags())
|
||||||
|
viper.BindPFlags(cmd.Flags())
|
||||||
|
|
||||||
|
cfg := zap.NewProductionConfig()
|
||||||
|
logger, err := cfg.Build()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.ReplaceGlobals(logger)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cobra.EnableTraverseRunHooks = true
|
||||||
|
|
||||||
|
RootCmd.AddCommand(serverCmd)
|
||||||
|
RootCmd.AddCommand(clientCmd)
|
||||||
|
}
|
||||||
40
cmds/server.go
Normal file
40
cmds/server.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package cmds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"inp2025/middlewares"
|
||||||
|
"inp2025/tcp"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pongHandler(w tcp.ResponseWriter, req *tcp.Request) error {
|
||||||
|
w.WriteHeader(tcp.StatusOK)
|
||||||
|
_, err := io.WriteString(w, string(req.Body))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func tickHandler(w tcp.ResponseWriter, req *tcp.Request) error {
|
||||||
|
zap.L().Info("Run tickHandler")
|
||||||
|
for {
|
||||||
|
w.SocketSendString(fmt.Sprintf("time: %s", time.Now().String()))
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverCmd = &cobra.Command{
|
||||||
|
Use: "server",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
router := tcp.NewRouter()
|
||||||
|
router.Use(middlewares.ErrorHandler)
|
||||||
|
router.Use(middlewares.AccessLog)
|
||||||
|
router.Register(tcp.MethodSOCKET, "/", tickHandler)
|
||||||
|
router.Register(tcp.MethodPOST, "/", pongHandler)
|
||||||
|
|
||||||
|
router.Listen(":8080")
|
||||||
|
},
|
||||||
|
}
|
||||||
26
go.mod
Normal file
26
go.mod
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module inp2025
|
||||||
|
|
||||||
|
go 1.25.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/spf13/cobra v1.10.1
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
)
|
||||||
60
go.sum
Normal file
60
go.sum
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
3
go.work.sum
Normal file
3
go.work.sum
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
7
main.go
Normal file
7
main.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "inp2025/cmds"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmds.RootCmd.Execute()
|
||||||
|
}
|
||||||
16
middlewares/accessLog.go
Normal file
16
middlewares/accessLog.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"inp2025/tcp"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AccessLog(next tcp.Handler) tcp.Handler {
|
||||||
|
return func(w tcp.ResponseWriter, req *tcp.Request) error {
|
||||||
|
zap.L().Info("access",
|
||||||
|
zap.String("method", string(req.Method)),
|
||||||
|
zap.String("route", req.Route))
|
||||||
|
return next(w, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
57
middlewares/errorHandler.go
Normal file
57
middlewares/errorHandler.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"inp2025/tcp"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
StatusCode int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
OriginError error `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(err error) Error {
|
||||||
|
return Error{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "Internal server error with unknown reason",
|
||||||
|
OriginError: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorHandler(next tcp.Handler) tcp.Handler {
|
||||||
|
return func(w tcp.ResponseWriter, req *tcp.Request) error {
|
||||||
|
originErr := next(w, req)
|
||||||
|
|
||||||
|
var err Error
|
||||||
|
switch originErr := originErr.(type) {
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case Error:
|
||||||
|
err = originErr
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = NewError(originErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.OriginError == nil {
|
||||||
|
zap.L().Warn(err.Message)
|
||||||
|
} else {
|
||||||
|
zap.L().Error(err.Message,
|
||||||
|
zap.Error(err.OriginError))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(err.StatusCode)
|
||||||
|
io.WriteString(w, err.Message)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
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