From ea7bc75fac3106e990cade6b68e65e556e04b3e3 Mon Sep 17 00:00:00 2001 From: woikos Date: Sat, 3 Jan 2026 07:17:48 +0100 Subject: [PATCH] Fix NIP-11 caching and export streaming issues (v0.46.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Content-Type header being set on request instead of response - Add Vary: Accept header to prevent browser/CDN caching NIP-11 for HTML - Add periodic flushing during export for HTTP streaming (every 100 events) - Add initial flush after headers to start streaming immediately - Add X-Content-Type-Options: nosniff to prevent browser buffering Files modified: - app/handle-relayinfo.go: Fix header and add Vary: Accept - app/server.go: Add initial flush and nosniff header for export - pkg/database/export.go: Add periodic and final flushing for streaming 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/handle-relayinfo.go | 3 ++- app/server.go | 6 ++++++ pkg/database/export.go | 37 +++++++++++++++++++++++++++++++++++++ pkg/version/version | 2 +- 4 files changed, 46 insertions(+), 2 deletions(-) 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