Skip to content

Commit

Permalink
fix: Status Line parsing and writing (#1135)
Browse files Browse the repository at this point in the history
* Adding zero-allocation uint64 to byte slice conversion and fixing the ResponseHeader.SetStatusLine function call signature

* Removing unnecessary i2b function

* Fixing various bugs

* Adding test cases

* Commenting AppendStatusLine

* Update status.go

Co-authored-by: Erik Dubbelboer <[email protected]>

* Update header.go

Co-authored-by: Erik Dubbelboer <[email protected]>

* Cleaning up references to strHTTP11, using formatStatusLine for invalidStatusLine, and making `appendStatusLine` an unexported function

Issue: #1132

* Fixing merge conflicts

Issue: #1132

* Replacing []byte{} with nil in some test cases

Issue: #1132

* Cleaning up parsing first line, and improving StatusMessage function

Issue: #1132

* Fixing as per PR

* Update header.go

Co-authored-by: Erik Dubbelboer <[email protected]>

* Update header.go

Co-authored-by: Erik Dubbelboer <[email protected]>

* Fixing as per requested changes

* Update header_test.go

Co-authored-by: Erik Dubbelboer <[email protected]>

Co-authored-by: Erik Dubbelboer <[email protected]>
  • Loading branch information
ShivanshVij and erikdubbelboer authored Nov 8, 2021
1 parent 931d0a4 commit 2ca01c7
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 60 deletions.
54 changes: 34 additions & 20 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ type ResponseHeader struct {
noDefaultDate bool

statusCode int
statusLine []byte
statusMessage []byte
protocol []byte
contentLength int
contentLengthBytes []byte
secureErrorLogMessage bool
Expand Down Expand Up @@ -137,17 +138,27 @@ func (h *ResponseHeader) SetStatusCode(statusCode int) {
h.statusCode = statusCode
}

// StatusLine returns response status line.
func (h *ResponseHeader) StatusLine() []byte {
if len(h.statusLine) > 0 {
return h.statusLine
// StatusMessage returns response status message.
func (h *ResponseHeader) StatusMessage() []byte {
return h.statusMessage
}

// SetStatusMessage sets response status message bytes.
func (h *ResponseHeader) SetStatusMessage(statusMessage []byte) {
h.statusMessage = append(h.statusMessage[:0], statusMessage...)
}

// Protocol returns response protocol bytes.
func (h *ResponseHeader) Protocol() []byte {
if len(h.protocol) > 0 {
return h.protocol
}
return statusLine(h.StatusCode())
return strHTTP11
}

// SetStatusLine sets response status line bytes.
func (h *ResponseHeader) SetStatusLine(statusLine []byte) {
h.statusLine = append(h.statusLine[:0], statusLine...)
// SetProtocol sets response protocol bytes.
func (h *ResponseHeader) SetProtocol(protocol []byte) {
h.protocol = append(h.protocol[:0], protocol...)
}

// SetLastModified sets 'Last-Modified' header to the given value.
Expand Down Expand Up @@ -697,7 +708,8 @@ func (h *ResponseHeader) resetSkipNormalize() {
h.connectionClose = false

h.statusCode = 0
h.statusLine = h.statusLine[:0]
h.statusMessage = h.statusMessage[:0]
h.protocol = h.protocol[:0]
h.contentLength = 0
h.contentLengthBytes = h.contentLengthBytes[:0]

Expand Down Expand Up @@ -746,7 +758,8 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) {
dst.noDefaultDate = h.noDefaultDate

dst.statusCode = h.statusCode
dst.statusLine = append(dst.statusLine, h.statusLine...)
dst.statusMessage = append(dst.statusMessage, h.statusMessage...)
dst.protocol = append(dst.protocol, h.protocol...)
dst.contentLength = h.contentLength
dst.contentLengthBytes = append(dst.contentLengthBytes, h.contentLengthBytes...)
dst.contentType = append(dst.contentType, h.contentType...)
Expand Down Expand Up @@ -1648,19 +1661,20 @@ func (h *ResponseHeader) String() string {
return string(h.Header())
}

// AppendBytes appends response header representation to dst and returns
// appendStatusLine appends the response status line to dst and returns
// the extended dst.
func (h *ResponseHeader) AppendBytes(dst []byte) []byte {
func (h *ResponseHeader) appendStatusLine(dst []byte) []byte {
statusCode := h.StatusCode()
if statusCode < 0 {
statusCode = StatusOK
}
return formatStatusLine(dst, h.Protocol(), statusCode, h.StatusMessage())
}

if len(h.statusLine) > 0 {
dst = append(dst, h.statusLine...)
} else {
dst = append(dst, statusLine(statusCode)...)
}
// AppendBytes appends response header representation to dst and returns
// the extended dst.
func (h *ResponseHeader) AppendBytes(dst []byte) []byte {
dst = h.appendStatusLine(dst[:0])

server := h.Server()
if len(server) != 0 {
Expand Down Expand Up @@ -1880,8 +1894,8 @@ func (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) {
}
return 0, fmt.Errorf("unexpected char at the end of status code. Response %q", buf)
}
if len(b) > n+1 && !bytes.Equal(b[n+1:], statusLine(h.statusCode)) {
h.SetStatusLine(b[n+1:])
if len(b) > n+1 {
h.SetStatusMessage(b[n+1:])
}

return len(buf) - len(bNext), nil
Expand Down
37 changes: 33 additions & 4 deletions header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,29 @@ func TestResponseHeaderMultiLineValue(t *testing.T) {
t.Fatalf("parse response using net/http failed, %s", err)
}

if !bytes.Equal(header.StatusLine(), []byte("SuperOK")) {
t.Errorf("parse status line with non-default value failed, got: %s want: SuperOK", header.StatusLine())
if !bytes.Equal(header.StatusMessage(), []byte("SuperOK")) {
t.Errorf("parse status line with non-default value failed, got: '%s' want: 'SuperOK'", header.StatusMessage())
}

header.SetProtocol([]byte("HTTP/3.3"))
if !bytes.Equal(header.Protocol(), []byte("HTTP/3.3")) {
t.Errorf("parse protocol with non-default value failed, got: '%s' want: 'HTTP/3.3'", header.Protocol())
}

if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 SuperOK\r\n")) {
t.Errorf("parse status line with non-default value failed, got: '%s' want: 'HTTP/3.3 200 SuperOK'", header.Protocol())
}

header.SetStatusMessage(nil)

if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) {
t.Errorf("parse status line with default protocol value failed, got: '%s' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil))
}

header.SetStatusMessage(s2b(StatusMessage(200)))

if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) {
t.Errorf("parse status line with default protocol value failed, got: '%s' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil))
}

for name, vals := range response.Header {
Expand Down Expand Up @@ -83,8 +104,16 @@ func TestResponseHeaderMultiLineName(t *testing.T) {
t.Errorf("expected error, got %q (%v)", m, err)
}

if !bytes.Equal(header.StatusLine(), []byte("OK")) {
t.Errorf("expected default status line, got: %s", header.StatusLine())
if !bytes.Equal(header.StatusMessage(), []byte("OK")) {
t.Errorf("expected default status line, got: %s", header.StatusMessage())
}

if !bytes.Equal(header.Protocol(), []byte("HTTP/1.1")) {
t.Errorf("expected default protocol, got: %s", header.Protocol())
}

if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/1.1 200 OK\r\n")) {
t.Errorf("parse status line with non-default value failed, got: %s want: HTTP/1.1 200 OK", header.Protocol())
}
}

Expand Down
4 changes: 2 additions & 2 deletions http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,9 +837,9 @@ func TestResponseSkipBody(t *testing.T) {
t.Fatalf("unexpected content-type in response %q", s)
}

// set StatusNoContent with statusLine
// set StatusNoContent with statusMessage
r.Header.SetStatusCode(StatusNoContent)
r.Header.SetStatusLine([]byte("HTTP/1.1 204 NC\r\n"))
r.Header.SetStatusMessage([]byte("NC"))
r.SetBodyString("foobar")
s = r.String()
if strings.Contains(s, "\r\n\r\nfoobar") {
Expand Down
2 changes: 1 addition & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2741,7 +2741,7 @@ func (s *Server) getServerName() []byte {
}

func (s *Server) writeFastError(w io.Writer, statusCode int, msg string) {
w.Write(statusLine(statusCode)) //nolint:errcheck
w.Write(formatStatusLine(nil, strHTTP11, statusCode, s2b(StatusMessage(statusCode)))) //nolint:errcheck

server := ""
if !s.NoDefaultServerHeader {
Expand Down
46 changes: 15 additions & 31 deletions status.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fasthttp

import (
"fmt"
"strconv"
)

Expand Down Expand Up @@ -81,7 +80,7 @@ const (
)

var (
statusLines = make([][]byte, statusMessageMax+1)
unknownStatusCode = "Unknown Status Code"

statusMessages = []string{
StatusContinue: "Continue",
Expand Down Expand Up @@ -155,39 +154,24 @@ var (
// StatusMessage returns HTTP status message for the given status code.
func StatusMessage(statusCode int) string {
if statusCode < statusMessageMin || statusCode > statusMessageMax {
return "Unknown Status Code"
return unknownStatusCode
}

s := statusMessages[statusCode]
if s == "" {
s = "Unknown Status Code"
if s := statusMessages[statusCode]; s != "" {
return s
}
return s
return unknownStatusCode
}

func init() {
// Fill all valid status lines
for i := 0; i < len(statusLines); i++ {
statusLines[i] = []byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n", i, StatusMessage(i)))
func formatStatusLine(dst []byte, protocol []byte, statusCode int, statusText []byte) []byte {
dst = append(dst, protocol...)
dst = append(dst, ' ')
dst = strconv.AppendInt(dst, int64(statusCode), 10)
dst = append(dst, ' ')
if len(statusText) == 0 {
dst = append(dst, s2b(StatusMessage(statusCode))...)
} else {
dst = append(dst, statusText...)
}
}

func statusLine(statusCode int) []byte {
if statusCode < 0 || statusCode > statusMessageMax {
return invalidStatusLine(statusCode)
}

return statusLines[statusCode]
}

func invalidStatusLine(statusCode int) []byte {
statusText := StatusMessage(statusCode)
// xxx placeholder of status code
var line = make([]byte, 0, len("HTTP/1.1 xxx \r\n")+len(statusText))
line = append(line, "HTTP/1.1 "...)
line = strconv.AppendInt(line, int64(statusCode), 10)
line = append(line, ' ')
line = append(line, statusText...)
line = append(line, "\r\n"...)
return line
return append(dst, strCRLF...)
}
2 changes: 1 addition & 1 deletion status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestStatusLine(t *testing.T) {
}

func testStatusLine(t *testing.T, statusCode int, expected []byte) {
line := statusLine(statusCode)
line := formatStatusLine(nil, strHTTP11, statusCode, s2b(StatusMessage(statusCode)))
if !bytes.Equal(expected, line) {
t.Fatalf("unexpected status line %s. Expecting %s", string(line), string(expected))
}
Expand Down
2 changes: 1 addition & 1 deletion status_timing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func BenchmarkStatusLine512(b *testing.B) {
func benchmarkStatusLine(b *testing.B, statusCode int, expected []byte) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
line := statusLine(statusCode)
line := formatStatusLine(nil, strHTTP11, statusCode, s2b(StatusMessage(statusCode)))
if !bytes.Equal(expected, line) {
b.Fatalf("unexpected status line %s. Expecting %s", string(line), string(expected))
}
Expand Down

0 comments on commit 2ca01c7

Please sign in to comment.