tcp.go raw

   1  // Package tcp provides a plain HTTP transport for the relay.
   2  package tcp
   3  
   4  import (
   5  	"context"
   6  	"fmt"
   7  	"net/http"
   8  	"sync"
   9  
  10  	"next.orly.dev/pkg/lol/log"
  11  )
  12  
  13  // Config holds TCP transport configuration.
  14  type Config struct {
  15  	// Addr is the listen address (e.g., "0.0.0.0:3334").
  16  	Addr string
  17  	// Handler is the HTTP handler to serve.
  18  	Handler http.Handler
  19  }
  20  
  21  // Transport serves HTTP over plain TCP.
  22  type Transport struct {
  23  	cfg    *Config
  24  	server *http.Server
  25  	mu     sync.Mutex
  26  }
  27  
  28  // New creates a new TCP transport.
  29  func New(cfg *Config) *Transport {
  30  	return &Transport{cfg: cfg}
  31  }
  32  
  33  func (t *Transport) Name() string { return "tcp" }
  34  
  35  func (t *Transport) Start(ctx context.Context) error {
  36  	t.mu.Lock()
  37  	defer t.mu.Unlock()
  38  
  39  	t.server = &http.Server{
  40  		Addr:    t.cfg.Addr,
  41  		Handler: t.cfg.Handler,
  42  	}
  43  
  44  	log.I.F("starting listener on http://%s", t.cfg.Addr)
  45  
  46  	errCh := make(chan error, 1)
  47  	go func() {
  48  		if err := t.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  49  			errCh <- err
  50  		}
  51  		close(errCh)
  52  	}()
  53  
  54  	// Give the server a moment to fail on bind errors
  55  	select {
  56  	case err := <-errCh:
  57  		if err != nil {
  58  			return fmt.Errorf("tcp listen on %s: %w", t.cfg.Addr, err)
  59  		}
  60  	default:
  61  	}
  62  
  63  	return nil
  64  }
  65  
  66  func (t *Transport) Stop(ctx context.Context) error {
  67  	t.mu.Lock()
  68  	defer t.mu.Unlock()
  69  
  70  	if t.server == nil {
  71  		return nil
  72  	}
  73  	return t.server.Shutdown(ctx)
  74  }
  75  
  76  func (t *Transport) Addresses() []string {
  77  	return []string{"ws://" + t.cfg.Addr + "/"}
  78  }
  79