fix: resolve 404 on gateway root and unify webhook routing on single port
The health server on port 18790 only handled /health and /ready, returning 404 for all other paths including /. This also meant webhook-based channels (LINE) ran on separate ports not exposed in docker-compose or firewall. Changes: - Add root `/` handler returning gateway info JSON instead of 404 - Expose health server mux so channels can register webhook routes on it - LINE channel now registers webhook on shared gateway mux (port 18790) - Channels still fall back to standalone servers if not using shared mux https://claude.ai/code/session_019vXaqxGmkdCjM8m3jp6rYj
This commit is contained in:
parent
1ea879745b
commit
7ca93d2596
4 changed files with 75 additions and 2 deletions
|
|
@ -659,17 +659,20 @@ func gatewayCmd() {
|
|||
fmt.Println("✓ Device event service started")
|
||||
}
|
||||
|
||||
// Create the gateway HTTP server first so channels can register webhooks on it
|
||||
healthServer := health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
channelManager.RegisterWebhooks(healthServer.Mux())
|
||||
|
||||
if err := channelManager.StartAll(ctx); err != nil {
|
||||
fmt.Printf("Error starting channels: %v\n", err)
|
||||
}
|
||||
|
||||
healthServer := health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
go func() {
|
||||
if err := healthServer.Start(); err != nil && err != http.ErrServerClosed {
|
||||
logger.ErrorCF("health", "Health server error", map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
}()
|
||||
fmt.Printf("✓ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
fmt.Printf("✓ Gateway listening on http://%s:%d (health, webhooks)\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
|
||||
go agentLoop.Run(ctx)
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ type LINEChannel struct {
|
|||
*BaseChannel
|
||||
config config.LINEConfig
|
||||
httpServer *http.Server
|
||||
useSharedMux bool // true when webhook is registered on the gateway mux
|
||||
botUserID string // Bot's user ID
|
||||
botBasicID string // Bot's basic ID (e.g. @216ru...)
|
||||
botDisplayName string // Bot's display name for text-based mention detection
|
||||
|
|
@ -67,6 +68,20 @@ func NewLINEChannel(cfg config.LINEConfig, messageBus *bus.MessageBus) (*LINECha
|
|||
}, nil
|
||||
}
|
||||
|
||||
// RegisterWebhook registers the LINE webhook handler on a shared HTTP mux
|
||||
// (typically the gateway mux) so no separate server is needed.
|
||||
func (c *LINEChannel) RegisterWebhook(mux *http.ServeMux) {
|
||||
path := c.config.WebhookPath
|
||||
if path == "" {
|
||||
path = "/webhook/line"
|
||||
}
|
||||
mux.HandleFunc(path, c.webhookHandler)
|
||||
c.useSharedMux = true
|
||||
logger.InfoCF("line", "LINE webhook registered on gateway", map[string]interface{}{
|
||||
"path": path,
|
||||
})
|
||||
}
|
||||
|
||||
// Start launches the HTTP webhook server.
|
||||
func (c *LINEChannel) Start(ctx context.Context) error {
|
||||
logger.InfoC("line", "Starting LINE channel (Webhook Mode)")
|
||||
|
|
@ -86,6 +101,13 @@ func (c *LINEChannel) Start(ctx context.Context) error {
|
|||
})
|
||||
}
|
||||
|
||||
// If webhook was already registered on the shared gateway mux, skip creating a standalone server
|
||||
if c.useSharedMux {
|
||||
c.setRunning(true)
|
||||
logger.InfoC("line", "LINE channel started (shared gateway webhook)")
|
||||
return nil
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
path := c.config.WebhookPath
|
||||
if path == "" {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package channels
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
|
|
@ -314,6 +315,19 @@ func (m *Manager) GetEnabledChannels() []string {
|
|||
return names
|
||||
}
|
||||
|
||||
// RegisterWebhooks registers webhook routes for channels that support HTTP webhooks
|
||||
// on the shared gateway mux, so they share the same port as the health server.
|
||||
func (m *Manager) RegisterWebhooks(mux *http.ServeMux) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if line, ok := m.channels["line"]; ok {
|
||||
if lc, ok := line.(*LINEChannel); ok {
|
||||
lc.RegisterWebhook(mux)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) RegisterChannel(name string, channel Channel) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
type Server struct {
|
||||
server *http.Server
|
||||
mux *http.ServeMux
|
||||
mu sync.RWMutex
|
||||
ready bool
|
||||
checks map[string]Check
|
||||
|
|
@ -30,14 +31,23 @@ type StatusResponse struct {
|
|||
Checks map[string]Check `json:"checks,omitempty"`
|
||||
}
|
||||
|
||||
type GatewayInfo struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Uptime string `json:"uptime"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func NewServer(host string, port int) *Server {
|
||||
mux := http.NewServeMux()
|
||||
s := &Server{
|
||||
mux: mux,
|
||||
ready: false,
|
||||
checks: make(map[string]Check),
|
||||
startTime: time.Now(),
|
||||
}
|
||||
|
||||
mux.HandleFunc("/", s.rootHandler)
|
||||
mux.HandleFunc("/health", s.healthHandler)
|
||||
mux.HandleFunc("/ready", s.readyHandler)
|
||||
|
||||
|
|
@ -52,6 +62,11 @@ func NewServer(host string, port int) *Server {
|
|||
return s
|
||||
}
|
||||
|
||||
// Mux returns the HTTP mux so channels can register webhook routes on the gateway port.
|
||||
func (s *Server) Mux() *http.ServeMux {
|
||||
return s.mux
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
s.mu.Lock()
|
||||
s.ready = true
|
||||
|
|
@ -103,6 +118,25 @@ func (s *Server) RegisterCheck(name string, checkFn func() (bool, string)) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Server) rootHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
uptime := time.Since(s.startTime)
|
||||
resp := GatewayInfo{
|
||||
Name: "PicoClaw Gateway",
|
||||
Status: "running",
|
||||
Uptime: uptime.String(),
|
||||
Version: "1.0.0",
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue