Allow no response to be send when a connection is hijacked (#712)

* Allow no response to be send when a connection is hijacked

At the moment there is always a HTTP response before the connection gets
hijacked. This second option to Hijack() prevents this response from
being send.

Fixes: https://github.com/valyala/fasthttp/issues/698

* Add HijackSetNoResponse method instead
This commit is contained in:
Erik Dubbelboer
2019-12-29 11:34:55 +01:00
committed by GitHub
parent 0724b3e064
commit 958ed36194
2 changed files with 87 additions and 26 deletions
+42 -26
View File
@@ -508,7 +508,8 @@ type RequestCtx struct {
timeoutCh chan struct{}
timeoutTimer *time.Timer
hijackHandler HijackHandler
hijackHandler HijackHandler
hijackNoResponse bool
}
// HijackHandler must process the hijacked connection c.
@@ -535,6 +536,7 @@ type HijackHandler func(c net.Conn)
// * Unexpected error during response writing to the connection.
//
// The server stops processing requests from hijacked connections.
//
// Server limits such as Concurrency, ReadTimeout, WriteTimeout, etc.
// aren't applied to hijacked connections.
//
@@ -550,6 +552,15 @@ func (ctx *RequestCtx) Hijack(handler HijackHandler) {
ctx.hijackHandler = handler
}
// HijackSetNoResponse changes the behavior of hijacking a request.
// If HijackSetNoResponse is called with false fasthttp will send a response
// to the client before calling the HijackHandler (default). If HijackSetNoResponse
// is called with true no response is send back before calling the
// HijackHandler supplied in the Hijack function.
func (ctx *RequestCtx) HijackSetNoResponse(noResponse bool) {
ctx.hijackNoResponse = noResponse
}
// Hijacked returns true after Hijack is called.
func (ctx *RequestCtx) Hijacked() bool {
return ctx.hijackHandler != nil
@@ -1869,9 +1880,10 @@ func (s *Server) serveConn(c net.Conn) error {
br *bufio.Reader
bw *bufio.Writer
err error
timeoutResponse *Response
hijackHandler HijackHandler
err error
timeoutResponse *Response
hijackHandler HijackHandler
hijackNoResponse bool
connectionClose bool
isHTTP11 bool
@@ -2044,6 +2056,8 @@ func (s *Server) serveConn(c net.Conn) error {
hijackHandler = ctx.hijackHandler
ctx.hijackHandler = nil
hijackNoResponse = ctx.hijackNoResponse
ctx.hijackNoResponse = false
ctx.userValues.Reset()
@@ -2071,30 +2085,32 @@ func (s *Server) serveConn(c net.Conn) error {
ctx.Response.Header.SetServerBytes(serverName)
}
if bw == nil {
bw = acquireWriter(ctx)
}
if err = writeResponse(ctx, bw); err != nil {
break
}
// Only flush the writer if we don't have another request in the pipeline.
// This is a big of an ugly optimization for https://www.techempower.com/benchmarks/
// This benchmark will send 16 pipelined requests. It is faster to pack as many responses
// in a TCP packet and send it back at once than waiting for a flush every request.
// In real world circumstances this behaviour could be argued as being wrong.
if br == nil || br.Buffered() == 0 || connectionClose {
err = bw.Flush()
if err != nil {
if !hijackNoResponse {
if bw == nil {
bw = acquireWriter(ctx)
}
if err = writeResponse(ctx, bw); err != nil {
break
}
}
if connectionClose {
break
}
if s.ReduceMemoryUsage {
releaseWriter(s, bw)
bw = nil
// Only flush the writer if we don't have another request in the pipeline.
// This is a big of an ugly optimization for https://www.techempower.com/benchmarks/
// This benchmark will send 16 pipelined requests. It is faster to pack as many responses
// in a TCP packet and send it back at once than waiting for a flush every request.
// In real world circumstances this behaviour could be argued as being wrong.
if br == nil || br.Buffered() == 0 || connectionClose {
err = bw.Flush()
if err != nil {
break
}
}
if connectionClose {
break
}
if s.ReduceMemoryUsage && hijackHandler == nil {
releaseWriter(s, bw)
bw = nil
}
}
if hijackHandler != nil {
+45
View File
@@ -2098,6 +2098,51 @@ func TestRequestCtxHijack(t *testing.T) {
}
}
func TestRequestCtxHijackNoResponse(t *testing.T) {
t.Parallel()
hijackDone := make(chan error)
s := &Server{
Handler: func(ctx *RequestCtx) {
ctx.Hijack(func(c net.Conn) {
_, err := c.Write([]byte("test"))
hijackDone <- err
})
ctx.HijackSetNoResponse(true)
},
}
rw := &readWriter{}
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\r\nContent-Length: 0\r\n\r\n")
ch := make(chan error)
go func() {
ch <- s.ServeConn(rw)
}()
select {
case err := <-ch:
if err != nil {
t.Fatalf("Unexpected error from serveConn: %s", err)
}
case <-time.After(100 * time.Millisecond):
t.Fatal("timeout")
}
select {
case err := <-hijackDone:
if err != nil {
t.Fatalf("Unexpected error from hijack: %s", err)
}
case <-time.After(100 * time.Millisecond):
t.Fatal("timeout")
}
if got := rw.w.String(); got != "test" {
t.Errorf(`expected "test", got %q`, got)
}
}
func TestRequestCtxInit(t *testing.T) {
var ctx RequestCtx
var logger testLogger