-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Add support for non buffered body server responses. #1657
base: master
Are you sure you want to change the base?
Changes from 2 commits
754125a
724588a
fbb0d48
50e5af5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -608,6 +608,47 @@ type RequestCtx struct { | |
hijackHandler HijackHandler | ||
hijackNoResponse bool | ||
formValueFunc FormValueFunc | ||
|
||
disableBuffering bool // disables buffered response body | ||
writer *bufio.Writer // used to send response in non-buffered mode | ||
bytesSent int // number of bytes sent to client (in non-buffered mode) | ||
bodyChunkStarted bool // true if the response body chunk has been started | ||
bodyLastChunkSent bool // true if the last chunk of the response body has been sent | ||
} | ||
|
||
// DisableBuffering modifies fasthttp to disable body buffering for this request. | ||
// This is useful for requests that return large data or stream data. | ||
// | ||
// When buffering is disabled you must: | ||
// 1. Set response status and header values before writing body | ||
// 2. Set ContentLength is optional. If not set, the server will use chunked encoding. | ||
// 3. Write body data using methods like ctx.Write or io.Copy(ctx,src), etc. | ||
// 4. Optionally call CloseResponse to finalize the response. | ||
// | ||
// CLosing the response will finalize the response and send the last chunk. | ||
// If the handler does not finish the response, it will be called automatically after handler returns. | ||
// Closing the response will also set BytesSent with the correct number of total bytes sent. | ||
func (ctx *RequestCtx) DisableBuffering() { | ||
ctx.disableBuffering = true | ||
} | ||
|
||
// CloseResponse finalizes non-buffered response dispatch. | ||
// This method must be called after performing non-buffered responses | ||
// If the handler does not finish the response, it will be called automatically | ||
// after the handler function returns. | ||
func (ctx *RequestCtx) CloseResponse() { | ||
if !ctx.disableBuffering || !ctx.bodyChunkStarted || ctx.bodyLastChunkSent { | ||
return | ||
} | ||
if ctx.writer != nil { | ||
// finalize chunks | ||
if ctx.bodyChunkStarted && ctx.Response.Header.IsHTTP11() && !ctx.bodyLastChunkSent { | ||
_, _ = ctx.writer.Write([]byte("0\r\n\r\n")) | ||
_ = ctx.writer.Flush() | ||
ctx.bytesSent += 5 | ||
} | ||
ctx.bodyLastChunkSent = true | ||
} | ||
} | ||
|
||
// HijackHandler must process the hijacked connection c. | ||
|
@@ -822,6 +863,12 @@ func (ctx *RequestCtx) reset() { | |
|
||
ctx.hijackHandler = nil | ||
ctx.hijackNoResponse = false | ||
|
||
ctx.writer = nil | ||
ctx.disableBuffering = false | ||
ctx.bytesSent = 0 | ||
ctx.bodyChunkStarted = false | ||
ctx.bodyLastChunkSent = false | ||
} | ||
|
||
type firstByteReader struct { | ||
|
@@ -1443,10 +1490,58 @@ func (ctx *RequestCtx) NotFound() { | |
|
||
// Write writes p into response body. | ||
func (ctx *RequestCtx) Write(p []byte) (int, error) { | ||
if ctx.disableBuffering { | ||
return ctx.writeDirect(p) | ||
} | ||
|
||
ctx.Response.AppendBody(p) | ||
return len(p), nil | ||
} | ||
|
||
// writeDirect writes p to underlying connection bypassing any buffering. | ||
func (ctx *RequestCtx) writeDirect(p []byte) (int, error) { | ||
// Non buffered response | ||
if ctx.writer == nil { | ||
ctx.writer = acquireWriter(ctx) | ||
} | ||
|
||
// Write headers if not written yet | ||
if !ctx.Response.headersWritten { | ||
if ctx.Response.Header.contentLength == 0 && ctx.Response.Header.IsHTTP11() { | ||
ctx.Response.Header.SetContentLength(-1) // means Transfer-Encoding = chunked | ||
} | ||
h := ctx.Response.Header.Header() | ||
n, err := ctx.writer.Write(h) | ||
if err != nil { | ||
return 0, err | ||
} | ||
ctx.bytesSent += n | ||
ctx.Response.headersWritten = true | ||
} | ||
|
||
// Write body. In chunks if content length is not set. | ||
if ctx.Response.Header.contentLength == -1 && ctx.Response.Header.IsHTTP11() { | ||
ctx.bodyChunkStarted = true | ||
err := writeChunk(ctx.writer, p) | ||
if err != nil { | ||
return 0, err | ||
} | ||
ctx.bytesSent += len(p) + 4 + countHexDigits(len(p)) | ||
return len(p), nil | ||
} | ||
|
||
n, err := ctx.writer.Write(p) | ||
ctx.bytesSent += n | ||
|
||
return n, err | ||
} | ||
|
||
// BytesSent returns the number of bytes sent to the client after non buffered operation. | ||
// Includes headers and body length. | ||
func (ctx *RequestCtx) BytesSent() int { | ||
return ctx.bytesSent | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why add this function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After streaming a response, it can be necessary to know the number of bytes sent. That number is known only if we a serving local resources, but unknown if we are proxying from external sources. Bytes sent can represent a cost related to data-transfer. It can be useful for logging and other analysis. We could return that value in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now I would remove this. This isn't supported with normal responses either so it's weird to only add it for this case now. |
||
|
||
// WriteString appends s to response body. | ||
func (ctx *RequestCtx) WriteString(s string) (int, error) { | ||
ctx.Response.AppendBodyString(s) | ||
|
@@ -2359,6 +2454,15 @@ func (s *Server) serveConn(c net.Conn) (err error) { | |
s.Handler(ctx) | ||
} | ||
|
||
if ctx.disableBuffering { | ||
ctx.CloseResponse() | ||
if ctx.writer != nil { | ||
releaseWriter(s, ctx.writer) | ||
ctx.writer = nil | ||
} | ||
break | ||
} | ||
|
||
timeoutResponse = ctx.timeoutResponse | ||
if timeoutResponse != nil { | ||
// Acquire a new ctx because the old one will still be in use by the timeout out handler. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this state needs to be kept here, you can just add a
headersWritten bool
toUnbufferedWriterHttp1
.