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

  1. 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).
  2. Verify the URL/port scheme — use rtsp:// and port 554 (or the camera's documented RTSP port), not the HTTP port.
  3. Update camera firmware or use a vendor-specific transport (e.g. HTTP tunneling) if the device emits non-conformant status lines.
  4. 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

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

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)