siyuan-note/siyuan · error
plugin service response forbids a body
Error message
plugin service response forbids a body
What it means
HTTP rules forbid response bodies for HEAD requests, any status below 200 (1xx), 204 No Content, and 304 Not Modified. This error means the plugin service produced bytes for such a response, which would corrupt the HTTP framing (e.g. a body after 204 confuses clients and proxies).
Solutions
- Skip all body writes when the request method is HEAD or the status is <200, 204, or 304
- Set the body-less status only after ensuring no bytes were already written
- Return an empty payload ([]byte(nil)) for these responses in tests/validation
Example fix
// before
w.WriteHeader(http.StatusNoContent)
w.Write([]byte("{}"))
// after
w.WriteHeader(http.StatusNoContent) // no body written Defensive patterns
Strategy: validation
Validate before calling
func bodyForbidden(method string, status int) bool {
return method == http.MethodHead || status < 200 || status == http.StatusNoContent || status == http.StatusNotModified
}
if bodyForbidden(endpoint.Method, status) && len(payload) > 0 {
payload = nil // drop body before validation/writing
} Prevention
- Check method/status before any w.Write call
- Avoid helpers that unconditionally render a body
- Add recorder-based tests asserting empty bodies for HEAD/204/304
When it happens
Trigger: Writing payload bytes with http.ResponseWriter for a HEAD request, or replying with status 204/304 (or a 1xx) while still emitting a body in a plugin-service endpoint.
Common situations: A handler writes a JSON error body but sets the status to 204; a shared middleware writes a body before the handler decides on 304; framework helpers that always render a body regardless of method/status.
Related errors
- empty plugin response contains a body
- download failed:
- GitHub API returned status
- invalid plugin redirect status
- invalid plugin service HTTP status
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/3be9673ef83dec08.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/plugin_service_protocol.go:174
case PluginServiceWebSocket:
if status != 101 && status != 400 && status != 500 {
return fmt.Errorf("invalid plugin WebSocket status")
}
case PluginServiceSSE:
if status != 200 && status != 500 {
return fmt.Errorf("invalid plugin SSE status")
}
}
return nil
}
func (b *Bundle) validatePluginServiceHTTPResponse(endpoint EndpointSchema, status int, contentType string, payload []byte) error {
if status < 100 || status > 999 {
return fmt.Errorf("invalid plugin service HTTP status")
}
if endpoint.Method == "HEAD" || status < 200 || status == 204 || status == 304 {
if len(payload) > 0 {
return fmt.Errorf("plugin service response forbids a body")
}
return nil
}
// 原始文件、代理及插件自选媒体允许任意字节,具体分支由 ValidatePluginServiceResponse 校验。
return nil
}
func (b *Bundle) ValidatePluginServiceResponse(method, path string, mode PluginServiceMode, status int, contentType string, payload []byte) error {
var found bool
for _, endpoint := range b.Endpoints {
if endpoint.Method == method && endpoint.Path == path && endpoint.PluginService != nil {
found = true
break
}
}
if !found {
return fmt.Errorf("unregistered plugin service: %s %s", method, path)
}View on GitHub (pinned to 9f775e8a12)