Initial vibeproxy implementation
Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go, with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS framework), and a REST API under /api/v1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/owen/vibeproxy/internal/certificates"
|
||||
"github.com/owen/vibeproxy/internal/logging"
|
||||
"github.com/owen/vibeproxy/internal/metrics"
|
||||
"github.com/owen/vibeproxy/internal/runtime"
|
||||
)
|
||||
|
||||
// Server owns the plain-HTTP and TLS listeners that serve all proxy hosts.
|
||||
type Server struct {
|
||||
httpSrv *http.Server
|
||||
httpsSrv *http.Server
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
HTTPAddr string // e.g. ":80"
|
||||
HTTPSAddr string // e.g. ":443"
|
||||
}
|
||||
|
||||
func NewServer(opts Options, store *runtime.Store, certMgr *certificates.Manager, db *sql.DB, log *slog.Logger) *Server {
|
||||
reqLog := logging.NewRequestLogger(db, log)
|
||||
go reqLog.Run(context.Background())
|
||||
|
||||
handler := NewHandler(store, certMgr, reqLog, db, log)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
cert, err := certMgr.GetCertificateForDomain(hello.ServerName)
|
||||
if err != nil {
|
||||
metrics.TLSHandshakesTotal.WithLabelValues("error").Inc()
|
||||
return nil, err
|
||||
}
|
||||
metrics.TLSHandshakesTotal.WithLabelValues("ok").Inc()
|
||||
return cert, nil
|
||||
},
|
||||
}
|
||||
|
||||
httpAddr := opts.HTTPAddr
|
||||
if httpAddr == "" {
|
||||
httpAddr = ":80"
|
||||
}
|
||||
httpsAddr := opts.HTTPSAddr
|
||||
if httpsAddr == "" {
|
||||
httpsAddr = ":443"
|
||||
}
|
||||
|
||||
return &Server{
|
||||
log: log,
|
||||
httpSrv: &http.Server{
|
||||
Addr: httpAddr,
|
||||
Handler: handler,
|
||||
ReadTimeout: 0, // per-request timeouts are enforced inside the handler
|
||||
WriteTimeout: 0,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
ErrorLog: slog.NewLogLogger(log.Handler(), slog.LevelWarn),
|
||||
},
|
||||
httpsSrv: &http.Server{
|
||||
Addr: httpsAddr,
|
||||
Handler: handler,
|
||||
TLSConfig: tlsConfig,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
ErrorLog: slog.NewLogLogger(log.Handler(), slog.LevelWarn),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start runs both listeners; it blocks until either fails or ctx is done.
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
errCh := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
s.log.Info("http listener starting", "addr", s.httpSrv.Addr)
|
||||
if err := s.httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
errCh <- fmt.Errorf("http listener: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
s.log.Info("https listener starting", "addr", s.httpsSrv.Addr)
|
||||
// Certificates are served dynamically via GetCertificate, so we
|
||||
// don't pass cert/key file paths here.
|
||||
if err := s.httpsSrv.ListenAndServeTLS("", ""); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
errCh <- fmt.Errorf("https listener: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return s.Shutdown(context.Background())
|
||||
case err := <-errCh:
|
||||
s.Shutdown(context.Background())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown gracefully drains in-flight requests/connections before
|
||||
// closing the listeners.
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
err1 := s.httpSrv.Shutdown(shutdownCtx)
|
||||
err2 := s.httpsSrv.Shutdown(shutdownCtx)
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
Reference in New Issue
Block a user