AlexxIT/go2rtc · error
malformed response
Error message
malformed response: %s
What it means
ReadResponse parses the first line of an RTSP response and requires exactly three space-separated fields: the protocol (e.g. RTSP/1.0), the status code, and the status text. If the line from the server does not split into exactly 3 parts, the library considers the response structurally malformed and refuses to build a Response. This guards against garbage bytes, partial reads, or non-RTSP replies on the socket.
Solutions
- Log the raw line shown in the error and confirm the peer is actually an RTSP server on that port (test with a raw RTSP OPTIONS request).
- Verify the URL/port scheme — use rtsp:// and port 554 (or the camera's documented RTSP port), not the HTTP port.
- Update camera firmware or use a vendor-specific transport (e.g. HTTP tunneling) if the device emits non-conformant status lines.
- If the device omits the reason phrase, patch the parser or interpose a proxy that normalizes the status line to 3 fields.
Example fix
// before
res, err := conn.ReadResponse()
// after
line := readStatusLine(conn)
if strings.Count(line, " ") < 2 {
log.Fatalf("peer sent non-RTSP status line: %q", line)
}
res, err := conn.ReadResponse() Defensive patterns
Strategy: try-catch
Validate before calling
// Peek the status line before trusting the response
raw := peekStatusLine(conn)
if len(strings.Fields(raw)) < 3 {
return fmt.Errorf("peer not speaking RTSP: %q", raw)
} Type guard
func isRTSPStatusLine(line string) bool {
parts := strings.SplitN(line, " ", 3)
return len(parts) == 3 && strings.HasPrefix(parts[0], "RTSP/")
} Try / catch
res, err := conn.ReadResponse()
if err != nil {
if strings.Contains(err.Error(), "malformed response") {
return fmt.Errorf("non-RTSP peer on port: %w", err)
}
return err
} Prevention
- Confirm the target port actually serves RTSP before dialing
- Log raw status lines on parse failure for diagnostics
- Pin to camera-documented RTSP ports and schemes
- Normalize non-conformant devices with a proxy if needed
When it happens
Trigger: Calling ReadResponse (directly or via Dial/handshake/openTalkChannel) when the server's status line has fewer or more than 3 space-separated tokens, e.g. an empty-ish line, an HTML error page, or a line like 'RTSP/1.0 200' without a reason phrase.
Common situations: Connecting to a device that is not speaking RTSP on the given port; an HTTP proxy or captive portal returning non-RTSP output; custom cameras emitting non-conformant status lines; reading stale/partial buffered data after a previous failed request.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/e32a4ba280652f76.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/tcp/textproto.go:56
func (r *Response) Write(w io.Writer) (err error) {
_, err = w.Write([]byte(r.String()))
return
}
func ReadResponse(r *bufio.Reader) (*Response, error) {
tp := textproto.NewReader(r)
line, err := tp.ReadLine()
if err != nil {
return nil, err
}
if line == "" {
return nil, errors.New("empty response on RTSP request")
}
ss := strings.SplitN(line, " ", 3)
if len(ss) != 3 {
return nil, fmt.Errorf("malformed response: %s", line)
}
res := &Response{
Status: ss[1] + " " + ss[2],
Proto: ss[0],
}
res.StatusCode, err = strconv.Atoi(ss[1])
if err != nil {
return nil, err
}
res.Header, err = tp.ReadMIMEHeader()
if err != nil {
return nil, err
}
if val := res.Header.Get("Content-Length"); val != "" {View on GitHub (pinned to c245815e75)