mirror of
https://github.com/jwetzell/showbridge-go.git
synced 2026-04-26 21:05:30 +00:00
initial commit
This commit is contained in:
66
cmd/cli/main.go
Normal file
66
cmd/cli/main.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jwetzell/showbridge-go"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := &cli.Command{
|
||||
Name: "showbridge",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Value: "./config.json",
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
configPath := c.String("config")
|
||||
if configPath == "" {
|
||||
return fmt.Errorf("config value cannot be empty")
|
||||
}
|
||||
|
||||
config, err := readConfig(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%+v\n", config)
|
||||
router, err := showbridge.NewRouter(ctx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router.Run()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := cmd.Run(context.Background(), os.Args)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func readConfig(configPath string) (showbridge.Config, error) {
|
||||
configBytes, err := os.ReadFile(configPath)
|
||||
|
||||
if err != nil {
|
||||
return showbridge.Config{}, err
|
||||
}
|
||||
|
||||
config := showbridge.Config{}
|
||||
|
||||
err = json.Unmarshal(configBytes, &config)
|
||||
if err != nil {
|
||||
return showbridge.Config{}, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
5
config.go
Normal file
5
config.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package showbridge
|
||||
|
||||
type Config struct {
|
||||
Protocols []ProtocolConfig `json:"protocols"`
|
||||
}
|
||||
11
config.json
Normal file
11
config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocols": [
|
||||
{
|
||||
"type": "tcp.client",
|
||||
"params": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 8000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/jwetzell/showbridge-go
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require github.com/urfave/cli/v3 v3.4.1
|
||||
10
go.sum
Normal file
10
go.sum
Normal file
@@ -0,0 +1,10 @@
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
|
||||
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
44
protocol.go
Normal file
44
protocol.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package showbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Protocol interface {
|
||||
Run(context.Context) error
|
||||
}
|
||||
|
||||
type ProtocolConfig struct {
|
||||
Type string `json:"type"`
|
||||
Params map[string]any `json:"params"`
|
||||
}
|
||||
|
||||
type ProtocolRegistration struct {
|
||||
Type string `json:"type"`
|
||||
New func(map[string]any) (Protocol, error)
|
||||
}
|
||||
|
||||
func RegisterProtocol(proto ProtocolRegistration) {
|
||||
|
||||
if proto.Type == "" {
|
||||
panic("protocol type is missing")
|
||||
}
|
||||
if proto.New == nil {
|
||||
panic("missing ProtocolInfo.New")
|
||||
}
|
||||
|
||||
protocolRegistryMu.Lock()
|
||||
defer protocolRegistryMu.Unlock()
|
||||
|
||||
if _, ok := protocolRegistry[string(proto.Type)]; ok {
|
||||
panic(fmt.Sprintf("protocol already registered: %s", proto.Type))
|
||||
}
|
||||
protocolRegistry[string(proto.Type)] = proto
|
||||
}
|
||||
|
||||
var (
|
||||
protocolRegistryMu sync.RWMutex
|
||||
protocolRegistry = make(map[string]ProtocolRegistration)
|
||||
)
|
||||
43
router.go
Normal file
43
router.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package showbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
Context context.Context
|
||||
ProtocolInstances []Protocol
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, config Config) (*Router, error) {
|
||||
|
||||
router := Router{
|
||||
Context: ctx,
|
||||
ProtocolInstances: []Protocol{},
|
||||
}
|
||||
|
||||
for _, protocolDecl := range config.Protocols {
|
||||
|
||||
protocolInfo, ok := protocolRegistry[protocolDecl.Type]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("problem loading protocol registration for protocol type: %s", protocolDecl.Type)
|
||||
}
|
||||
|
||||
protocolInstance, err := protocolInfo.New(protocolDecl.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
router.ProtocolInstances = append(router.ProtocolInstances, protocolInstance)
|
||||
|
||||
}
|
||||
|
||||
return &router, nil
|
||||
}
|
||||
|
||||
func (r *Router) Run() {
|
||||
for _, protocolInstance := range r.ProtocolInstances {
|
||||
protocolInstance.Run(r.Context)
|
||||
}
|
||||
}
|
||||
85
tcp-client.go
Normal file
85
tcp-client.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package showbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TCPClient struct {
|
||||
Host string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterProtocol(ProtocolRegistration{
|
||||
Type: "tcp.client",
|
||||
New: func(params map[string]any) (Protocol, error) {
|
||||
|
||||
host, ok := params["host"]
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tcp client requires a host parameter")
|
||||
}
|
||||
|
||||
hostString, ok := host.(string)
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tcp client host must be uint16")
|
||||
}
|
||||
|
||||
port, ok := params["port"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tcp client requires a port parameter")
|
||||
}
|
||||
|
||||
portNum, ok := port.(float64)
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tcp client port must be uint16")
|
||||
}
|
||||
|
||||
return TCPClient{Host: hostString, Port: uint16(portNum)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (ts TCPClient) Run(ctx context.Context) error {
|
||||
for {
|
||||
client, err := net.Dial("tcp", fmt.Sprintf(":%d", ts.Port))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
time.Sleep(time.Second * 2)
|
||||
continue
|
||||
}
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
READ:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
byteCount, err := client.Read(buffer)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("connection closed")
|
||||
break READ
|
||||
}
|
||||
|
||||
if byteCount > 0 {
|
||||
fmt.Println(buffer[0:byteCount])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
78
tcp-server.go
Normal file
78
tcp-server.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package showbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type TCPServer struct {
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterProtocol(ProtocolRegistration{
|
||||
Type: "tcp.server",
|
||||
New: func(params map[string]any) (Protocol, error) {
|
||||
port, ok := params["port"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tcp server requires a port parameter")
|
||||
}
|
||||
|
||||
portNum, ok := port.(float64)
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tcp server port must be uint16")
|
||||
}
|
||||
|
||||
return TCPServer{Port: uint16(portNum)}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (ts TCPServer) HandleClient(ctx context.Context, client net.Conn) {
|
||||
fmt.Printf("handling connection %s\n", client.RemoteAddr())
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
byteCount, err := client.Read(buffer)
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
fmt.Println("connection closed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if byteCount > 0 {
|
||||
fmt.Println(buffer[0:byteCount])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (ts TCPServer) Run(ctx context.Context) error {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", ts.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
client, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go ts.HandleClient(ctx, client)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user