Skip to content

Commit

Permalink
Adding zero-allocation uint64 to byte slice conversion and fixing the…
Browse files Browse the repository at this point in the history
… ResponseHeader.SetStatusLine function call signature
  • Loading branch information
uwaterloo gitlab committed Oct 24, 2021
1 parent 528dd62 commit 36ba831
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 10 deletions.
69 changes: 69 additions & 0 deletions bytesconv.go
Expand Up @@ -350,6 +350,75 @@ func s2b(s string) (b []byte) {
return b
}

const host32bit = ^uint(0)>>32 == 0

const smallsString = "00010203040506070809" +
"10111213141516171819" +
"20212223242526272829" +
"30313233343536373839" +
"40414243444546474849" +
"50515253545556575859" +
"60616263646566676869" +
"70717273747576777879" +
"80818283848586878889" +
"90919293949596979899"

// i2b converts a uint64 to a byte slice without memory allocation.
//
// Note that this is a slightly modified version of strconv.formatBits
func i2b(dst []byte, i uint64) []byte {
var a [64 + 1]byte
ignore := len(a)

// convert bits
// We use uint values where we can because those will
// fit into a single register even on a 32bit machine.
if host32bit {
// convert the lower digits using 32bit operations
for i >= 1e9 {
// Avoid using r = a%b in addition to q = a/b
// since 64bit division and modulo operations
// are calculated by runtime functions on 32bit machines.
q := i / 1e9
is := uint(i - q*1e9) // i % 1e9 fits into a uint
for j := 4; j > 0; j-- {
is := is % 100 * 2
is /= 100
ignore -= 2
a[ignore+1] = smallsString[is+1]
a[ignore+0] = smallsString[is+0]
}

// is < 10, since it contains the last digit
// from the initial 9-digit is.
ignore--
a[ignore] = smallsString[is*2+1]

i = q
}
// i < 1e9
}
// i guaranteed to fit into a uint
us := uint(i)
for us >= 100 {
is := us % 100 * 2
us /= 100
ignore -= 2
a[ignore+1] = smallsString[is+1]
a[ignore+0] = smallsString[is+0]
}

// us < 100
is := us * 2
ignore--
a[ignore] = smallsString[is+1]
if us >= 10 {
ignore--
a[ignore] = smallsString[is]
}
return append(dst, a[ignore:]...)
}

// AppendUnquotedArg appends url-decoded src to dst and returns appended dst.
//
// dst may point to src. In this case src will be overwritten.
Expand Down
8 changes: 4 additions & 4 deletions header.go
Expand Up @@ -146,8 +146,8 @@ func (h *ResponseHeader) StatusLine() []byte {
}

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

// SetLastModified sets 'Last-Modified' header to the given value.
Expand Down Expand Up @@ -1880,8 +1880,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 && !bytes.Equal(b[n+1:], statusLines[h.statusCode]) {
h.SetStatusLine(h.statusCode, b[n+1:])
}

return len(buf) - len(bNext), nil
Expand Down
6 changes: 3 additions & 3 deletions header_test.go
Expand Up @@ -52,8 +52,8 @@ 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.StatusLine(), []byte("HTTP/1.1 200 SuperOK\r\n")) {
t.Errorf("parse status line with non-default value failed, got: %s want: HTTP/1.1 200 SuperOK", header.StatusLine())
}

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

if !bytes.Equal(header.StatusLine(), []byte("OK")) {
if !bytes.Equal(header.StatusLine(), []byte("HTTP/1.1 200 OK\r\n")) {
t.Errorf("expected default status line, got: %s", header.StatusLine())
}
}
Expand Down
2 changes: 1 addition & 1 deletion http_test.go
Expand Up @@ -839,7 +839,7 @@ func TestResponseSkipBody(t *testing.T) {

// set StatusNoContent with statusLine
r.Header.SetStatusCode(StatusNoContent)
r.Header.SetStatusLine([]byte("HTTP/1.1 204 NC\r\n"))
r.Header.SetStatusLine(204, []byte("NC"))
r.SetBodyString("foobar")
s = r.String()
if strings.Contains(s, "\r\n\r\nfoobar") {
Expand Down
15 changes: 13 additions & 2 deletions status.go
@@ -1,7 +1,6 @@
package fasthttp

import (
"fmt"
"strconv"
)

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

var (
httpHeader = []byte("HTTP/1.1")

statusLines = make([][]byte, statusMessageMax+1)

statusMessages = []string{
Expand Down Expand Up @@ -168,10 +169,20 @@ func StatusMessage(statusCode int) string {
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)))
statusLines[i] = formatStatusLine([]byte{}, i, []byte(StatusMessage(i)))
}
}

func formatStatusLine(dst []byte, statusCode int, statusMessage []byte) []byte {
dst = append(dst[:0], httpHeader...)
dst = append(dst, ' ')
dst = i2b(dst, uint64(statusCode))
dst = append(dst, ' ')
dst = append(dst, statusMessage...)
dst = append(dst, strCRLF...)
return dst
}

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

0 comments on commit 36ba831

Please sign in to comment.