complete the marshal/unmarshal of events using the new pool enabled tag codecs

This commit is contained in:
2025-08-22 14:29:55 +01:00
parent 8add32bb78
commit bf178eae4e
25 changed files with 1547 additions and 44 deletions

5
app/handle-message.go Normal file
View File

@@ -0,0 +1,5 @@
package app
func (s *Server) HandleMessage() {
}

View File

@@ -25,9 +25,9 @@ import (
// The function constructs a relay information document using either the
// Informer interface implementation or predefined server configuration. It
// returns this document as a JSON response to the client.
func (l *Listener) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
r.Header.Set("Content-Type", "application/json")
log.I.Ln("handling relay information document")
log.D.Ln("handling relay information document")
var info *relayinfo.T
supportedNIPs := relayinfo.GetList(
relayinfo.BasicProtocol,
@@ -47,14 +47,14 @@ func (l *Listener) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
sort.Sort(supportedNIPs)
log.T.Ln("supported NIPs", supportedNIPs)
info = &relayinfo.T{
Name: l.Config.AppName,
Name: s.Config.AppName,
Description: version.Description,
Nips: supportedNIPs,
Software: version.URL,
Version: version.V,
Limitation: relayinfo.Limits{
// AuthRequired: l.C.AuthRequired,
// RestrictedWrites: l.C.AuthRequired,
// AuthRequired: s.C.AuthRequired,
// RestrictedWrites: s.C.AuthRequired,
},
Icon: "https://cdn.satellite.earth/ac9778868fbf23b63c47c769a74e163377e6ea94d3f0f31711931663d035c4f6.png",
}

100
app/handle-websocket.go Normal file
View File

@@ -0,0 +1,100 @@
package app
import (
"context"
"net/http"
"strings"
"time"
"github.com/coder/websocket"
"lol.mleku.dev/chk"
"lol.mleku.dev/log"
)
const (
// CloseMessage denotes a close control message. The optional message
// payload contains a numeric code and text. Use the FormatCloseMessage
// function to format a close message payload.
CloseMessage = 8
// PingMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
PingMessage = 9
// PongMessage denotes a pong control message. The optional message payload
// is UTF-8 encoded text.
PongMessage = 10
)
func (s *Server) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
remote := GetRemoteFromReq(r)
var cancel context.CancelFunc
s.Ctx, cancel = context.WithCancel(s.Ctx)
defer cancel()
var err error
var conn *websocket.Conn
if conn, err = websocket.Accept(
w, r, &websocket.AcceptOptions{},
); chk.E(err) {
return
}
defer conn.CloseNow()
go s.Pinger(s.Ctx, conn, time.NewTicker(time.Second*10), cancel)
for {
select {
case <-s.Ctx.Done():
return
default:
}
var typ websocket.MessageType
var message []byte
if typ, message, err = conn.Read(s.Ctx); err != nil {
if strings.Contains(
err.Error(), "use of closed network connection",
) {
return
}
status := websocket.CloseStatus(err)
switch status {
case websocket.StatusNormalClosure,
websocket.StatusGoingAway,
websocket.StatusNoStatusRcvd,
websocket.StatusAbnormalClosure,
websocket.StatusProtocolError:
default:
log.E.F("unexpected close error from %s: %v", remote, err)
}
return
}
if typ == PingMessage {
if err = conn.Write(s.Ctx, PongMessage, message); chk.E(err) {
return
}
continue
}
go s.HandleMessage()
}
}
func (s *Server) Pinger(
ctx context.Context, conn *websocket.Conn, ticker *time.Ticker,
cancel context.CancelFunc,
) {
defer func() {
cancel()
ticker.Stop()
}()
var err error
for {
select {
case <-ticker.C:
if err = conn.Write(ctx, PingMessage, nil); err != nil {
log.E.F("error writing ping: %v; closing websocket", err)
return
}
case <-ctx.Done():
return
}
}
}

69
app/helpers.go Normal file
View File

@@ -0,0 +1,69 @@
package app
import (
"net/http"
"strings"
)
// GetRemoteFromReq retrieves the originating IP address of the client from
// an HTTP request, considering standard and non-standard proxy headers.
//
// # Parameters
//
// - r: The HTTP request object containing details of the client and
// routing information.
//
// # Return Values
//
// - rr: A string value representing the IP address of the originating
// remote client.
//
// # Expected behaviour
//
// The function first checks for the standardized "Forwarded" header (RFC 7239)
// to identify the original client IP. If that isn't available, it falls back to
// the "X-Forwarded-For" header. If both headers are absent, it defaults to
// using the request's RemoteAddr.
//
// For the "Forwarded" header, it extracts the client IP from the "for"
// parameter. For the "X-Forwarded-For" header, if it contains one IP, it
// returns that. If it contains two IPs, it returns the second.
func GetRemoteFromReq(r *http.Request) (rr string) {
// First check for the standardized Forwarded header (RFC 7239)
forwarded := r.Header.Get("Forwarded")
if forwarded != "" {
// Parse the Forwarded header which can contain multiple parameters
//
// Format:
//
// Forwarded: by=<identifier>;for=<identifier>;host=<host>;proto=<http|https>
parts := strings.Split(forwarded, ";")
for _, part := range parts {
part = strings.TrimSpace(part)
if strings.HasPrefix(part, "for=") {
// Extract the client IP from the "for" parameter
forValue := strings.TrimPrefix(part, "for=")
// Remove quotes if present
forValue = strings.Trim(forValue, "\"")
// Handle IPv6 addresses which are enclosed in square brackets
forValue = strings.Trim(forValue, "[]")
return forValue
}
}
}
// If the Forwarded header is not available or doesn't contain "for"
// parameter, fall back to X-Forwarded-For
rem := r.Header.Get("X-Forwarded-For")
if rem == "" {
rr = r.RemoteAddr
} else {
splitted := strings.Split(rem, " ")
if len(splitted) == 1 {
rr = splitted[0]
}
if len(splitted) == 2 {
rr = splitted[1]
}
}
return
}

View File

@@ -1,29 +1,9 @@
package app
import (
"net/http"
"lol.mleku.dev/log"
"next.orly.dev/app/config"
"github.com/coder/websocket"
)
type Listener struct {
mux *http.ServeMux
Config *config.C
}
func (l *Listener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.I.F("path %v header %v", r.URL, r.Header)
if r.Header.Get("Upgrade") == "websocket" {
l.HandleWebsocket(w, r)
} else if r.Header.Get("Accept") == "application/nostr+json" {
l.HandleRelayInfo(w, r)
} else {
http.Error(w, "Upgrade required", http.StatusUpgradeRequired)
}
}
func (l *Listener) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
log.I.F("websocket")
return
conn *websocket.Conn
}

View File

@@ -19,7 +19,7 @@ func Run(ctx context.Context, cfg *config.C) (quit chan struct{}) {
}
}()
// start listener
l := &Listener{
l := &Server{
Config: cfg,
}
addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)

26
app/server.go Normal file
View File

@@ -0,0 +1,26 @@
package app
import (
"context"
"net/http"
"lol.mleku.dev/log"
"next.orly.dev/app/config"
)
type Server struct {
mux *http.ServeMux
Config *config.C
Ctx context.Context
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.T.F("path %v header %v", r.URL, r.Header)
if r.Header.Get("Upgrade") == "websocket" {
s.HandleWebsocket(w, r)
} else if r.Header.Get("Accept") == "application/nostr+json" {
s.HandleRelayInfo(w, r)
} else {
http.Error(w, "Upgrade required", http.StatusUpgradeRequired)
}
}