Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve statusLine and StatusMessage by using slice instead of map #855

Merged
merged 4 commits into from
Aug 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package fasthttp

import (
"fmt"
"sync/atomic"
)

const (
statusMessageMin = 100
statusMessageMax = 511
)

// HTTP status codes were stolen from net/http.
const (
StatusContinue = 100 // RFC 7231, 6.2.1
StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
StatusProcessing = 102 // RFC 2518, 10.1
StatusEarlyHints = 103 // RFC 8297

StatusOK = 200 // RFC 7231, 6.3.1
StatusCreated = 201 // RFC 7231, 6.3.2
Expand Down Expand Up @@ -51,6 +56,7 @@ const (
StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
StatusExpectationFailed = 417 // RFC 7231, 6.5.14
StatusTeapot = 418 // RFC 7168, 2.3.3
StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2
StatusUnprocessableEntity = 422 // RFC 4918, 11.2
StatusLocked = 423 // RFC 4918, 11.3
StatusFailedDependency = 424 // RFC 4918, 11.4
Expand All @@ -74,12 +80,13 @@ const (
)

var (
statusLines atomic.Value
statusLines = make([][]byte, statusMessageMax+1)

statusMessages = map[int]string{
statusMessages = []string{
StatusContinue: "Continue",
StatusSwitchingProtocols: "Switching Protocols",
StatusProcessing: "Processing",
StatusEarlyHints: "Early Hints",

StatusOK: "OK",
StatusCreated: "Created",
Expand Down Expand Up @@ -120,6 +127,7 @@ var (
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
StatusExpectationFailed: "Expectation Failed",
StatusTeapot: "I'm a teapot",
StatusMisdirectedRequest: "Misdirected Request",
StatusUnprocessableEntity: "Unprocessable Entity",
StatusLocked: "Locked",
StatusFailedDependency: "Failed Dependency",
Expand All @@ -145,6 +153,10 @@ 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"
}

s := statusMessages[statusCode]
if s == "" {
s = "Unknown Status Code"
Expand All @@ -153,24 +165,21 @@ func StatusMessage(statusCode int) string {
}

func init() {
statusLines.Store(make(map[int][]byte))
// 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 statusLine(statusCode int) []byte {
m := statusLines.Load().(map[int][]byte)
h := m[statusCode]
if h != nil {
return h
if statusCode < 0 || statusCode > statusMessageMax {
return invalidStatusLine(statusCode)
}

statusText := StatusMessage(statusCode)
return statusLines[statusCode]
}

h = []byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n", statusCode, statusText))
newM := make(map[int][]byte, len(m)+1)
for k, v := range m {
newM[k] = v
}
newM[statusCode] = h
statusLines.Store(newM)
return h
func invalidStatusLine(statusCode int) []byte {
statusText := StatusMessage(statusCode)
return []byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n", statusCode, statusText))
}
24 changes: 24 additions & 0 deletions status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package fasthttp

import (
"bytes"
"testing"
)

func TestStatusLine(t *testing.T) {
t.Parallel()

testStatusLine(t, -1, []byte("HTTP/1.1 -1 Unknown Status Code\r\n"))
testStatusLine(t, 99, []byte("HTTP/1.1 99 Unknown Status Code\r\n"))
testStatusLine(t, 200, []byte("HTTP/1.1 200 OK\r\n"))
testStatusLine(t, 512, []byte("HTTP/1.1 512 Unknown Status Code\r\n"))
testStatusLine(t, 512, []byte("HTTP/1.1 512 Unknown Status Code\r\n"))
testStatusLine(t, 520, []byte("HTTP/1.1 520 Unknown Status Code\r\n"))
}

func testStatusLine(t *testing.T, statusCode int, expected []byte) {
line := statusLine(statusCode)
if !bytes.Equal(expected, line) {
t.Fatalf("unexpected status line %s. Expecting %s", string(line), string(expected))
}
}
29 changes: 29 additions & 0 deletions status_timing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fasthttp

import (
"bytes"
"testing"
)

func BenchmarkStatusLine99(b *testing.B) {
benchmarkStatusLine(b, 99, []byte("HTTP/1.1 99 Unknown Status Code\r\n"))
}

func BenchmarkStatusLine200(b *testing.B) {
benchmarkStatusLine(b, 200, []byte("HTTP/1.1 200 OK\r\n"))
}

func BenchmarkStatusLine512(b *testing.B) {
benchmarkStatusLine(b, 512, []byte("HTTP/1.1 512 Unknown Status Code\r\n"))
}

func benchmarkStatusLine(b *testing.B, statusCode int, expected []byte) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
line := statusLine(statusCode)
if !bytes.Equal(expected, line) {
b.Fatalf("unexpected status line %s. Expecting %s", string(line), string(expected))
}
}
})
}