v2 with breaking changes and a simpler API.

This commit is contained in:
fiatjaf
2023-05-01 19:21:09 -03:00
parent a82e5edb0d
commit e3d4655dba
6 changed files with 71 additions and 172 deletions

145
start.go
View File

@@ -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 {