| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build go1.24 |
| |
| package http3 |
| |
| import ( |
| "errors" |
| "fmt" |
| "io" |
| "sync" |
| ) |
| |
| // A bodyWriter writes a request or response body to a stream |
| // as a series of DATA frames. |
| type bodyWriter struct { |
| st *stream |
| remain int64 // -1 when content-length is not known |
| flush bool // flush the stream after every write |
| name string // "request" or "response" |
| } |
| |
| func (w *bodyWriter) Write(p []byte) (n int, err error) { |
| if w.remain >= 0 && int64(len(p)) > w.remain { |
| return 0, &streamError{ |
| code: errH3InternalError, |
| message: w.name + " body longer than specified content length", |
| } |
| } |
| w.st.writeVarint(int64(frameTypeData)) |
| w.st.writeVarint(int64(len(p))) |
| n, err = w.st.Write(p) |
| if w.remain >= 0 { |
| w.remain -= int64(n) |
| } |
| if w.flush && err == nil { |
| err = w.st.Flush() |
| } |
| if err != nil { |
| err = fmt.Errorf("writing %v body: %w", w.name, err) |
| } |
| return n, err |
| } |
| |
| func (w *bodyWriter) Close() error { |
| if w.remain > 0 { |
| return errors.New(w.name + " body shorter than specified content length") |
| } |
| return nil |
| } |
| |
| // A bodyReader reads a request or response body from a stream. |
| type bodyReader struct { |
| st *stream |
| |
| mu sync.Mutex |
| remain int64 |
| err error |
| } |
| |
| func (r *bodyReader) Read(p []byte) (n int, err error) { |
| // The HTTP/1 and HTTP/2 implementations both permit concurrent reads from a body, |
| // in the sense that the race detector won't complain. |
| // Use a mutex here to provide the same behavior. |
| r.mu.Lock() |
| defer r.mu.Unlock() |
| if r.err != nil { |
| return 0, r.err |
| } |
| defer func() { |
| if err != nil { |
| r.err = err |
| } |
| }() |
| if r.st.lim == 0 { |
| // We've finished reading the previous DATA frame, so end it. |
| if err := r.st.endFrame(); err != nil { |
| return 0, err |
| } |
| } |
| // Read the next DATA frame header, |
| // if we aren't already in the middle of one. |
| for r.st.lim < 0 { |
| ftype, err := r.st.readFrameHeader() |
| if err == io.EOF && r.remain > 0 { |
| return 0, &streamError{ |
| code: errH3MessageError, |
| message: "body shorter than content-length", |
| } |
| } |
| if err != nil { |
| return 0, err |
| } |
| switch ftype { |
| case frameTypeData: |
| if r.remain >= 0 && r.st.lim > r.remain { |
| return 0, &streamError{ |
| code: errH3MessageError, |
| message: "body longer than content-length", |
| } |
| } |
| // Fall out of the loop and process the frame body below. |
| case frameTypeHeaders: |
| // This HEADERS frame contains the message trailers. |
| if r.remain > 0 { |
| return 0, &streamError{ |
| code: errH3MessageError, |
| message: "body shorter than content-length", |
| } |
| } |
| // TODO: Fill in Request.Trailer. |
| if err := r.st.discardFrame(); err != nil { |
| return 0, err |
| } |
| return 0, io.EOF |
| default: |
| if err := r.st.discardUnknownFrame(ftype); err != nil { |
| return 0, err |
| } |
| } |
| } |
| // We are now reading the content of a DATA frame. |
| // Fill the read buffer or read to the end of the frame, |
| // whichever comes first. |
| if int64(len(p)) > r.st.lim { |
| p = p[:r.st.lim] |
| } |
| n, err = r.st.Read(p) |
| if r.remain > 0 { |
| r.remain -= int64(n) |
| } |
| return n, err |
| } |
| |
| func (r *bodyReader) Close() error { |
| // Unlike the HTTP/1 and HTTP/2 body readers (at the time of this comment being written), |
| // calling Close concurrently with Read will interrupt the read. |
| r.st.stream.CloseRead() |
| return nil |
| } |