feat: Add Handler() method and fix SSE closed pipe errors (#24)

* feat: add Handler() method for testing and custom server integration

Exposes the underlying http.Handler to enable:
- Integration testing with gost-dom/browser for SSE/Datastar testing
- Custom server setups (e.g., embedding Via in existing applications)
- Standard Go httptest patterns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Suppress closed pipe errors when SSE connection closes

Don't log error when SSE fails to send patches if the connection was
already closed. This reduces noise in logs during shutdown and testing,
where browsers/clients close connections before server handlers finish.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: joeblew999 <joeblew999@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gerard Webb
2025-12-11 16:51:16 +07:00
committed by GitHub
parent 81d44954a4
commit 6da518d990

19
via.go
View File

@@ -243,6 +243,12 @@ func (v *V) Start() {
log.Fatalf("[fatal] %v", http.ListenAndServe(v.cfg.ServerAddress, v.mux)) log.Fatalf("[fatal] %v", http.ListenAndServe(v.cfg.ServerAddress, v.mux))
} }
// Handler returns the underlying http.Handler for use with custom servers or testing.
// This enables integration with test frameworks like gost-dom/browser for SSE/Datastar testing.
func (v *V) Handler() http.Handler {
return v.mux
}
func (v *V) devModePersist(c *Context) { func (v *V) devModePersist(c *Context) {
p := filepath.Join(".via", "devmode", "ctx.json") p := filepath.Join(".via", "devmode", "ctx.json")
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
@@ -413,17 +419,24 @@ func New() *V {
switch patch.typ { switch patch.typ {
case patchTypeElements: case patchTypeElements:
if err := sse.PatchElements(patch.content); err != nil { if err := sse.PatchElements(patch.content); err != nil {
v.logErr(c, "PatchElements failed: %v", err) // Only log if connection wasn't closed (avoids noise during shutdown/tests)
if sse.Context().Err() == nil {
v.logErr(c, "PatchElements failed: %v", err)
}
continue continue
} }
case patchTypeSignals: case patchTypeSignals:
if err := sse.PatchSignals([]byte(patch.content)); err != nil { if err := sse.PatchSignals([]byte(patch.content)); err != nil {
v.logErr(c, "PatchSignals failed: %v", err) if sse.Context().Err() == nil {
v.logErr(c, "PatchSignals failed: %v", err)
}
continue continue
} }
case patchTypeScript: case patchTypeScript:
if err := sse.ExecuteScript(patch.content, datastar.WithExecuteScriptAutoRemove(true)); err != nil { if err := sse.ExecuteScript(patch.content, datastar.WithExecuteScriptAutoRemove(true)); err != nil {
v.logErr(c, "ExecuteScript failed: %v", err) if sse.Context().Err() == nil {
v.logErr(c, "ExecuteScript failed: %v", err)
}
continue continue
} }
} }