Skip to content

Commit

Permalink
feat: Can now parse body from response of type text/plain in a GET
Browse files Browse the repository at this point in the history
- Can now parse body from response
- Supports reason phrase with white spaces such as "Bad Request", before we split it naively and rejected it
  • Loading branch information
randuck-dev committed Nov 9, 2023
1 parent 2eed1af commit 4179b2c
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 17 deletions.
22 changes: 22 additions & 0 deletions internal/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,28 @@ func parseResponse(conn io.Reader) (Response, error) {
Headers: headers,
}

content_length, err := resp.ContentLength()

if err == ErrHeaderNotFound {
return resp, nil
}

if err != nil {
return Response{}, err
}

limit_reader := io.LimitReader(reader, content_length)

buf := make([]byte, content_length)

_, err = limit_reader.Read(buf)

if err != nil {
return Response{}, nil
}

resp.Body = buf
return resp, nil
line, err = tp.ReadLine()

if err == io.EOF {
Expand Down
13 changes: 11 additions & 2 deletions internal/http/http_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func parseStatusLine(payload string) (StatusLine, error) {

split_line := strings.Split(payload, " ")
if len(split_line) != 3 {
if len(split_line) < 3 {
return StatusLine{}, ErrIncompleteStatusLine
}

Expand All @@ -29,10 +29,19 @@ func parseStatusLine(payload string) (StatusLine, error) {
return StatusLine{}, ErrStatusCodeOutsideOfRange
}

reason_phrase := ""

for i := 2; i < len(split_line); i++ {
reason_phrase += split_line[i]
if i != len(split_line)-1 {
reason_phrase += " "
}
}

sl := StatusLine{
split_line[0],
uint16(status_code),
split_line[2],
reason_phrase,
}

slog.Info("parsed status line", "status_line", sl)
Expand Down
14 changes: 14 additions & 0 deletions internal/http/http_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ func TestParseStatusLine(t *testing.T) {
}
})

t.Run("Status line should parse when Bad Request is set, since that contains two words", func(t *testing.T) {
rawStatusLine := "HTTP/1.1 400 Bad Request"

sl, err := parseStatusLine(rawStatusLine)
if err != nil {
t.Errorf("Unexpected error when parsing status line %s", err)
}

if sl.ReasonPhrase != "Bad Request" {
t.Errorf("got %s want %s", sl.ReasonPhrase, "Bad Request")
}
})

t.Run("Status line fails when statuscode is not an integer", func(t *testing.T) {
rawStatusLine := "HTTP/1.1 FAIL OK"
_, err := parseStatusLine(rawStatusLine)
Expand Down Expand Up @@ -103,4 +116,5 @@ func TestHeaderParsing(t *testing.T) {
t.Errorf("got %s want %s", err, ErrInvalidHeaderFormat)
}
})

}
55 changes: 41 additions & 14 deletions internal/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,62 @@ package http

import (
"io"
"log/slog"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"
)

func XyzHandler(w http.ResponseWriter, r *http.Request) {
resp := "Hello World"
slog.Info("Received request with protocol type", "proto", r.Proto)
w.Header().Add("Content-Type", "text/plain")
w.WriteHeader(HttpStatusCodeOK)

w.Write(([]byte(resp)))
}

func MiddleWare(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Info("got a request", "request", r)
next.ServeHTTP(w, r)
})
}

func TestGet(t *testing.T) {
return
server, client := net.Pipe()
http_client := HttpClient{client}

go func() {
defer client.Close()
_, err := http_client.Get("xyz")
mux := http.NewServeMux()
mux.Handle("/", MiddleWare(http.HandlerFunc(XyzHandler)))

if err != nil {
t.Errorf("Unexpected error %s", err)
}
}()
server := httptest.NewServer(mux)
defer server.Close()

res, err := io.ReadAll(server)
url := strings.Split(server.URL, "://")[1]
conn, err := net.Dial("tcp", url)
if err != nil {
t.Errorf("failed to dial socket %s", err)
}
http_client := HttpClient{conn}
res, err := http_client.Get("/")

if err != nil {
t.Errorf("Unexpected error %s", err)
}

expected := "GET xyz HTTP/1.1\nHost: localhost\r\n\r\n"
if ct, err := res.ContentType(); err == nil && ct != "text/plain" {
t.Errorf("got %s want %s", ct, "text/plain")
}

if string(res) != expected {
t.Errorf("got %s want %s", string(res), expected)
if !res.Ok() {
t.Errorf("got %d want %d", res.StatusLine.StatusCode, HttpStatusCodeOK)
}

body := string(res.Body)

if body != "Hello World" {
t.Errorf("got %s want %s", body, "Hello World")
}
}

Expand Down
28 changes: 27 additions & 1 deletion internal/http/response.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package http

import "errors"
import (
"errors"
"strconv"
)

type Response struct {
StatusLine StatusLine
Headers map[string]string

Body []byte
}

type StatusLine struct {
Expand All @@ -15,6 +20,7 @@ type StatusLine struct {

var ErrNoContentTypefound = errors.New("no content type")
var ErrHeaderNotFound = errors.New("header not found")
var ErrInvalidContentLengthFormat = errors.New("invalid content lenght format")

func (r Response) ContentType() (string, error) {

Expand All @@ -36,3 +42,23 @@ func (r Response) TransferEncoding() (string, error) {

return res, nil
}

func (r Response) Ok() bool {
return r.StatusLine.StatusCode == HttpStatusCodeOK
}

func (r Response) ContentLength() (int64, error) {
res, ok := r.Headers["Content-Length"]

if !ok {
return -1, ErrHeaderNotFound
}

val, err := strconv.ParseInt(res, 10, 64)

if err != nil {
return -1, ErrInvalidContentLengthFormat
}

return val, nil
}

0 comments on commit 4179b2c

Please sign in to comment.