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"
|
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.
|
// Interface for an HTTP server.
|
||||||
type Server interface {
|
type Server interface {
|
||||||
Get(path string, handler Handler)
|
Get(path string, handler Handler)
|
||||||
|
@ -33,6 +76,7 @@ type Server interface {
|
||||||
type server struct {
|
type server struct {
|
||||||
handlers []Handler
|
handlers []Handler
|
||||||
contextPool sync.Pool
|
contextPool sync.Pool
|
||||||
|
bufferPool sync.Pool
|
||||||
router *router.Router[Handler]
|
router *router.Router[Handler]
|
||||||
errorHandler func(Context, error)
|
errorHandler func(Context, error)
|
||||||
}
|
}
|
||||||
|
@ -61,6 +105,7 @@ func NewServer() Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.contextPool.New = func() any { return s.newContext() }
|
s.contextPool.New = func() any { return s.newContext() }
|
||||||
|
s.bufferPool.New = func() any { return bytes.NewBuffer(make([]byte, 0, 1024)) }
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +228,7 @@ func (s *server) handleConnection(conn net.Conn) {
|
||||||
url = message[space+1 : lastSpace]
|
url = message[space+1 : lastSpace]
|
||||||
|
|
||||||
// Add headers until we meet an empty line
|
// Add headers until we meet an empty line
|
||||||
|
ctx.request.headers = ctx.request.headers[:0] // Reset headers without allocation
|
||||||
for {
|
for {
|
||||||
message, err = ctx.reader.ReadString('\n')
|
message, err = ctx.reader.ReadString('\n')
|
||||||
|
|
||||||
|
@ -212,15 +258,20 @@ func (s *server) handleConnection(conn net.Conn) {
|
||||||
// Read the body, if any
|
// Read the body, if any
|
||||||
if contentLength := ctx.request.Header(HeaderContentLength); contentLength != "" {
|
if contentLength := ctx.request.Header(HeaderContentLength); contentLength != "" {
|
||||||
length, _ := strconv.Atoi(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)
|
ctx.reader.Read(ctx.request.body)
|
||||||
|
} else {
|
||||||
|
ctx.request.body = ctx.request.body[:0] // Empty the body slice without allocation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the request
|
// Handle the request
|
||||||
s.handleRequest(ctx, method, url, conn)
|
s.handleRequest(ctx, method, url, conn)
|
||||||
|
|
||||||
// Clean up the context
|
// Clean up the context - reset slices without allocation
|
||||||
ctx.request.headers = ctx.request.headers[:0]
|
|
||||||
ctx.request.body = ctx.request.body[:0]
|
ctx.request.body = ctx.request.body[:0]
|
||||||
ctx.response.headers = ctx.response.headers[:0]
|
ctx.response.headers = ctx.response.headers[:0]
|
||||||
ctx.response.body = ctx.response.body[: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) {
|
func (s *server) handleRequest(ctx *context, method string, url string, writer io.Writer) {
|
||||||
ctx.method = method
|
ctx.method = method
|
||||||
ctx.scheme, ctx.host, ctx.path, ctx.query = parseURL(url)
|
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)
|
s.errorHandler(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := bytes.Buffer{}
|
// Get buffer from pool
|
||||||
tmp.WriteString("HTTP/1.1 ")
|
buf := s.bufferPool.Get().(*bytes.Buffer)
|
||||||
tmp.WriteString(strconv.Itoa(int(ctx.status)))
|
buf.Reset()
|
||||||
tmp.WriteString("\r\n" + HeaderContentLength + ": ")
|
defer s.bufferPool.Put(buf)
|
||||||
tmp.WriteString(strconv.Itoa(len(ctx.response.body)))
|
|
||||||
tmp.WriteString("\r\n")
|
|
||||||
|
|
||||||
for _, header := range ctx.response.headers {
|
// Write status line using map lookup for efficiency
|
||||||
tmp.WriteString(header.Key)
|
if statusLine, ok := statusLines[int(ctx.status)]; ok {
|
||||||
tmp.WriteString(": ")
|
buf.Write(statusLine)
|
||||||
tmp.WriteString(header.Value)
|
} else {
|
||||||
tmp.WriteString("\r\n")
|
// 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")
|
// Write Content-Length header
|
||||||
tmp.Write(ctx.response.body)
|
buf.Write(contentLengthHeader)
|
||||||
writer.Write(tmp.Bytes())
|
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.
|
// Allocates a new context with the default state.
|
||||||
|
@ -266,7 +338,7 @@ func (s *server) newContext() *context {
|
||||||
server: s,
|
server: s,
|
||||||
request: request{
|
request: request{
|
||||||
reader: bufio.NewReader(nil),
|
reader: bufio.NewReader(nil),
|
||||||
body: make([]byte, 0),
|
body: make([]byte, 0, 1024),
|
||||||
headers: make([]Header, 0, 8),
|
headers: make([]Header, 0, 8),
|
||||||
params: make([]router.Parameter, 0, 8),
|
params: make([]router.Parameter, 0, 8),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user