Skip to content

Commit 958ed36

Browse files
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: #698 * Add HijackSetNoResponse method instead
1 parent 0724b3e commit 958ed36

File tree

2 files changed

+86
-25
lines changed

2 files changed

+86
-25
lines changed

server.go

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,8 @@ type RequestCtx struct {
508508
timeoutCh chan struct{}
509509
timeoutTimer *time.Timer
510510

511-
hijackHandler HijackHandler
511+
hijackHandler HijackHandler
512+
hijackNoResponse bool
512513
}
513514

514515
// HijackHandler must process the hijacked connection c.
@@ -535,6 +536,7 @@ type HijackHandler func(c net.Conn)
535536
// * Unexpected error during response writing to the connection.
536537
//
537538
// The server stops processing requests from hijacked connections.
539+
//
538540
// Server limits such as Concurrency, ReadTimeout, WriteTimeout, etc.
539541
// aren't applied to hijacked connections.
540542
//
@@ -550,6 +552,15 @@ func (ctx *RequestCtx) Hijack(handler HijackHandler) {
550552
ctx.hijackHandler = handler
551553
}
552554

555+
// HijackSetNoResponse changes the behavior of hijacking a request.
556+
// If HijackSetNoResponse is called with false fasthttp will send a response
557+
// to the client before calling the HijackHandler (default). If HijackSetNoResponse
558+
// is called with true no response is send back before calling the
559+
// HijackHandler supplied in the Hijack function.
560+
func (ctx *RequestCtx) HijackSetNoResponse(noResponse bool) {
561+
ctx.hijackNoResponse = noResponse
562+
}
563+
553564
// Hijacked returns true after Hijack is called.
554565
func (ctx *RequestCtx) Hijacked() bool {
555566
return ctx.hijackHandler != nil
@@ -1869,9 +1880,10 @@ func (s *Server) serveConn(c net.Conn) error {
18691880
br *bufio.Reader
18701881
bw *bufio.Writer
18711882

1872-
err error
1873-
timeoutResponse *Response
1874-
hijackHandler HijackHandler
1883+
err error
1884+
timeoutResponse *Response
1885+
hijackHandler HijackHandler
1886+
hijackNoResponse bool
18751887

18761888
connectionClose bool
18771889
isHTTP11 bool
@@ -2044,6 +2056,8 @@ func (s *Server) serveConn(c net.Conn) error {
20442056

20452057
hijackHandler = ctx.hijackHandler
20462058
ctx.hijackHandler = nil
2059+
hijackNoResponse = ctx.hijackNoResponse
2060+
ctx.hijackNoResponse = false
20472061

20482062
ctx.userValues.Reset()
20492063

@@ -2071,30 +2085,32 @@ func (s *Server) serveConn(c net.Conn) error {
20712085
ctx.Response.Header.SetServerBytes(serverName)
20722086
}
20732087

2074-
if bw == nil {
2075-
bw = acquireWriter(ctx)
2076-
}
2077-
if err = writeResponse(ctx, bw); err != nil {
2078-
break
2079-
}
2088+
if !hijackNoResponse {
2089+
if bw == nil {
2090+
bw = acquireWriter(ctx)
2091+
}
2092+
if err = writeResponse(ctx, bw); err != nil {
2093+
break
2094+
}
20802095

2081-
// Only flush the writer if we don't have another request in the pipeline.
2082-
// This is a big of an ugly optimization for https://www.techempower.com/benchmarks/
2083-
// This benchmark will send 16 pipelined requests. It is faster to pack as many responses
2084-
// in a TCP packet and send it back at once than waiting for a flush every request.
2085-
// In real world circumstances this behaviour could be argued as being wrong.
2086-
if br == nil || br.Buffered() == 0 || connectionClose {
2087-
err = bw.Flush()
2088-
if err != nil {
2096+
// Only flush the writer if we don't have another request in the pipeline.
2097+
// This is a big of an ugly optimization for https://www.techempower.com/benchmarks/
2098+
// This benchmark will send 16 pipelined requests. It is faster to pack as many responses
2099+
// in a TCP packet and send it back at once than waiting for a flush every request.
2100+
// In real world circumstances this behaviour could be argued as being wrong.
2101+
if br == nil || br.Buffered() == 0 || connectionClose {
2102+
err = bw.Flush()
2103+
if err != nil {
2104+
break
2105+
}
2106+
}
2107+
if connectionClose {
20892108
break
20902109
}
2091-
}
2092-
if connectionClose {
2093-
break
2094-
}
2095-
if s.ReduceMemoryUsage {
2096-
releaseWriter(s, bw)
2097-
bw = nil
2110+
if s.ReduceMemoryUsage && hijackHandler == nil {
2111+
releaseWriter(s, bw)
2112+
bw = nil
2113+
}
20982114
}
20992115

21002116
if hijackHandler != nil {

server_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,6 +2098,51 @@ func TestRequestCtxHijack(t *testing.T) {
20982098
}
20992099
}
21002100

2101+
func TestRequestCtxHijackNoResponse(t *testing.T) {
2102+
t.Parallel()
2103+
2104+
hijackDone := make(chan error)
2105+
s := &Server{
2106+
Handler: func(ctx *RequestCtx) {
2107+
ctx.Hijack(func(c net.Conn) {
2108+
_, err := c.Write([]byte("test"))
2109+
hijackDone <- err
2110+
})
2111+
ctx.HijackSetNoResponse(true)
2112+
},
2113+
}
2114+
2115+
rw := &readWriter{}
2116+
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\r\nContent-Length: 0\r\n\r\n")
2117+
2118+
ch := make(chan error)
2119+
go func() {
2120+
ch <- s.ServeConn(rw)
2121+
}()
2122+
2123+
select {
2124+
case err := <-ch:
2125+
if err != nil {
2126+
t.Fatalf("Unexpected error from serveConn: %s", err)
2127+
}
2128+
case <-time.After(100 * time.Millisecond):
2129+
t.Fatal("timeout")
2130+
}
2131+
2132+
select {
2133+
case err := <-hijackDone:
2134+
if err != nil {
2135+
t.Fatalf("Unexpected error from hijack: %s", err)
2136+
}
2137+
case <-time.After(100 * time.Millisecond):
2138+
t.Fatal("timeout")
2139+
}
2140+
2141+
if got := rw.w.String(); got != "test" {
2142+
t.Errorf(`expected "test", got %q`, got)
2143+
}
2144+
}
2145+
21012146
func TestRequestCtxInit(t *testing.T) {
21022147
var ctx RequestCtx
21032148
var logger testLogger

0 commit comments

Comments
 (0)