diff --git a/app/handle-relayinfo.go b/app/handle-relayinfo.go index ca46be1..7124ae5 100644 --- a/app/handle-relayinfo.go +++ b/app/handle-relayinfo.go @@ -37,7 +37,8 @@ type ExtendedRelayInfo struct { // Informer interface implementation or predefined server configuration. It // returns this document as a JSON response to the client. func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) { - r.Header.Set("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Vary", "Accept") log.D.Ln("handling relay information document") var info *relayinfo.T nips := []relayinfo.NIP{ diff --git a/app/server.go b/app/server.go index f84d331..6e03a73 100644 --- a/app/server.go +++ b/app/server.go @@ -738,6 +738,12 @@ func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) { w.Header().Set( "Content-Disposition", "attachment; filename=\""+filename+"\"", ) + w.Header().Set("X-Content-Type-Options", "nosniff") + + // Flush headers to start streaming immediately + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } // Stream export s.DB.Export(s.Ctx, w, pks...) diff --git a/pkg/database/export.go b/pkg/database/export.go index c1e2572..e7058de 100644 --- a/pkg/database/export.go +++ b/pkg/database/export.go @@ -17,6 +17,11 @@ import ( "git.mleku.dev/mleku/nostr/utils/units" ) +// Flusher interface for HTTP streaming +type flusher interface { + Flush() +} + // Export the complete database of stored events to an io.Writer in line structured minified // JSON. Supports both legacy and compact event formats. func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { @@ -24,11 +29,18 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { evB := make([]byte, 0, units.Mb) evBuf := bytes.NewBuffer(evB) + // Get flusher for HTTP streaming if available + var f flusher + if fl, ok := w.(flusher); ok { + f = fl + } + // Performance tracking startTime := time.Now() var eventCount, bytesWritten int64 lastLogTime := startTime const logInterval = 5 * time.Second + const flushInterval = 100 // Flush every N events log.I.F("export: starting export operation") @@ -109,6 +121,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { eventCount++ ev.Free() + // Flush periodically for HTTP streaming + if f != nil && eventCount%flushInterval == 0 { + f.Flush() + } + // Progress logging every logInterval if time.Since(lastLogTime) >= logInterval { elapsed := time.Since(startTime) @@ -169,6 +186,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { eventCount++ ev.Free() + // Flush periodically for HTTP streaming + if f != nil && eventCount%flushInterval == 0 { + f.Flush() + } + // Progress logging every logInterval if time.Since(lastLogTime) >= logInterval { elapsed := time.Since(startTime) @@ -186,6 +208,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { return } + // Final flush + if f != nil { + f.Flush() + } + // Final export summary elapsed := time.Since(startTime) eventsPerSec := float64(eventCount) / elapsed.Seconds() @@ -244,6 +271,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { eventCount++ ev.Free() + // Flush periodically for HTTP streaming + if f != nil && eventCount%flushInterval == 0 { + f.Flush() + } + // Progress logging every logInterval if time.Since(lastLogTime) >= logInterval { elapsed := time.Since(startTime) @@ -261,6 +293,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { } } + // Final flush + if f != nil { + f.Flush() + } + // Final export summary for pubkey export elapsed := time.Since(startTime) eventsPerSec := float64(eventCount) / elapsed.Seconds() diff --git a/pkg/version/version b/pkg/version/version index fe2725e..7d20f6f 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.46.1 +v0.46.2