ref
This commit is contained in:
parent
9295e5445e
commit
47a1b56619
110
server.go
110
server.go
|
@ -16,6 +16,49 @@ import (
|
|||
router "git.sharkk.net/Go/Router"
|
||||
)
|
||||
|
||||
// Pre-allocated response components
|
||||
var (
|
||||
// Common strings for response headers
|
||||
contentLengthHeader = []byte(HeaderContentLength + ": ")
|
||||
crlf = []byte("\r\n")
|
||||
colonSpace = []byte(": ")
|
||||
|
||||
// Status line map for quick lookups
|
||||
statusLines = map[int][]byte{
|
||||
100: []byte("HTTP/1.1 100 Continue\r\n"),
|
||||
101: []byte("HTTP/1.1 101 Switching Protocols\r\n"),
|
||||
200: []byte("HTTP/1.1 200 OK\r\n"),
|
||||
201: []byte("HTTP/1.1 201 Created\r\n"),
|
||||
202: []byte("HTTP/1.1 202 Accepted\r\n"),
|
||||
204: []byte("HTTP/1.1 204 No Content\r\n"),
|
||||
206: []byte("HTTP/1.1 206 Partial Content\r\n"),
|
||||
300: []byte("HTTP/1.1 300 Multiple Choices\r\n"),
|
||||
301: []byte("HTTP/1.1 301 Moved Permanently\r\n"),
|
||||
302: []byte("HTTP/1.1 302 Found\r\n"),
|
||||
304: []byte("HTTP/1.1 304 Not Modified\r\n"),
|
||||
307: []byte("HTTP/1.1 307 Temporary Redirect\r\n"),
|
||||
308: []byte("HTTP/1.1 308 Permanent Redirect\r\n"),
|
||||
400: []byte("HTTP/1.1 400 Bad Request\r\n"),
|
||||
401: []byte("HTTP/1.1 401 Unauthorized\r\n"),
|
||||
403: []byte("HTTP/1.1 403 Forbidden\r\n"),
|
||||
404: []byte("HTTP/1.1 404 Not Found\r\n"),
|
||||
405: []byte("HTTP/1.1 405 Method Not Allowed\r\n"),
|
||||
406: []byte("HTTP/1.1 406 Not Acceptable\r\n"),
|
||||
409: []byte("HTTP/1.1 409 Conflict\r\n"),
|
||||
410: []byte("HTTP/1.1 410 Gone\r\n"),
|
||||
412: []byte("HTTP/1.1 412 Precondition Failed\r\n"),
|
||||
413: []byte("HTTP/1.1 413 Payload Too Large\r\n"),
|
||||
415: []byte("HTTP/1.1 415 Unsupported Media Type\r\n"),
|
||||
416: []byte("HTTP/1.1 416 Range Not Satisfiable\r\n"),
|
||||
429: []byte("HTTP/1.1 429 Too Many Requests\r\n"),
|
||||
500: []byte("HTTP/1.1 500 Internal Server Error\r\n"),
|
||||
501: []byte("HTTP/1.1 501 Not Implemented\r\n"),
|
||||
502: []byte("HTTP/1.1 502 Bad Gateway\r\n"),
|
||||
503: []byte("HTTP/1.1 503 Service Unavailable\r\n"),
|
||||
504: []byte("HTTP/1.1 504 Gateway Timeout\r\n"),
|
||||
}
|
||||
)
|
||||
|
||||
// Interface for an HTTP server.
|
||||
type Server interface {
|
||||
Get(path string, handler Handler)
|
||||
|
@ -33,6 +76,7 @@ type Server interface {
|
|||
type server struct {
|
||||
handlers []Handler
|
||||
contextPool sync.Pool
|
||||
bufferPool sync.Pool
|
||||
router *router.Router[Handler]
|
||||
errorHandler func(Context, error)
|
||||
}
|
||||
|
@ -61,6 +105,7 @@ func NewServer() Server {
|
|||
}
|
||||
|
||||
s.contextPool.New = func() any { return s.newContext() }
|
||||
s.bufferPool.New = func() any { return bytes.NewBuffer(make([]byte, 0, 1024)) }
|
||||
return s
|
||||
}
|
||||
|
||||
|
@ -183,6 +228,7 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
url = message[space+1 : lastSpace]
|
||||
|
||||
// Add headers until we meet an empty line
|
||||
ctx.request.headers = ctx.request.headers[:0] // Reset headers without allocation
|
||||
for {
|
||||
message, err = ctx.reader.ReadString('\n')
|
||||
|
||||
|
@ -212,15 +258,20 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
// Read the body, if any
|
||||
if contentLength := ctx.request.Header(HeaderContentLength); contentLength != "" {
|
||||
length, _ := strconv.Atoi(contentLength)
|
||||
ctx.request.body = make([]byte, length)
|
||||
if cap(ctx.request.body) >= length {
|
||||
ctx.request.body = ctx.request.body[:length] // Reuse existing slice if possible
|
||||
} else {
|
||||
ctx.request.body = make([]byte, length)
|
||||
}
|
||||
ctx.reader.Read(ctx.request.body)
|
||||
} else {
|
||||
ctx.request.body = ctx.request.body[:0] // Empty the body slice without allocation
|
||||
}
|
||||
|
||||
// Handle the request
|
||||
s.handleRequest(ctx, method, url, conn)
|
||||
|
||||
// Clean up the context
|
||||
ctx.request.headers = ctx.request.headers[:0]
|
||||
// Clean up the context - reset slices without allocation
|
||||
ctx.request.body = ctx.request.body[:0]
|
||||
ctx.response.headers = ctx.response.headers[:0]
|
||||
ctx.response.body = ctx.response.body[:0]
|
||||
|
@ -230,7 +281,7 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
}
|
||||
}
|
||||
|
||||
// Handles the given request.
|
||||
// Handles the given request with reduced allocations.
|
||||
func (s *server) handleRequest(ctx *context, method string, url string, writer io.Writer) {
|
||||
ctx.method = method
|
||||
ctx.scheme, ctx.host, ctx.path, ctx.query = parseURL(url)
|
||||
|
@ -241,23 +292,44 @@ func (s *server) handleRequest(ctx *context, method string, url string, writer i
|
|||
s.errorHandler(ctx, err)
|
||||
}
|
||||
|
||||
tmp := bytes.Buffer{}
|
||||
tmp.WriteString("HTTP/1.1 ")
|
||||
tmp.WriteString(strconv.Itoa(int(ctx.status)))
|
||||
tmp.WriteString("\r\n" + HeaderContentLength + ": ")
|
||||
tmp.WriteString(strconv.Itoa(len(ctx.response.body)))
|
||||
tmp.WriteString("\r\n")
|
||||
// Get buffer from pool
|
||||
buf := s.bufferPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer s.bufferPool.Put(buf)
|
||||
|
||||
for _, header := range ctx.response.headers {
|
||||
tmp.WriteString(header.Key)
|
||||
tmp.WriteString(": ")
|
||||
tmp.WriteString(header.Value)
|
||||
tmp.WriteString("\r\n")
|
||||
// Write status line using map lookup for efficiency
|
||||
if statusLine, ok := statusLines[int(ctx.status)]; ok {
|
||||
buf.Write(statusLine)
|
||||
} else {
|
||||
// For uncommon status codes, format the line dynamically
|
||||
buf.WriteString("HTTP/1.1 ")
|
||||
buf.WriteString(strconv.Itoa(int(ctx.status)))
|
||||
buf.Write(crlf)
|
||||
}
|
||||
|
||||
tmp.WriteString("\r\n")
|
||||
tmp.Write(ctx.response.body)
|
||||
writer.Write(tmp.Bytes())
|
||||
// Write Content-Length header
|
||||
buf.Write(contentLengthHeader)
|
||||
buf.WriteString(strconv.Itoa(len(ctx.response.body)))
|
||||
buf.Write(crlf)
|
||||
|
||||
// Write all response headers
|
||||
for _, header := range ctx.response.headers {
|
||||
buf.WriteString(header.Key)
|
||||
buf.Write(colonSpace)
|
||||
buf.WriteString(header.Value)
|
||||
buf.Write(crlf)
|
||||
}
|
||||
|
||||
// End headers
|
||||
buf.Write(crlf)
|
||||
|
||||
// Write headers
|
||||
writer.Write(buf.Bytes())
|
||||
|
||||
// Write body directly to avoid another copy
|
||||
if len(ctx.response.body) > 0 {
|
||||
writer.Write(ctx.response.body)
|
||||
}
|
||||
}
|
||||
|
||||
// Allocates a new context with the default state.
|
||||
|
@ -266,7 +338,7 @@ func (s *server) newContext() *context {
|
|||
server: s,
|
||||
request: request{
|
||||
reader: bufio.NewReader(nil),
|
||||
body: make([]byte, 0),
|
||||
body: make([]byte, 0, 1024),
|
||||
headers: make([]Header, 0, 8),
|
||||
params: make([]router.Parameter, 0, 8),
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue
Block a user