Make Response an io.ReadCloser

This commit is contained in:
Adnan Maolood 2021-02-23 17:50:47 -05:00
parent ae3fc2fc73
commit e1c04ee605
2 changed files with 33 additions and 39 deletions

View File

@ -3,7 +3,6 @@ package gemini
import ( import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"fmt"
"io" "io"
"net" "net"
"strconv" "strconv"
@ -16,7 +15,10 @@ const defaultMediaType = "text/gemini; charset=utf-8"
// //
// The Client returns Responses from servers once the response // The Client returns Responses from servers once the response
// header has been received. The response body is streamed on demand // 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 { type Response struct {
// Status contains the response status code. // Status contains the response status code.
Status Status Status Status
@ -27,18 +29,7 @@ type Response struct {
// Meta should not be longer than 1024 bytes. // Meta should not be longer than 1024 bytes.
Meta string Meta string
// Body represents the response body. body io.ReadCloser
//
// 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
conn net.Conn conn net.Conn
} }
@ -90,9 +81,9 @@ func ReadResponse(rc io.ReadCloser) (*Response, error) {
} }
if resp.Status.Class() == StatusSuccess { if resp.Status.Class() == StatusSuccess {
resp.Body = newReadCloserBody(br, rc) resp.body = newReadCloserBody(br, rc)
} else { } else {
resp.Body = nopReadCloser{} resp.body = nopReadCloser{}
rc.Close() rc.Close()
} }
return resp, nil return resp, nil
@ -135,22 +126,15 @@ func (b *readCloserBody) Read(p []byte) (n int, err error) {
return b.ReadCloser.Read(p) return b.ReadCloser.Read(p)
} }
// Write writes r to w in the Gemini response format, including the // Read reads data from the response body.
// header and body. // The response body is streamed on demand as Read is called.
// func (r *Response) Read(p []byte) (n int, err error) {
// This method consults the Status, Meta, and Body fields of the response. return r.body.Read(p)
// 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 { // Close closes the response body.
return err func (r *Response) Close() error {
} return r.body.Close()
if r.Body != nil {
defer r.Body.Close()
if _, err := io.Copy(w, r.Body); err != nil {
return err
}
}
return nil
} }
// Conn returns the network connection on which the response was received. // Conn returns the network connection on which the response was received.

View File

@ -96,7 +96,7 @@ func TestReadWriteResponse(t *testing.T) {
if resp.Meta != test.Meta { if resp.Meta != test.Meta {
t.Errorf("expected meta = %s, got %s", test.Meta, resp.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) body := string(b)
if body != test.Body { if body != test.Body {
t.Errorf("expected body = %#v, got %#v", test.Body, 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 { if test.Err != nil || test.SkipWrite {
continue continue
} }
resp := &Response{
Status: test.Status,
Meta: test.Meta,
Body: io.NopCloser(strings.NewReader(test.Body)),
}
var b strings.Builder 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) t.Error(err)
continue 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
}