iop/apps/control-plane/internal/wire/portal.go

114 lines
3.1 KiB
Go

package wire
import (
"context"
"fmt"
"net"
"strconv"
"time"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
"nhooyr.io/websocket"
iop "iop/proto/gen/iop"
proto_socket "git.toki-labs.com/toki/proto-socket/go"
)
// PortalServer wraps the proto-socket WebSocket server for Portal-Control Plane communication.
type PortalServer struct {
server *proto_socket.WsServer
logger *zap.Logger
host string
port int
}
// NewPortalServer creates a new PortalServer instance listening on the specified address.
func NewPortalServer(listenAddr string, logger *zap.Logger) (*PortalServer, error) {
host, portStr, err := net.SplitHostPort(listenAddr)
if err != nil {
return nil, fmt.Errorf("invalid listen address %q: %w", listenAddr, err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port in listen address %q: %w", listenAddr, err)
}
parserMap := proto_socket.ParserMap{
proto_socket.TypeNameOf(&iop.PortalHelloRequest{}): func(b []byte) (proto.Message, error) {
req := &iop.PortalHelloRequest{}
return req, proto.Unmarshal(b, req)
},
proto_socket.TypeNameOf(&iop.PortalHelloResponse{}): func(b []byte) (proto.Message, error) {
res := &iop.PortalHelloResponse{}
return res, proto.Unmarshal(b, res)
},
}
opts := proto_socket.WsServerOptions{
AcceptOptions: &websocket.AcceptOptions{
OriginPatterns: []string{
"localhost",
"localhost:*",
"127.0.0.1",
"127.0.0.1:*",
},
},
}
wsServer := proto_socket.NewWsServerWithOptions(host, port, "/portal", opts, func(conn *websocket.Conn) *proto_socket.WsClient {
client := proto_socket.NewWsClient(conn, proto_socket.DefaultHeartbeatIntervalSec, proto_socket.DefaultHeartbeatWaitSec, parserMap)
proto_socket.AddRequestListenerTyped(&client.Communicator, func(req *iop.PortalHelloRequest) (*iop.PortalHelloResponse, error) {
logger.Info("portal client hello request received",
zap.String("client_id", req.ClientId),
zap.String("client_version", req.ClientVersion),
)
return &iop.PortalHelloResponse{
Ready: true,
Protocol: Protocol,
ServerTimeUnixNano: time.Now().UnixNano(),
Message: "Hello from IOP Control Plane",
}, nil
})
return client
})
wsServer.OnClientConnected = func(client *proto_socket.WsClient) {
logger.Info("portal client connected via proto-socket WS")
}
return &PortalServer{
server: wsServer,
logger: logger,
host: host,
port: port,
}, nil
}
// Start starts the portal wire WebSocket server.
func (s *PortalServer) Start(ctx context.Context) error {
s.logger.Info("starting portal wire WS server",
zap.String("host", s.host),
zap.Int("port", s.port),
zap.String("path", "/portal"),
)
return s.server.Start(ctx)
}
// Stop stops the portal wire WebSocket server.
func (s *PortalServer) Stop() error {
s.logger.Info("stopping portal wire WS server")
return s.server.Stop()
}
// Host returns the host the server is listening on.
func (s *PortalServer) Host() string {
return s.host
}
// Port returns the port the server is listening on.
func (s *PortalServer) Port() int {
return s.port
}