mirror of
https://github.com/fiatjaf/khatru.git
synced 2026-06-07 21:28:43 +02:00
v2 with breaking changes and a simpler API.
This commit is contained in:
145
start.go
145
start.go
@@ -4,41 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
// Settings specify initial startup parameters for Start and StartConf.
|
||||
type Settings struct {
|
||||
Host string `envconfig:"HOST" default:"0.0.0.0"`
|
||||
Port string `envconfig:"PORT" default:"7447"`
|
||||
}
|
||||
|
||||
// Start calls StartConf with Settings parsed from the process environment.
|
||||
func Start(relay Relay) error {
|
||||
var s Settings
|
||||
if err := envconfig.Process("", &s); err != nil {
|
||||
return fmt.Errorf("envconfig: %w", err)
|
||||
}
|
||||
return StartConf(s, relay)
|
||||
}
|
||||
|
||||
// StartConf creates a new Server, passing it host:port for the address,
|
||||
// and starts serving propagating any error returned from [Server.Start].
|
||||
func StartConf(s Settings, relay Relay) error {
|
||||
addr := net.JoinHostPort(s.Host, s.Port)
|
||||
srv := NewServer(addr, relay)
|
||||
return srv.Start()
|
||||
}
|
||||
|
||||
// Server is a base for package users to implement nostr relays.
|
||||
// It can serve HTTP requests and websockets, passing control over to a relay implementation.
|
||||
//
|
||||
@@ -58,84 +31,33 @@ type Server struct {
|
||||
// outputting to stderr.
|
||||
Log Logger
|
||||
|
||||
addr string
|
||||
relay Relay
|
||||
router *mux.Router
|
||||
httpServer *http.Server // set at Server.Start
|
||||
addr string
|
||||
relay Relay
|
||||
|
||||
// keep a connection reference to all connected clients for Server.Shutdown
|
||||
clientsMu sync.Mutex
|
||||
clients map[*websocket.Conn]struct{}
|
||||
}
|
||||
|
||||
// NewServer creates a relay server with sensible defaults.
|
||||
// The provided address is used to listen and respond to HTTP requests.
|
||||
func NewServer(addr string, relay Relay) *Server {
|
||||
// NewServer initializes the relay and its storage using their respective Init methods,
|
||||
// returning any non-nil errors, and returns a Server ready to listen for HTTP requests.
|
||||
func NewServer(relay Relay) (*Server, error) {
|
||||
srv := &Server{
|
||||
Log: defaultLogger(relay.Name() + ": "),
|
||||
addr: addr,
|
||||
relay: relay,
|
||||
router: mux.NewRouter(),
|
||||
clients: make(map[*websocket.Conn]struct{}),
|
||||
}
|
||||
srv.router.Path("/").Headers("Upgrade", "websocket").HandlerFunc(srv.handleWebsocket)
|
||||
srv.router.Path("/").Headers("Accept", "application/nostr+json").HandlerFunc(srv.handleNIP11)
|
||||
return srv
|
||||
}
|
||||
|
||||
// Router returns an http.Handler used to handle server's in-flight HTTP requests.
|
||||
// By default, the router is setup to handle websocket upgrade and NIP-11 requests.
|
||||
//
|
||||
// In a larger system, where the relay server is not the only HTTP handler,
|
||||
// prefer using s as http.Handler instead of the returned router.
|
||||
func (s *Server) Router() *mux.Router {
|
||||
return s.router
|
||||
}
|
||||
|
||||
// Addr returns Server's HTTP listener address in host:port form.
|
||||
// If the initial port value provided in NewServer is 0, the actual port
|
||||
// value is picked at random and available by the time [Relay.OnInitialized]
|
||||
// is called.
|
||||
func (s *Server) Addr() string {
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.router.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Start initializes the relay and its storage using their respective Init methods,
|
||||
// returning any non-nil errors, and starts listening for HTTP requests on the address
|
||||
// provided to NewServer.
|
||||
//
|
||||
// Just before starting to serve HTTP requests, Start calls Relay.OnInitialized
|
||||
// allowing package users to make last adjustments, such as setting up custom HTTP
|
||||
// handlers using s.Router.
|
||||
//
|
||||
// Start never returns until termination of the underlying http.Server, forwarding
|
||||
// any but http.ErrServerClosed error from the server's ListenAndServe.
|
||||
// To terminate the server, call Shutdown.
|
||||
func (s *Server) Start() error {
|
||||
ln, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.addr = ln.Addr().String()
|
||||
return s.startListener(ln)
|
||||
}
|
||||
|
||||
func (s *Server) startListener(ln net.Listener) error {
|
||||
// init the relay
|
||||
if err := s.relay.Init(); err != nil {
|
||||
return fmt.Errorf("relay init: %w", err)
|
||||
if err := relay.Init(); err != nil {
|
||||
return nil, fmt.Errorf("relay init: %w", err)
|
||||
}
|
||||
if err := s.relay.Storage().Init(); err != nil {
|
||||
return fmt.Errorf("storage init: %w", err)
|
||||
if err := relay.Storage(context.Background()).Init(); err != nil {
|
||||
return nil, fmt.Errorf("storage init: %w", err)
|
||||
}
|
||||
|
||||
// push events from implementations, if any
|
||||
if inj, ok := s.relay.(Injector); ok {
|
||||
// start listening from events from other sources, if any
|
||||
if inj, ok := relay.(Injector); ok {
|
||||
go func() {
|
||||
for event := range inj.InjectEvents() {
|
||||
notifyListeners(&event)
|
||||
@@ -143,41 +65,24 @@ func (s *Server) startListener(ln net.Listener) error {
|
||||
}()
|
||||
}
|
||||
|
||||
s.httpServer = &http.Server{
|
||||
Handler: cors.Default().Handler(s),
|
||||
Addr: s.addr,
|
||||
WriteTimeout: 2 * time.Second,
|
||||
ReadTimeout: 2 * time.Second,
|
||||
IdleTimeout: 30 * time.Second,
|
||||
}
|
||||
s.httpServer.RegisterOnShutdown(s.disconnectAllClients)
|
||||
// final callback, just before serving http
|
||||
s.relay.OnInitialized(s)
|
||||
|
||||
// start accepting incoming requests
|
||||
s.Log.Infof("listening on %s", s.addr)
|
||||
err := s.httpServer.Serve(ln)
|
||||
if err == http.ErrServerClosed {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Shutdown stops serving HTTP requests and send a websocket close control message
|
||||
// to all connected clients.
|
||||
// ServeHTTP implements http.Handler interface.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
s.HandleWebsocket(w, r)
|
||||
} else if r.Header.Get("Accept") == "application/nostr+json" {
|
||||
s.HandleNIP11(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown sends a websocket close control message to all connected clients.
|
||||
//
|
||||
// If the relay is ShutdownAware, Shutdown calls its OnShutdown, passing the context as is.
|
||||
// Note that the HTTP server make some time to shutdown and so the context deadline,
|
||||
// if any, may have been shortened by the time OnShutdown is called.
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
err := s.httpServer.Shutdown(ctx)
|
||||
if f, ok := s.relay.(ShutdownAware); ok {
|
||||
f.OnShutdown(ctx)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) disconnectAllClients() {
|
||||
func (s *Server) Shutdown(ctx context.Context) {
|
||||
s.clientsMu.Lock()
|
||||
defer s.clientsMu.Unlock()
|
||||
for conn := range s.clients {
|
||||
@@ -185,6 +90,10 @@ func (s *Server) disconnectAllClients() {
|
||||
conn.Close()
|
||||
delete(s.clients, conn)
|
||||
}
|
||||
|
||||
if f, ok := s.relay.(ShutdownAware); ok {
|
||||
f.OnShutdown(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultLogger(prefix string) Logger {
|
||||
|
||||
Reference in New Issue
Block a user