refactor(channels): consolidate HTTP servers into shared server managed by Manager
Merge 3 independent channel HTTP servers (LINE :18791, WeCom Bot :18793, WeCom App :18792) and the health server (:18790) into a single shared HTTP server on the Gateway address. Channels implement WebhookHandler and/or HealthChecker interfaces to register their handlers on the shared mux. Also change Gateway default host from 0.0.0.0 to 127.0.0.1 for security.
This commit is contained in:
parent
cc92a62812
commit
d1551dc423
7 changed files with 166 additions and 124 deletions
|
|
@ -6,7 +6,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -208,16 +207,15 @@ func gatewayCmd() {
|
||||||
fmt.Println("✓ Device event service started")
|
fmt.Println("✓ Device event service started")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup shared HTTP server with health endpoints and webhook handlers
|
||||||
|
healthServer := health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port)
|
||||||
|
addr := fmt.Sprintf("%s:%d", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||||
|
channelManager.SetupHTTPServer(addr, healthServer)
|
||||||
|
|
||||||
if err := channelManager.StartAll(ctx); err != nil {
|
if err := channelManager.StartAll(ctx); err != nil {
|
||||||
fmt.Printf("Error starting channels: %v\n", err)
|
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]any{"error": err.Error()})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fmt.Printf("✓ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
fmt.Printf("✓ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||||
|
|
||||||
go agentLoop.Run(ctx)
|
go agentLoop.Run(ctx)
|
||||||
|
|
@ -229,12 +227,11 @@ func gatewayCmd() {
|
||||||
fmt.Println("\nShutting down...")
|
fmt.Println("\nShutting down...")
|
||||||
cancel()
|
cancel()
|
||||||
msgBus.Close()
|
msgBus.Close()
|
||||||
healthServer.Stop(context.Background())
|
channelManager.StopAll(ctx)
|
||||||
deviceService.Stop()
|
deviceService.Stop()
|
||||||
heartbeatService.Stop()
|
heartbeatService.Stop()
|
||||||
cronService.Stop()
|
cronService.Stop()
|
||||||
agentLoop.Stop()
|
agentLoop.Stop()
|
||||||
channelManager.StopAll(ctx)
|
|
||||||
fmt.Println("✓ Gateway stopped")
|
fmt.Println("✓ Gateway stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ type replyTokenEntry struct {
|
||||||
type LINEChannel struct {
|
type LINEChannel struct {
|
||||||
*channels.BaseChannel
|
*channels.BaseChannel
|
||||||
config config.LINEConfig
|
config config.LINEConfig
|
||||||
httpServer *http.Server
|
|
||||||
botUserID string // Bot's user ID
|
botUserID string // Bot's user ID
|
||||||
botBasicID string // Bot's basic ID (e.g. @216ru...)
|
botBasicID string // Bot's basic ID (e.g. @216ru...)
|
||||||
botDisplayName string // Bot's display name for text-based mention detection
|
botDisplayName string // Bot's display name for text-based mention detection
|
||||||
|
|
@ -68,7 +67,7 @@ func NewLINEChannel(cfg config.LINEConfig, messageBus *bus.MessageBus) (*LINECha
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start launches the HTTP webhook server.
|
// Start initializes the LINE channel.
|
||||||
func (c *LINEChannel) Start(ctx context.Context) error {
|
func (c *LINEChannel) Start(ctx context.Context) error {
|
||||||
logger.InfoC("line", "Starting LINE channel (Webhook Mode)")
|
logger.InfoC("line", "Starting LINE channel (Webhook Mode)")
|
||||||
|
|
||||||
|
|
@ -87,31 +86,6 @@ func (c *LINEChannel) Start(ctx context.Context) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
path := c.config.WebhookPath
|
|
||||||
if path == "" {
|
|
||||||
path = "/webhook/line"
|
|
||||||
}
|
|
||||||
mux.HandleFunc(path, c.webhookHandler)
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", c.config.WebhookHost, c.config.WebhookPort)
|
|
||||||
c.httpServer = &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
logger.InfoCF("line", "LINE webhook server listening", map[string]any{
|
|
||||||
"addr": addr,
|
|
||||||
"path": path,
|
|
||||||
})
|
|
||||||
if err := c.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
logger.ErrorCF("line", "Webhook server error", map[string]any{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.SetRunning(true)
|
c.SetRunning(true)
|
||||||
logger.InfoC("line", "LINE channel started (Webhook Mode)")
|
logger.InfoC("line", "LINE channel started (Webhook Mode)")
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -151,7 +125,7 @@ func (c *LINEChannel) fetchBotInfo() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop gracefully shuts down the HTTP server.
|
// Stop gracefully stops the LINE channel.
|
||||||
func (c *LINEChannel) Stop(ctx context.Context) error {
|
func (c *LINEChannel) Stop(ctx context.Context) error {
|
||||||
logger.InfoC("line", "Stopping LINE channel")
|
logger.InfoC("line", "Stopping LINE channel")
|
||||||
|
|
||||||
|
|
@ -159,21 +133,24 @@ func (c *LINEChannel) Stop(ctx context.Context) error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.httpServer != nil {
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := c.httpServer.Shutdown(shutdownCtx); err != nil {
|
|
||||||
logger.ErrorCF("line", "Webhook server shutdown error", map[string]any{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetRunning(false)
|
c.SetRunning(false)
|
||||||
logger.InfoC("line", "LINE channel stopped")
|
logger.InfoC("line", "LINE channel stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebhookPath returns the path for registering on the shared HTTP server.
|
||||||
|
func (c *LINEChannel) WebhookPath() string {
|
||||||
|
if c.config.WebhookPath != "" {
|
||||||
|
return c.config.WebhookPath
|
||||||
|
}
|
||||||
|
return "/webhook/line"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements http.Handler for the shared HTTP server.
|
||||||
|
func (c *LINEChannel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.webhookHandler(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
// webhookHandler handles incoming LINE webhook requests.
|
// webhookHandler handles incoming LINE webhook requests.
|
||||||
func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
|
func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -19,6 +20,7 @@ import (
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/constants"
|
"github.com/sipeed/picoclaw/pkg/constants"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/health"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
"github.com/sipeed/picoclaw/pkg/media"
|
"github.com/sipeed/picoclaw/pkg/media"
|
||||||
"github.com/sipeed/picoclaw/pkg/utils"
|
"github.com/sipeed/picoclaw/pkg/utils"
|
||||||
|
|
@ -55,6 +57,8 @@ type Manager struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
mediaStore media.MediaStore
|
mediaStore media.MediaStore
|
||||||
dispatchTask *asyncTask
|
dispatchTask *asyncTask
|
||||||
|
mux *http.ServeMux
|
||||||
|
httpServer *http.Server
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +173,43 @@ func (m *Manager) initChannels() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetupHTTPServer creates a shared HTTP server with the given listen address.
|
||||||
|
// It registers health endpoints from the health server and discovers channels
|
||||||
|
// that implement WebhookHandler and/or HealthChecker to register their handlers.
|
||||||
|
func (m *Manager) SetupHTTPServer(addr string, healthServer *health.Server) {
|
||||||
|
m.mux = http.NewServeMux()
|
||||||
|
|
||||||
|
// Register health endpoints
|
||||||
|
if healthServer != nil {
|
||||||
|
healthServer.RegisterOnMux(m.mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover and register webhook handlers and health checkers
|
||||||
|
for name, ch := range m.channels {
|
||||||
|
if wh, ok := ch.(WebhookHandler); ok {
|
||||||
|
m.mux.Handle(wh.WebhookPath(), wh)
|
||||||
|
logger.InfoCF("channels", "Webhook handler registered", map[string]any{
|
||||||
|
"channel": name,
|
||||||
|
"path": wh.WebhookPath(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if hc, ok := ch.(HealthChecker); ok {
|
||||||
|
m.mux.HandleFunc(hc.HealthPath(), hc.HealthHandler)
|
||||||
|
logger.InfoCF("channels", "Health endpoint registered", map[string]any{
|
||||||
|
"channel": name,
|
||||||
|
"path": hc.HealthPath(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.httpServer = &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: m.mux,
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
WriteTimeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) StartAll(ctx context.Context) error {
|
func (m *Manager) StartAll(ctx context.Context) error {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
@ -203,6 +244,20 @@ func (m *Manager) StartAll(ctx context.Context) error {
|
||||||
// Start the dispatcher that reads from the bus and routes to workers
|
// Start the dispatcher that reads from the bus and routes to workers
|
||||||
go m.dispatchOutbound(dispatchCtx)
|
go m.dispatchOutbound(dispatchCtx)
|
||||||
|
|
||||||
|
// Start shared HTTP server if configured
|
||||||
|
if m.httpServer != nil {
|
||||||
|
go func() {
|
||||||
|
logger.InfoCF("channels", "Shared HTTP server listening", map[string]any{
|
||||||
|
"addr": m.httpServer.Addr,
|
||||||
|
})
|
||||||
|
if err := m.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
logger.ErrorCF("channels", "Shared HTTP server error", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
logger.InfoC("channels", "All channels started")
|
logger.InfoC("channels", "All channels started")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +268,19 @@ func (m *Manager) StopAll(ctx context.Context) error {
|
||||||
|
|
||||||
logger.InfoC("channels", "Stopping all channels")
|
logger.InfoC("channels", "Stopping all channels")
|
||||||
|
|
||||||
// Cancel dispatcher first
|
// Shutdown shared HTTP server first
|
||||||
|
if m.httpServer != nil {
|
||||||
|
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := m.httpServer.Shutdown(shutdownCtx); err != nil {
|
||||||
|
logger.ErrorCF("channels", "Shared HTTP server shutdown error", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
m.httpServer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel dispatcher
|
||||||
if m.dispatchTask != nil {
|
if m.dispatchTask != nil {
|
||||||
m.dispatchTask.cancel()
|
m.dispatchTask.cancel()
|
||||||
m.dispatchTask = nil
|
m.dispatchTask = nil
|
||||||
|
|
|
||||||
20
pkg/channels/webhook.go
Normal file
20
pkg/channels/webhook.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// WebhookHandler is an optional interface for channels that receive messages
|
||||||
|
// via HTTP webhooks. Manager discovers channels implementing this interface
|
||||||
|
// and registers them on the shared HTTP server.
|
||||||
|
type WebhookHandler interface {
|
||||||
|
// WebhookPath returns the path to mount this handler on the shared server.
|
||||||
|
// Examples: "/webhook/line", "/webhook/wecom"
|
||||||
|
WebhookPath() string
|
||||||
|
http.Handler // ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthChecker is an optional interface for channels that expose
|
||||||
|
// a health check endpoint on the shared HTTP server.
|
||||||
|
type HealthChecker interface {
|
||||||
|
HealthPath() string
|
||||||
|
HealthHandler(w http.ResponseWriter, r *http.Request)
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,6 @@ const (
|
||||||
type WeComAppChannel struct {
|
type WeComAppChannel struct {
|
||||||
*channels.BaseChannel
|
*channels.BaseChannel
|
||||||
config config.WeComAppConfig
|
config config.WeComAppConfig
|
||||||
server *http.Server
|
|
||||||
accessToken string
|
accessToken string
|
||||||
tokenExpiry time.Time
|
tokenExpiry time.Time
|
||||||
tokenMu sync.RWMutex
|
tokenMu sync.RWMutex
|
||||||
|
|
@ -134,7 +133,7 @@ func (c *WeComAppChannel) Name() string {
|
||||||
return "wecom_app"
|
return "wecom_app"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start initializes the WeCom App channel with HTTP webhook server
|
// Start initializes the WeCom App channel
|
||||||
func (c *WeComAppChannel) Start(ctx context.Context) error {
|
func (c *WeComAppChannel) Start(ctx context.Context) error {
|
||||||
logger.InfoC("wecom_app", "Starting WeCom App channel...")
|
logger.InfoC("wecom_app", "Starting WeCom App channel...")
|
||||||
|
|
||||||
|
|
@ -150,37 +149,8 @@ func (c *WeComAppChannel) Start(ctx context.Context) error {
|
||||||
// Start token refresh goroutine
|
// Start token refresh goroutine
|
||||||
go c.tokenRefreshLoop()
|
go c.tokenRefreshLoop()
|
||||||
|
|
||||||
// Setup HTTP server for webhook
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
webhookPath := c.config.WebhookPath
|
|
||||||
if webhookPath == "" {
|
|
||||||
webhookPath = "/webhook/wecom-app"
|
|
||||||
}
|
|
||||||
mux.HandleFunc(webhookPath, c.handleWebhook)
|
|
||||||
|
|
||||||
// Health check endpoint
|
|
||||||
mux.HandleFunc("/health/wecom-app", c.handleHealth)
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", c.config.WebhookHost, c.config.WebhookPort)
|
|
||||||
c.server = &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetRunning(true)
|
c.SetRunning(true)
|
||||||
logger.InfoCF("wecom_app", "WeCom App channel started", map[string]any{
|
logger.InfoC("wecom_app", "WeCom App channel started")
|
||||||
"address": addr,
|
|
||||||
"path": webhookPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start server in goroutine
|
|
||||||
go func() {
|
|
||||||
if err := c.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
logger.ErrorCF("wecom_app", "HTTP server error", map[string]any{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -193,12 +163,6 @@ func (c *WeComAppChannel) Stop(ctx context.Context) error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.server != nil {
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
c.server.Shutdown(shutdownCtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetRunning(false)
|
c.SetRunning(false)
|
||||||
logger.InfoC("wecom_app", "WeCom App channel stopped")
|
logger.InfoC("wecom_app", "WeCom App channel stopped")
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -223,6 +187,29 @@ func (c *WeComAppChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
return c.sendTextMessage(ctx, accessToken, msg.ChatID, msg.Content)
|
return c.sendTextMessage(ctx, accessToken, msg.ChatID, msg.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebhookPath returns the path for registering on the shared HTTP server.
|
||||||
|
func (c *WeComAppChannel) WebhookPath() string {
|
||||||
|
if c.config.WebhookPath != "" {
|
||||||
|
return c.config.WebhookPath
|
||||||
|
}
|
||||||
|
return "/webhook/wecom-app"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements http.Handler for the shared HTTP server.
|
||||||
|
func (c *WeComAppChannel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.handleWebhook(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthPath returns the health check endpoint path.
|
||||||
|
func (c *WeComAppChannel) HealthPath() string {
|
||||||
|
return "/health/wecom-app"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthHandler handles health check requests.
|
||||||
|
func (c *WeComAppChannel) HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.handleHealth(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
// handleWebhook handles incoming webhook requests from WeCom
|
// handleWebhook handles incoming webhook requests from WeCom
|
||||||
func (c *WeComAppChannel) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
func (c *WeComAppChannel) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
type WeComBotChannel struct {
|
type WeComBotChannel struct {
|
||||||
*channels.BaseChannel
|
*channels.BaseChannel
|
||||||
config config.WeComConfig
|
config config.WeComConfig
|
||||||
server *http.Server
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
processedMsgs map[string]bool // Message deduplication: msg_id -> processed
|
processedMsgs map[string]bool // Message deduplication: msg_id -> processed
|
||||||
|
|
@ -101,43 +100,14 @@ func (c *WeComBotChannel) Name() string {
|
||||||
return "wecom"
|
return "wecom"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start initializes the WeCom Bot channel with HTTP webhook server
|
// Start initializes the WeCom Bot channel
|
||||||
func (c *WeComBotChannel) Start(ctx context.Context) error {
|
func (c *WeComBotChannel) Start(ctx context.Context) error {
|
||||||
logger.InfoC("wecom", "Starting WeCom Bot channel...")
|
logger.InfoC("wecom", "Starting WeCom Bot channel...")
|
||||||
|
|
||||||
c.ctx, c.cancel = context.WithCancel(ctx)
|
c.ctx, c.cancel = context.WithCancel(ctx)
|
||||||
|
|
||||||
// Setup HTTP server for webhook
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
webhookPath := c.config.WebhookPath
|
|
||||||
if webhookPath == "" {
|
|
||||||
webhookPath = "/webhook/wecom"
|
|
||||||
}
|
|
||||||
mux.HandleFunc(webhookPath, c.handleWebhook)
|
|
||||||
|
|
||||||
// Health check endpoint
|
|
||||||
mux.HandleFunc("/health/wecom", c.handleHealth)
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", c.config.WebhookHost, c.config.WebhookPort)
|
|
||||||
c.server = &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetRunning(true)
|
c.SetRunning(true)
|
||||||
logger.InfoCF("wecom", "WeCom Bot channel started", map[string]any{
|
logger.InfoC("wecom", "WeCom Bot channel started")
|
||||||
"address": addr,
|
|
||||||
"path": webhookPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start server in goroutine
|
|
||||||
go func() {
|
|
||||||
if err := c.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
logger.ErrorCF("wecom", "HTTP server error", map[string]any{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -150,12 +120,6 @@ func (c *WeComBotChannel) Stop(ctx context.Context) error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.server != nil {
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
c.server.Shutdown(shutdownCtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetRunning(false)
|
c.SetRunning(false)
|
||||||
logger.InfoC("wecom", "WeCom Bot channel stopped")
|
logger.InfoC("wecom", "WeCom Bot channel stopped")
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -177,6 +141,29 @@ func (c *WeComBotChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
return c.sendWebhookReply(ctx, msg.ChatID, msg.Content)
|
return c.sendWebhookReply(ctx, msg.ChatID, msg.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebhookPath returns the path for registering on the shared HTTP server.
|
||||||
|
func (c *WeComBotChannel) WebhookPath() string {
|
||||||
|
if c.config.WebhookPath != "" {
|
||||||
|
return c.config.WebhookPath
|
||||||
|
}
|
||||||
|
return "/webhook/wecom"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements http.Handler for the shared HTTP server.
|
||||||
|
func (c *WeComBotChannel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.handleWebhook(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthPath returns the health check endpoint path.
|
||||||
|
func (c *WeComBotChannel) HealthPath() string {
|
||||||
|
return "/health/wecom"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthHandler handles health check requests.
|
||||||
|
func (c *WeComBotChannel) HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.handleHealth(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
// handleWebhook handles incoming webhook requests from WeCom
|
// handleWebhook handles incoming webhook requests from WeCom
|
||||||
func (c *WeComBotChannel) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
func (c *WeComBotChannel) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,13 @@ func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterOnMux registers /health and /ready handlers onto the given mux.
|
||||||
|
// This allows the health endpoints to be served by a shared HTTP server.
|
||||||
|
func (s *Server) RegisterOnMux(mux *http.ServeMux) {
|
||||||
|
mux.HandleFunc("/health", s.healthHandler)
|
||||||
|
mux.HandleFunc("/ready", s.readyHandler)
|
||||||
|
}
|
||||||
|
|
||||||
func statusString(ok bool) string {
|
func statusString(ok bool) string {
|
||||||
if ok {
|
if ok {
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue