diff --git a/response.go b/response.go index 9177b4a..d113a1b 100644 --- a/response.go +++ b/response.go @@ -3,7 +3,6 @@ package gemini import ( "bufio" "crypto/tls" - "fmt" "io" "net" "strconv" @@ -16,7 +15,10 @@ const defaultMediaType = "text/gemini; charset=utf-8" // // The Client returns Responses from servers once the response // header has been received. The response body is streamed on demand -// as the Body field is read. +// as the response is read. If the network connection fails or the server +// terminates the response, Read calls return an error. +// +// It is the caller's responsibility to close the response. type Response struct { // Status contains the response status code. Status Status @@ -27,18 +29,7 @@ type Response struct { // Meta should not be longer than 1024 bytes. Meta string - // Body represents the response body. - // - // The response body is streamed on demand as the Body field - // is read. If the network connection fails or the server - // terminates the response, Body.Read calls return an error. - // - // The Gemini client guarantees that Body is always - // non-nil, even on responses without a body or responses with - // a zero-length body. It is the caller's responsibility to - // close Body. - Body io.ReadCloser - + body io.ReadCloser conn net.Conn } @@ -90,9 +81,9 @@ func ReadResponse(rc io.ReadCloser) (*Response, error) { } if resp.Status.Class() == StatusSuccess { - resp.Body = newReadCloserBody(br, rc) + resp.body = newReadCloserBody(br, rc) } else { - resp.Body = nopReadCloser{} + resp.body = nopReadCloser{} rc.Close() } return resp, nil @@ -135,22 +126,15 @@ func (b *readCloserBody) Read(p []byte) (n int, err error) { return b.ReadCloser.Read(p) } -// Write writes r to w in the Gemini response format, including the -// header and body. -// -// This method consults the Status, Meta, and Body fields of the response. -// The Response Body is closed after it is sent. -func (r *Response) Write(w io.Writer) error { - if _, err := fmt.Fprintf(w, "%02d %s\r\n", r.Status, r.Meta); err != nil { - return err - } - if r.Body != nil { - defer r.Body.Close() - if _, err := io.Copy(w, r.Body); err != nil { - return err - } - } - return nil +// Read reads data from the response body. +// The response body is streamed on demand as Read is called. +func (r *Response) Read(p []byte) (n int, err error) { + return r.body.Read(p) +} + +// Close closes the response body. +func (r *Response) Close() error { + return r.body.Close() } // Conn returns the network connection on which the response was received. diff --git a/response_test.go b/response_test.go index a6226ee..68e0884 100644 --- a/response_test.go +++ b/response_test.go @@ -96,7 +96,7 @@ func TestReadWriteResponse(t *testing.T) { if resp.Meta != test.Meta { t.Errorf("expected meta = %s, got %s", test.Meta, resp.Meta) } - b, _ := io.ReadAll(resp.Body) + b, _ := io.ReadAll(resp.body) body := string(b) if body != test.Body { t.Errorf("expected body = %#v, got %#v", test.Body, body) @@ -107,14 +107,12 @@ func TestReadWriteResponse(t *testing.T) { if test.Err != nil || test.SkipWrite { continue } - resp := &Response{ - Status: test.Status, - Meta: test.Meta, - Body: io.NopCloser(strings.NewReader(test.Body)), - } var b strings.Builder - if err := resp.Write(&b); err != nil { + w := NewResponseWriter(nopCloser{&b}) + w.WriteHeader(test.Status, test.Meta) + io.Copy(w, strings.NewReader(test.Body)) + if err := w.Flush(); err != nil { t.Error(err) continue } @@ -125,3 +123,15 @@ func TestReadWriteResponse(t *testing.T) { } } } + +type nopCloser struct { + io.Writer +} + +func (w nopCloser) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func (nopCloser) Close() error { + return nil +}