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