mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-14 15:56:44 +03:00
103adc311e
Compressing small bodies has little sense, since the compressed result size may exceed the original body size. This should save CPU time when the server responds with small responses.
2508 lines
62 KiB
Go
2508 lines
62 KiB
Go
package fasthttp
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/valyala/fasthttp/fasthttputil"
|
|
)
|
|
|
|
func TestServerErrSmallBuffer(t *testing.T) {
|
|
logger := &customLogger{}
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
ctx.WriteString("shouldn't be never called")
|
|
},
|
|
ReadBufferSize: 20,
|
|
Logger: logger,
|
|
}
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
serverCh := make(chan error, 1)
|
|
go func() {
|
|
err := s.Serve(ln)
|
|
serverCh <- err
|
|
}()
|
|
|
|
clientCh := make(chan error, 1)
|
|
go func() {
|
|
c, err := ln.Dial()
|
|
if err != nil {
|
|
clientCh <- fmt.Errorf("unexpected error: %s", err)
|
|
return
|
|
}
|
|
_, err = c.Write([]byte("GET / HTTP/1.1\r\nHost: aabb.com\r\nVERY-long-Header: sdfdfsd dsf dsaf dsf df fsd\r\n\r\n"))
|
|
if err != nil {
|
|
clientCh <- fmt.Errorf("unexpected error when sending request: %s", err)
|
|
return
|
|
}
|
|
br := bufio.NewReader(c)
|
|
var resp Response
|
|
if err = resp.Read(br); err != nil {
|
|
clientCh <- fmt.Errorf("unexpected error: %s", err)
|
|
return
|
|
}
|
|
statusCode := resp.StatusCode()
|
|
if statusCode != StatusRequestHeaderFieldsTooLarge {
|
|
clientCh <- fmt.Errorf("unexpected status code: %d. Expecting %d", statusCode, StatusRequestHeaderFieldsTooLarge)
|
|
return
|
|
}
|
|
if !resp.ConnectionClose() {
|
|
clientCh <- fmt.Errorf("missing 'Connection: close' response header")
|
|
return
|
|
}
|
|
clientCh <- nil
|
|
}()
|
|
|
|
var err error
|
|
|
|
// wait for the client
|
|
select {
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when waiting for the client. Server log: %q", logger.out)
|
|
case err = <-clientCh:
|
|
if err != nil {
|
|
t.Fatalf("unexpected client error: %s. Server log: %q", err, logger.out)
|
|
}
|
|
}
|
|
|
|
// wait for the server
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s. Server log: %q", err, logger.out)
|
|
}
|
|
select {
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when waiting for the server. Server log: %q", logger.out)
|
|
case err = <-serverCh:
|
|
if err != nil {
|
|
t.Fatalf("unexpected server error: %s. Server log: %q", err, logger.out)
|
|
}
|
|
}
|
|
|
|
expectedErr := errSmallBuffer.Error()
|
|
if !strings.Contains(logger.out, expectedErr) {
|
|
t.Fatalf("unexpected log output: %q. Expecting %q", logger.out, expectedErr)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxIsTLS(t *testing.T) {
|
|
var ctx RequestCtx
|
|
|
|
// tls.Conn
|
|
ctx.c = &tls.Conn{}
|
|
if !ctx.IsTLS() {
|
|
t.Fatalf("IsTLS must return true")
|
|
}
|
|
|
|
// non-tls.Conn
|
|
ctx.c = &readWriter{}
|
|
if ctx.IsTLS() {
|
|
t.Fatalf("IsTLS must return false")
|
|
}
|
|
|
|
// overridden tls.Conn
|
|
ctx.c = &struct {
|
|
*tls.Conn
|
|
fooBar bool
|
|
}{}
|
|
if !ctx.IsTLS() {
|
|
t.Fatalf("IsTLS must return true")
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxRedirectHTTPSSchemeless(t *testing.T) {
|
|
var ctx RequestCtx
|
|
|
|
s := "GET /foo/bar?baz HTTP/1.1\nHost: aaa.com\n\n"
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := ctx.Request.Read(br); err != nil {
|
|
t.Fatalf("cannot read request: %s", err)
|
|
}
|
|
ctx.Request.isTLS = true
|
|
|
|
ctx.Redirect("//foobar.com/aa/bbb", StatusFound)
|
|
location := ctx.Response.Header.Peek("Location")
|
|
expectedLocation := "https://foobar.com/aa/bbb"
|
|
if string(location) != expectedLocation {
|
|
t.Fatalf("Unexpected location: %q. Expecting %q", location, expectedLocation)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxRedirect(t *testing.T) {
|
|
testRequestCtxRedirect(t, "http://qqq/", "", "http://qqq/")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "", "http://qqq/foo/bar?baz=111")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "#aaa", "http://qqq/foo/bar?baz=111#aaa")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "?abc=de&f", "http://qqq/foo/bar?abc=de&f")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "?abc=de&f#sf", "http://qqq/foo/bar?abc=de&f#sf")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html", "http://qqq/foo/x.html")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html?a=1", "http://qqq/foo/x.html?a=1")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html#aaa=bbb&cc=ddd", "http://qqq/foo/x.html#aaa=bbb&cc=ddd")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html?b=1#aaa=bbb&cc=ddd", "http://qqq/foo/x.html?b=1#aaa=bbb&cc=ddd")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "/x.html", "http://qqq/x.html")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "/x.html#aaa=bbb&cc=ddd", "http://qqq/x.html#aaa=bbb&cc=ddd")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "../x.html", "http://qqq/x.html")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "../../x.html", "http://qqq/x.html")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "./.././../x.html", "http://qqq/x.html")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "http://foo.bar/baz", "http://foo.bar/baz")
|
|
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "https://foo.bar/baz", "https://foo.bar/baz")
|
|
testRequestCtxRedirect(t, "https://foo.com/bar?aaa", "//google.com/aaa?bb", "https://google.com/aaa?bb")
|
|
}
|
|
|
|
func testRequestCtxRedirect(t *testing.T, origURL, redirectURL, expectedURL string) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.SetRequestURI(origURL)
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ctx.Redirect(redirectURL, StatusFound)
|
|
loc := ctx.Response.Header.Peek("Location")
|
|
if string(loc) != expectedURL {
|
|
t.Fatalf("unexpected redirect url %q. Expecting %q. origURL=%q, redirectURL=%q", loc, expectedURL, origURL, redirectURL)
|
|
}
|
|
}
|
|
|
|
func TestServerResponseServerHeader(t *testing.T) {
|
|
serverName := "foobar serv"
|
|
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
name := ctx.Response.Header.Server()
|
|
if string(name) != serverName {
|
|
fmt.Fprintf(ctx, "unexpected server name: %q. Expecting %q", name, serverName)
|
|
} else {
|
|
ctx.WriteString("OK")
|
|
}
|
|
|
|
// make sure the server name is sent to the client after ctx.Response.Reset()
|
|
ctx.NotFound()
|
|
},
|
|
Name: serverName,
|
|
}
|
|
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
serverCh := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(serverCh)
|
|
}()
|
|
|
|
clientCh := make(chan struct{})
|
|
go func() {
|
|
c, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if _, err = c.Write([]byte("GET / HTTP/1.1\r\nHost: aa\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
br := bufio.NewReader(c)
|
|
var resp Response
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if resp.StatusCode() != StatusNotFound {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusNotFound)
|
|
}
|
|
if string(resp.Body()) != "404 Page not found" {
|
|
t.Fatalf("unexpected body: %q. Expecting %q", resp.Body(), "404 Page not found")
|
|
}
|
|
if string(resp.Header.Server()) != serverName {
|
|
t.Fatalf("unexpected server header: %q. Expecting %q", resp.Header.Server(), serverName)
|
|
}
|
|
if err = c.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(clientCh)
|
|
}()
|
|
|
|
select {
|
|
case <-clientCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-serverCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
func TestServerResponseBodyStream(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
readyCh := make(chan struct{})
|
|
h := func(ctx *RequestCtx) {
|
|
ctx.SetConnectionClose()
|
|
if ctx.IsBodyStream() {
|
|
t.Fatalf("IsBodyStream must return false")
|
|
}
|
|
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
|
fmt.Fprintf(w, "first")
|
|
if err := w.Flush(); err != nil {
|
|
return
|
|
}
|
|
<-readyCh
|
|
fmt.Fprintf(w, "second")
|
|
// there is no need to flush w here, since it will
|
|
// be flushed automatically after returning from StreamWriter.
|
|
})
|
|
if !ctx.IsBodyStream() {
|
|
t.Fatalf("IsBodyStream must return true")
|
|
}
|
|
}
|
|
|
|
serverCh := make(chan struct{})
|
|
go func() {
|
|
if err := Serve(ln, h); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(serverCh)
|
|
}()
|
|
|
|
clientCh := make(chan struct{})
|
|
go func() {
|
|
c, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if _, err = c.Write([]byte("GET / HTTP/1.1\r\nHost: aa\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
br := bufio.NewReader(c)
|
|
var respH ResponseHeader
|
|
if err = respH.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if respH.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", respH.StatusCode(), StatusOK)
|
|
}
|
|
|
|
buf := make([]byte, 1024)
|
|
n, err := br.Read(buf)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
b := buf[:n]
|
|
if string(b) != "5\r\nfirst\r\n" {
|
|
t.Fatalf("unexpected result %q. Expecting %q", b, "5\r\nfirst\r\n")
|
|
}
|
|
close(readyCh)
|
|
|
|
tail, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if string(tail) != "6\r\nsecond\r\n0\r\n\r\n" {
|
|
t.Fatalf("unexpected tail %q. Expecting %q", tail, "6\r\nsecond\r\n0\r\n\r\n")
|
|
}
|
|
|
|
close(clientCh)
|
|
}()
|
|
|
|
select {
|
|
case <-clientCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-serverCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
func TestServerDisableKeepalive(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
ctx.WriteString("OK")
|
|
},
|
|
DisableKeepalive: true,
|
|
}
|
|
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
serverCh := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(serverCh)
|
|
}()
|
|
|
|
clientCh := make(chan struct{})
|
|
go func() {
|
|
c, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if _, err = c.Write([]byte("GET / HTTP/1.1\r\nHost: aa\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
br := bufio.NewReader(c)
|
|
var resp Response
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
if !resp.ConnectionClose() {
|
|
t.Fatalf("expecting 'Connection: close' response header")
|
|
}
|
|
if string(resp.Body()) != "OK" {
|
|
t.Fatalf("unexpected body: %q. Expecting %q", resp.Body(), "OK")
|
|
}
|
|
|
|
// make sure the connection is closed
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if len(data) > 0 {
|
|
t.Fatalf("unexpected data read from the connection: %q. Expecting empty data", data)
|
|
}
|
|
|
|
close(clientCh)
|
|
}()
|
|
|
|
select {
|
|
case <-clientCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-serverCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
func TestServerMaxConnsPerIPLimit(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
ctx.WriteString("OK")
|
|
},
|
|
MaxConnsPerIP: 1,
|
|
Logger: &customLogger{},
|
|
}
|
|
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
serverCh := make(chan struct{})
|
|
go func() {
|
|
fakeLN := &fakeIPListener{
|
|
Listener: ln,
|
|
}
|
|
if err := s.Serve(fakeLN); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(serverCh)
|
|
}()
|
|
|
|
clientCh := make(chan struct{})
|
|
go func() {
|
|
c1, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
c2, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
br := bufio.NewReader(c2)
|
|
var resp Response
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusTooManyRequests {
|
|
t.Fatalf("unexpected status code for the second connection: %d. Expecting %d",
|
|
resp.StatusCode(), StatusTooManyRequests)
|
|
}
|
|
|
|
if _, err = c1.Write([]byte("GET / HTTP/1.1\r\nHost: aa\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error when writing to the first connection: %s", err)
|
|
}
|
|
br = bufio.NewReader(c1)
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code for the first connection: %d. Expecting %d",
|
|
resp.StatusCode(), StatusOK)
|
|
}
|
|
if string(resp.Body()) != "OK" {
|
|
t.Fatalf("unexpected body for the first connection: %q. Expecting %q", resp.Body(), "OK")
|
|
}
|
|
close(clientCh)
|
|
}()
|
|
|
|
select {
|
|
case <-clientCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-serverCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
type fakeIPListener struct {
|
|
net.Listener
|
|
}
|
|
|
|
func (ln *fakeIPListener) Accept() (net.Conn, error) {
|
|
conn, err := ln.Listener.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &fakeIPConn{
|
|
Conn: conn,
|
|
}, nil
|
|
}
|
|
|
|
type fakeIPConn struct {
|
|
net.Conn
|
|
}
|
|
|
|
func (conn *fakeIPConn) RemoteAddr() net.Addr {
|
|
addr, err := net.ResolveTCPAddr("tcp4", "1.2.3.4:5789")
|
|
if err != nil {
|
|
panic(fmt.Sprintf("BUG: unexpected error: %s", err))
|
|
}
|
|
return addr
|
|
}
|
|
|
|
func TestServerConcurrencyLimit(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
ctx.WriteString("OK")
|
|
},
|
|
Concurrency: 1,
|
|
Logger: &customLogger{},
|
|
}
|
|
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
serverCh := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(serverCh)
|
|
}()
|
|
|
|
clientCh := make(chan struct{})
|
|
go func() {
|
|
c1, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
c2, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
br := bufio.NewReader(c2)
|
|
var resp Response
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusServiceUnavailable {
|
|
t.Fatalf("unexpected status code for the second connection: %d. Expecting %d",
|
|
resp.StatusCode(), StatusServiceUnavailable)
|
|
}
|
|
|
|
if _, err = c1.Write([]byte("GET / HTTP/1.1\r\nHost: aa\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error when writing to the first connection: %s", err)
|
|
}
|
|
br = bufio.NewReader(c1)
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code for the first connection: %d. Expecting %d",
|
|
resp.StatusCode(), StatusOK)
|
|
}
|
|
if string(resp.Body()) != "OK" {
|
|
t.Fatalf("unexpected body for the first connection: %q. Expecting %q", resp.Body(), "OK")
|
|
}
|
|
close(clientCh)
|
|
}()
|
|
|
|
select {
|
|
case <-clientCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-serverCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
func TestServerWriteFastError(t *testing.T) {
|
|
s := &Server{
|
|
Name: "foobar",
|
|
}
|
|
var buf bytes.Buffer
|
|
expectedBody := "access denied"
|
|
s.writeFastError(&buf, StatusForbidden, expectedBody)
|
|
|
|
br := bufio.NewReader(&buf)
|
|
var resp Response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusForbidden {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusForbidden)
|
|
}
|
|
body := resp.Body()
|
|
if string(body) != expectedBody {
|
|
t.Fatalf("unexpected body: %q. Expecting %q", body, expectedBody)
|
|
}
|
|
server := string(resp.Header.Server())
|
|
if server != s.Name {
|
|
t.Fatalf("unexpected server: %q. Expecting %q", server, s.Name)
|
|
}
|
|
contentType := string(resp.Header.ContentType())
|
|
if contentType != "text/plain" {
|
|
t.Fatalf("unexpected content-type: %q. Expecting %q", contentType, "text/plain")
|
|
}
|
|
if !resp.Header.ConnectionClose() {
|
|
t.Fatalf("expecting 'Connection: close' response header")
|
|
}
|
|
}
|
|
|
|
func TestServerServeTLSEmbed(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
certFile := "./ssl-cert-snakeoil.pem"
|
|
keyFile := "./ssl-cert-snakeoil.key"
|
|
|
|
certData, err := ioutil.ReadFile(certFile)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when reading %q: %s", certFile, err)
|
|
}
|
|
keyData, err := ioutil.ReadFile(keyFile)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when reading %q: %s", keyFile, err)
|
|
}
|
|
|
|
// start the server
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
err := ServeTLSEmbed(ln, certData, keyData, func(ctx *RequestCtx) {
|
|
if !ctx.IsTLS() {
|
|
ctx.Error("expecting tls", StatusBadRequest)
|
|
return
|
|
}
|
|
scheme := ctx.URI().Scheme()
|
|
if string(scheme) != "https" {
|
|
ctx.Error(fmt.Sprintf("unexpected scheme=%q. Expecting %q", scheme, "https"), StatusBadRequest)
|
|
return
|
|
}
|
|
ctx.WriteString("success")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(ch)
|
|
}()
|
|
|
|
// establish connection to the server
|
|
conn, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
tlsConn := tls.Client(conn, &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
})
|
|
|
|
// send request
|
|
if _, err = tlsConn.Write([]byte("GET / HTTP/1.1\r\nHost: aaa\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// read response
|
|
respCh := make(chan struct{})
|
|
go func() {
|
|
br := bufio.NewReader(tlsConn)
|
|
var resp Response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error")
|
|
}
|
|
body := resp.Body()
|
|
if string(body) != "success" {
|
|
t.Fatalf("unexpected response body %q. Expecting %q", body, "success")
|
|
}
|
|
close(respCh)
|
|
}()
|
|
select {
|
|
case <-respCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
// close the server
|
|
if err = ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
func TestServerMultipartFormDataRequest(t *testing.T) {
|
|
reqS := `POST /upload HTTP/1.1
|
|
Host: qwerty.com
|
|
Content-Length: 521
|
|
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg
|
|
|
|
------WebKitFormBoundaryJwfATyF8tmxSJnLg
|
|
Content-Disposition: form-data; name="f1"
|
|
|
|
value1
|
|
------WebKitFormBoundaryJwfATyF8tmxSJnLg
|
|
Content-Disposition: form-data; name="fileaaa"; filename="TODO"
|
|
Content-Type: application/octet-stream
|
|
|
|
- SessionClient with referer and cookies support.
|
|
- Client with requests' pipelining support.
|
|
- ProxyHandler similar to FSHandler.
|
|
- WebSockets. See https://tools.ietf.org/html/rfc6455 .
|
|
- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .
|
|
|
|
------WebKitFormBoundaryJwfATyF8tmxSJnLg--
|
|
|
|
GET / HTTP/1.1
|
|
Host: asbd
|
|
Connection: close
|
|
|
|
`
|
|
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
switch string(ctx.Path()) {
|
|
case "/upload":
|
|
f, err := ctx.MultipartForm()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if len(f.Value) != 1 {
|
|
t.Fatalf("unexpected values %d. Expecting %d", len(f.Value), 1)
|
|
}
|
|
if len(f.File) != 1 {
|
|
t.Fatalf("unexpected file values %d. Expecting %d", len(f.File), 1)
|
|
}
|
|
fv := ctx.FormValue("f1")
|
|
if string(fv) != "value1" {
|
|
t.Fatalf("unexpected form value: %q. Expecting %q", fv, "value1")
|
|
}
|
|
ctx.Redirect("/", StatusSeeOther)
|
|
default:
|
|
ctx.WriteString("non-upload")
|
|
}
|
|
},
|
|
}
|
|
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(ch)
|
|
}()
|
|
|
|
conn, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if _, err = conn.Write([]byte(reqS)); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
var resp Response
|
|
br := bufio.NewReader(conn)
|
|
respCh := make(chan struct{})
|
|
go func() {
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading response: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusSeeOther {
|
|
t.Fatalf("unexpected status code %d. Expecting %d", resp.StatusCode(), StatusSeeOther)
|
|
}
|
|
loc := resp.Header.Peek("Location")
|
|
if string(loc) != "http://qwerty.com/" {
|
|
t.Fatalf("unexpected location %q. Expecting %q", loc, "http://qwerty.com/")
|
|
}
|
|
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading the second response: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
body := resp.Body()
|
|
if string(body) != "non-upload" {
|
|
t.Fatalf("unexpected body %q. Expecting %q", body, "non-upload")
|
|
}
|
|
close(respCh)
|
|
}()
|
|
|
|
select {
|
|
case <-respCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("error when closing listener: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when waiting for the server to stop")
|
|
}
|
|
}
|
|
|
|
func TestServerDisableHeaderNamesNormalizing(t *testing.T) {
|
|
headerName := "CASE-senSITive-HEAder-NAME"
|
|
headerNameLower := strings.ToLower(headerName)
|
|
headerValue := "foobar baz"
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
hv := ctx.Request.Header.Peek(headerName)
|
|
if string(hv) != headerValue {
|
|
t.Fatalf("unexpected header value for %q: %q. Expecting %q", headerName, hv, headerValue)
|
|
}
|
|
hv = ctx.Request.Header.Peek(headerNameLower)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("unexpected header value for %q: %q. Expecting empty value", headerNameLower, hv)
|
|
}
|
|
ctx.Response.Header.Set(headerName, headerValue)
|
|
ctx.WriteString("ok")
|
|
ctx.SetContentType("aaa")
|
|
},
|
|
DisableHeaderNamesNormalizing: true,
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString(fmt.Sprintf("GET / HTTP/1.1\r\n%s: %s\r\nHost: google.com\r\n\r\n", headerName, headerValue))
|
|
|
|
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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
var resp Response
|
|
resp.Header.DisableNormalizing()
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
hv := resp.Header.Peek(headerName)
|
|
if string(hv) != headerValue {
|
|
t.Fatalf("unexpected header value for %q: %q. Expecting %q", headerName, hv, headerValue)
|
|
}
|
|
hv = resp.Header.Peek(headerNameLower)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("unexpected header value for %q: %q. Expecting empty value", headerNameLower, hv)
|
|
}
|
|
}
|
|
|
|
func TestServerReduceMemoryUsageSerial(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {},
|
|
ReduceMemoryUsage: true,
|
|
}
|
|
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(ch)
|
|
}()
|
|
|
|
testServerRequests(t, ln)
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("error when closing listener: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when waiting for the server to stop")
|
|
}
|
|
}
|
|
|
|
func TestServerReduceMemoryUsageConcurrent(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {},
|
|
ReduceMemoryUsage: true,
|
|
}
|
|
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(ch)
|
|
}()
|
|
|
|
gCh := make(chan struct{})
|
|
for i := 0; i < 10; i++ {
|
|
go func() {
|
|
testServerRequests(t, ln)
|
|
gCh <- struct{}{}
|
|
}()
|
|
}
|
|
for i := 0; i < 10; i++ {
|
|
select {
|
|
case <-gCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout on goroutine %d", i)
|
|
}
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("error when closing listener: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when waiting for the server to stop")
|
|
}
|
|
}
|
|
|
|
func testServerRequests(t *testing.T, ln *fasthttputil.InmemoryListener) {
|
|
conn, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
br := bufio.NewReader(conn)
|
|
var resp Response
|
|
for i := 0; i < 10; i++ {
|
|
if _, err = fmt.Fprintf(conn, "GET / HTTP/1.1\r\nHost: aaa\r\n\r\n"); err != nil {
|
|
t.Fatalf("unexpected error on iteration %d: %s", i, err)
|
|
}
|
|
|
|
respCh := make(chan struct{})
|
|
go func() {
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error when reading response on iteration %d: %s", i, err)
|
|
}
|
|
close(respCh)
|
|
}()
|
|
select {
|
|
case <-respCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout on iteration %d", i)
|
|
}
|
|
}
|
|
|
|
if err = conn.Close(); err != nil {
|
|
t.Fatalf("error when closing the connection: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestServerHTTP10ConnectionKeepAlive(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
err := Serve(ln, func(ctx *RequestCtx) {
|
|
if string(ctx.Path()) == "/close" {
|
|
ctx.SetConnectionClose()
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(ch)
|
|
}()
|
|
|
|
conn, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
_, err = fmt.Fprintf(conn, "%s", "GET / HTTP/1.0\r\nHost: aaa\r\nConnection: keep-alive\r\n\r\n")
|
|
if err != nil {
|
|
t.Fatalf("error when writing request: %s", err)
|
|
}
|
|
_, err = fmt.Fprintf(conn, "%s", "GET /close HTTP/1.0\r\nHost: aaa\r\nConnection: keep-alive\r\n\r\n")
|
|
if err != nil {
|
|
t.Fatalf("error when writing request: %s", err)
|
|
}
|
|
|
|
br := bufio.NewReader(conn)
|
|
var resp Response
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading response: %s", err)
|
|
}
|
|
if resp.ConnectionClose() {
|
|
t.Fatalf("response mustn't have 'Connection: close' header")
|
|
}
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading response: %s", err)
|
|
}
|
|
if !resp.ConnectionClose() {
|
|
t.Fatalf("response must have 'Connection: close' header")
|
|
}
|
|
|
|
tailCh := make(chan struct{})
|
|
go func() {
|
|
tail, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("error when reading tail: %s", err)
|
|
}
|
|
if len(tail) > 0 {
|
|
t.Fatalf("unexpected non-zero tail %q", tail)
|
|
}
|
|
close(tailCh)
|
|
}()
|
|
|
|
select {
|
|
case <-tailCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when reading tail")
|
|
}
|
|
|
|
if err = conn.Close(); err != nil {
|
|
t.Fatalf("error when closing the connection: %s", err)
|
|
}
|
|
|
|
if err = ln.Close(); err != nil {
|
|
t.Fatalf("error when closing listener: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when waiting for the server to stop")
|
|
}
|
|
}
|
|
|
|
func TestServerHTTP10ConnectionClose(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
err := Serve(ln, func(ctx *RequestCtx) {
|
|
// The server must close the connection irregardless
|
|
// of request and response state set inside request
|
|
// handler, since the HTTP/1.0 request
|
|
// had no 'Connection: keep-alive' header.
|
|
ctx.Request.Header.ResetConnectionClose()
|
|
ctx.Request.Header.Set("Connection", "keep-alive")
|
|
ctx.Response.Header.ResetConnectionClose()
|
|
ctx.Response.Header.Set("Connection", "keep-alive")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
close(ch)
|
|
}()
|
|
|
|
conn, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
_, err = fmt.Fprintf(conn, "%s", "GET / HTTP/1.0\r\nHost: aaa\r\n\r\n")
|
|
if err != nil {
|
|
t.Fatalf("error when writing request: %s", err)
|
|
}
|
|
|
|
br := bufio.NewReader(conn)
|
|
var resp Response
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading response: %s", err)
|
|
}
|
|
|
|
if !resp.ConnectionClose() {
|
|
t.Fatalf("HTTP1.0 response must have 'Connection: close' header")
|
|
}
|
|
|
|
tailCh := make(chan struct{})
|
|
go func() {
|
|
tail, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("error when reading tail: %s", err)
|
|
}
|
|
if len(tail) > 0 {
|
|
t.Fatalf("unexpected non-zero tail %q", tail)
|
|
}
|
|
close(tailCh)
|
|
}()
|
|
|
|
select {
|
|
case <-tailCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when reading tail")
|
|
}
|
|
|
|
if err = conn.Close(); err != nil {
|
|
t.Fatalf("error when closing the connection: %s", err)
|
|
}
|
|
|
|
if err = ln.Close(); err != nil {
|
|
t.Fatalf("error when closing listener: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout when waiting for the server to stop")
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxFormValue(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.SetRequestURI("/foo/bar?baz=123&aaa=bbb")
|
|
req.SetBodyString("qqq=port&mmm=sddd")
|
|
req.Header.SetContentType("application/x-www-form-urlencoded")
|
|
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
v := ctx.FormValue("baz")
|
|
if string(v) != "123" {
|
|
t.Fatalf("unexpected value %q. Expecting %q", v, "123")
|
|
}
|
|
v = ctx.FormValue("mmm")
|
|
if string(v) != "sddd" {
|
|
t.Fatalf("unexpected value %q. Expecting %q", v, "sddd")
|
|
}
|
|
v = ctx.FormValue("aaaasdfsdf")
|
|
if len(v) > 0 {
|
|
t.Fatalf("unexpected value for unknown key %q", v)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxUserValue(t *testing.T) {
|
|
var ctx RequestCtx
|
|
|
|
for i := 0; i < 5; i++ {
|
|
k := fmt.Sprintf("key-%d", i)
|
|
ctx.SetUserValue(k, i)
|
|
}
|
|
for i := 5; i < 10; i++ {
|
|
k := fmt.Sprintf("key-%d", i)
|
|
ctx.SetUserValueBytes([]byte(k), i)
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
k := fmt.Sprintf("key-%d", i)
|
|
v := ctx.UserValue(k)
|
|
n, ok := v.(int)
|
|
if !ok || n != i {
|
|
t.Fatalf("unexpected value obtained for key %q: %v. Expecting %d", k, v, i)
|
|
}
|
|
}
|
|
vlen := 0
|
|
ctx.VisitUserValues(func(key []byte, value interface{}) {
|
|
vlen++
|
|
v := ctx.UserValueBytes(key)
|
|
if v != value {
|
|
t.Fatalf("unexpected value obtained from VisitUserValues for key: %q, expecting: %#v but got: %#v", key, v, value)
|
|
}
|
|
})
|
|
if len(ctx.userValues) != vlen {
|
|
t.Fatalf("the length of user values returned from VisitUserValues is not equal to the length of the userValues, expecting: %d but got: %d", len(ctx.userValues), vlen)
|
|
}
|
|
}
|
|
|
|
func TestServerHeadRequest(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
fmt.Fprintf(ctx, "Request method is %q", ctx.Method())
|
|
ctx.SetContentType("aaa/bbb")
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("HEAD /foobar HTTP/1.1\r\nHost: aaa.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
var resp Response
|
|
resp.SkipBody = true
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when parsing response: %s", err)
|
|
}
|
|
if resp.Header.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.Header.StatusCode(), StatusOK)
|
|
}
|
|
if len(resp.Body()) > 0 {
|
|
t.Fatalf("Unexpected non-zero body %q", resp.Body())
|
|
}
|
|
if resp.Header.ContentLength() != 24 {
|
|
t.Fatalf("unexpected content-length %d. Expecting %d", resp.Header.ContentLength(), 24)
|
|
}
|
|
if string(resp.Header.ContentType()) != "aaa/bbb" {
|
|
t.Fatalf("unexpected content-type %q. Expecting %q", resp.Header.ContentType(), "aaa/bbb")
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) > 0 {
|
|
t.Fatalf("unexpected remaining data %q", data)
|
|
}
|
|
}
|
|
|
|
func TestServerExpect100Continue(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
if !ctx.IsPost() {
|
|
t.Fatalf("unexpected method %q. Expecting POST", ctx.Method())
|
|
}
|
|
if string(ctx.Path()) != "/foo" {
|
|
t.Fatalf("unexpected path %q. Expecting %q", ctx.Path(), "/foo")
|
|
}
|
|
ct := ctx.Request.Header.ContentType()
|
|
if string(ct) != "a/b" {
|
|
t.Fatalf("unexpectected content-type: %q. Expecting %q", ct, "a/b")
|
|
}
|
|
if string(ctx.PostBody()) != "12345" {
|
|
t.Fatalf("unexpected body: %q. Expecting %q", ctx.PostBody(), "12345")
|
|
}
|
|
ctx.WriteString("foobar")
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("POST /foo HTTP/1.1\r\nHost: gle.com\r\nExpect: 100-continue\r\nContent-Length: 5\r\nContent-Type: a/b\r\n\r\n12345")
|
|
|
|
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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, StatusOK, string(defaultContentType), "foobar")
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) > 0 {
|
|
t.Fatalf("unexpected remaining data %q", data)
|
|
}
|
|
}
|
|
|
|
func TestCompressHandler(t *testing.T) {
|
|
expectedBody := string(createFixedBody(2e4))
|
|
h := CompressHandler(func(ctx *RequestCtx) {
|
|
ctx.Write([]byte(expectedBody))
|
|
})
|
|
|
|
var ctx RequestCtx
|
|
var resp Response
|
|
|
|
// verify uncompressed response
|
|
h(&ctx)
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
ce := resp.Header.Peek("Content-Encoding")
|
|
if string(ce) != "" {
|
|
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "")
|
|
}
|
|
body := resp.Body()
|
|
if string(body) != expectedBody {
|
|
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
|
|
}
|
|
|
|
// verify gzip-compressed response
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ctx.Request.Header.Set("Accept-Encoding", "gzip, deflate, sdhc")
|
|
|
|
h(&ctx)
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
ce = resp.Header.Peek("Content-Encoding")
|
|
if string(ce) != "gzip" {
|
|
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "gzip")
|
|
}
|
|
body, err := resp.BodyGunzip()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if string(body) != expectedBody {
|
|
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
|
|
}
|
|
|
|
// an attempt to compress already compressed response
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ctx.Request.Header.Set("Accept-Encoding", "gzip, deflate, sdhc")
|
|
hh := CompressHandler(h)
|
|
hh(&ctx)
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
ce = resp.Header.Peek("Content-Encoding")
|
|
if string(ce) != "gzip" {
|
|
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "gzip")
|
|
}
|
|
body, err = resp.BodyGunzip()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if string(body) != expectedBody {
|
|
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
|
|
}
|
|
|
|
// verify deflate-compressed response
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ctx.Request.Header.Set("Accept-Encoding", "foobar, deflate, sdhc")
|
|
|
|
h(&ctx)
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
ce = resp.Header.Peek("Content-Encoding")
|
|
if string(ce) != "deflate" {
|
|
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "deflate")
|
|
}
|
|
body, err = resp.BodyInflate()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if string(body) != expectedBody {
|
|
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxWriteString(t *testing.T) {
|
|
var ctx RequestCtx
|
|
n, err := ctx.WriteString("foo")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if n != 3 {
|
|
t.Fatalf("unexpected n %d. Expecting 3", n)
|
|
}
|
|
n, err = ctx.WriteString("привет")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if n != 12 {
|
|
t.Fatalf("unexpected n=%d. Expecting 12", n)
|
|
}
|
|
|
|
s := ctx.Response.Body()
|
|
if string(s) != "fooпривет" {
|
|
t.Fatalf("unexpected response body %q. Expecting %q", s, "fooпривет")
|
|
}
|
|
}
|
|
|
|
func TestServeConnNonHTTP11KeepAlive(t *testing.T) {
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo HTTP/1.0\r\nConnection: keep-alive\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /bar HTTP/1.0\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /must/be/ignored HTTP/1.0\r\nHost: google.com\r\n\r\n")
|
|
|
|
requestsServed := 0
|
|
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
err := ServeConn(rw, func(ctx *RequestCtx) {
|
|
requestsServed++
|
|
ctx.SuccessString("aaa/bbb", "foobar")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error in ServeConn: %s", err)
|
|
}
|
|
close(ch)
|
|
}()
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
|
|
var resp Response
|
|
|
|
// verify the first response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when parsing response: %s", err)
|
|
}
|
|
if string(resp.Header.Peek("Connection")) != "keep-alive" {
|
|
t.Fatalf("unexpected Connection header %q. Expecting %q", resp.Header.Peek("Connection"), "keep-alive")
|
|
}
|
|
if resp.Header.ConnectionClose() {
|
|
t.Fatalf("unexpected Connection: close")
|
|
}
|
|
|
|
// verify the second response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when parsing response: %s", err)
|
|
}
|
|
if string(resp.Header.Peek("Connection")) != "close" {
|
|
t.Fatalf("unexpected Connection header %q. Expecting %q", resp.Header.Peek("Connection"), "close")
|
|
}
|
|
if !resp.Header.ConnectionClose() {
|
|
t.Fatalf("expecting Connection: close")
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("Unexpected data read after responses %q", data)
|
|
}
|
|
|
|
if requestsServed != 2 {
|
|
t.Fatalf("unexpected number of requests served: %d. Expecting 2", requestsServed)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxSetBodyStreamWriter(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
ctx.Init(&req, nil, defaultLogger)
|
|
|
|
if ctx.IsBodyStream() {
|
|
t.Fatalf("IsBodyStream must return false")
|
|
}
|
|
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
|
fmt.Fprintf(w, "body writer line 1\n")
|
|
if err := w.Flush(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
fmt.Fprintf(w, "body writer line 2\n")
|
|
})
|
|
if !ctx.IsBodyStream() {
|
|
t.Fatalf("IsBodyStream must return true")
|
|
}
|
|
|
|
s := ctx.Response.String()
|
|
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
var resp Response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Error when reading response: %s", err)
|
|
}
|
|
|
|
body := string(resp.Body())
|
|
expectedBody := "body writer line 1\nbody writer line 2\n"
|
|
if body != expectedBody {
|
|
t.Fatalf("unexpected body: %q. Expecting %q", body, expectedBody)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxIfModifiedSince(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
ctx.Init(&req, nil, defaultLogger)
|
|
|
|
lastModified := time.Now().Add(-time.Hour)
|
|
|
|
if !ctx.IfModifiedSince(lastModified) {
|
|
t.Fatalf("IfModifiedSince must return true for non-existing If-Modified-Since header")
|
|
}
|
|
|
|
ctx.Request.Header.Set("If-Modified-Since", string(AppendHTTPDate(nil, lastModified)))
|
|
|
|
if ctx.IfModifiedSince(lastModified) {
|
|
t.Fatalf("If-Modified-Since current time must return false")
|
|
}
|
|
|
|
past := lastModified.Add(-time.Hour)
|
|
if ctx.IfModifiedSince(past) {
|
|
t.Fatalf("If-Modified-Since past time must return false")
|
|
}
|
|
|
|
future := lastModified.Add(time.Hour)
|
|
if !ctx.IfModifiedSince(future) {
|
|
t.Fatalf("If-Modified-Since future time must return true")
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxSendFileNotModified(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
ctx.Init(&req, nil, defaultLogger)
|
|
|
|
filePath := "./server_test.go"
|
|
lastModified, err := FileLastModified(filePath)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
ctx.Request.Header.Set("If-Modified-Since", string(AppendHTTPDate(nil, lastModified)))
|
|
|
|
ctx.SendFile(filePath)
|
|
|
|
s := ctx.Response.String()
|
|
|
|
var resp Response
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading response: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusNotModified {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusNotModified)
|
|
}
|
|
if len(resp.Body()) > 0 {
|
|
t.Fatalf("unexpected non-zero response body: %q", resp.Body())
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxSendFileModified(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
ctx.Init(&req, nil, defaultLogger)
|
|
|
|
filePath := "./server_test.go"
|
|
lastModified, err := FileLastModified(filePath)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
lastModified = lastModified.Add(-time.Hour)
|
|
ctx.Request.Header.Set("If-Modified-Since", string(AppendHTTPDate(nil, lastModified)))
|
|
|
|
ctx.SendFile(filePath)
|
|
|
|
s := ctx.Response.String()
|
|
|
|
var resp Response
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading response: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
t.Fatalf("cannot open file: %s", err)
|
|
}
|
|
body, err := ioutil.ReadAll(f)
|
|
f.Close()
|
|
if err != nil {
|
|
t.Fatalf("error when reading file: %s", err)
|
|
}
|
|
|
|
if !bytes.Equal(resp.Body(), body) {
|
|
t.Fatalf("unexpected response body: %q. Expecting %q", resp.Body(), body)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxSendFile(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
ctx.Init(&req, nil, defaultLogger)
|
|
|
|
filePath := "./server_test.go"
|
|
ctx.SendFile(filePath)
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
if err := ctx.Response.Write(bw); err != nil {
|
|
t.Fatalf("error when writing response: %s", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("error when flushing response: %s", err)
|
|
}
|
|
|
|
var resp Response
|
|
br := bufio.NewReader(w)
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("error when reading response: %s", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
t.Fatalf("cannot open file: %s", err)
|
|
}
|
|
body, err := ioutil.ReadAll(f)
|
|
f.Close()
|
|
if err != nil {
|
|
t.Fatalf("error when reading file: %s", err)
|
|
}
|
|
|
|
if !bytes.Equal(resp.Body(), body) {
|
|
t.Fatalf("unexpected response body: %q. Expecting %q", resp.Body(), body)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxHijack(t *testing.T) {
|
|
hijackStartCh := make(chan struct{})
|
|
hijackStopCh := make(chan struct{})
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
if ctx.Hijacked() {
|
|
t.Fatalf("connection mustn't be hijacked")
|
|
}
|
|
ctx.Hijack(func(c net.Conn) {
|
|
<-hijackStartCh
|
|
|
|
b := make([]byte, 1)
|
|
// ping-pong echo via hijacked conn
|
|
for {
|
|
n, err := c.Read(b)
|
|
if n != 1 {
|
|
if err == io.EOF {
|
|
close(hijackStopCh)
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
t.Fatalf("unexpected number of bytes read: %d. Expecting 1", n)
|
|
}
|
|
if _, err = c.Write(b); err != nil {
|
|
t.Fatalf("unexpected error when writing data: %s", err)
|
|
}
|
|
}
|
|
})
|
|
if !ctx.Hijacked() {
|
|
t.Fatalf("connection must be hijacked")
|
|
}
|
|
ctx.Success("foo/bar", []byte("hijack it!"))
|
|
},
|
|
}
|
|
|
|
hijackedString := "foobar baz hijacked!!!"
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString(hijackedString)
|
|
|
|
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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, StatusOK, "foo/bar", "hijack it!")
|
|
|
|
close(hijackStartCh)
|
|
select {
|
|
case <-hijackStopCh:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if string(data) != hijackedString {
|
|
t.Fatalf("Unexpected data read after the first response %q. Expecting %q", data, hijackedString)
|
|
}
|
|
}
|
|
|
|
func TestRequestCtxInit(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var logger customLogger
|
|
globalConnID = 0x123456
|
|
ctx.Init(&ctx.Request, zeroTCPAddr, &logger)
|
|
ip := ctx.RemoteIP()
|
|
if !ip.IsUnspecified() {
|
|
t.Fatalf("unexpected ip for bare RequestCtx: %q. Expected 0.0.0.0", ip)
|
|
}
|
|
ctx.Logger().Printf("foo bar %d", 10)
|
|
|
|
expectedLog := "#0012345700000000 - 0.0.0.0:0<->0.0.0.0:0 - GET http:/// - foo bar 10\n"
|
|
if logger.out != expectedLog {
|
|
t.Fatalf("Unexpected log output: %q. Expected %q", logger.out, expectedLog)
|
|
}
|
|
}
|
|
|
|
func TestTimeoutHandlerSuccess(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
h := func(ctx *RequestCtx) {
|
|
if string(ctx.Path()) == "/" {
|
|
ctx.Success("aaa/bbb", []byte("real response"))
|
|
}
|
|
}
|
|
s := &Server{
|
|
Handler: TimeoutHandler(h, 10*time.Second, "timeout!!!"),
|
|
}
|
|
serverCh := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexepcted error: %s", err)
|
|
}
|
|
close(serverCh)
|
|
}()
|
|
|
|
concurrency := 20
|
|
clientCh := make(chan struct{}, concurrency)
|
|
for i := 0; i < concurrency; i++ {
|
|
go func() {
|
|
conn, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexepcted error: %s", err)
|
|
}
|
|
if _, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
br := bufio.NewReader(conn)
|
|
verifyResponse(t, br, StatusOK, "aaa/bbb", "real response")
|
|
clientCh <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
select {
|
|
case <-clientCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-serverCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
func TestTimeoutHandlerTimeout(t *testing.T) {
|
|
ln := fasthttputil.NewInmemoryListener()
|
|
readyCh := make(chan struct{})
|
|
doneCh := make(chan struct{})
|
|
h := func(ctx *RequestCtx) {
|
|
ctx.Success("aaa/bbb", []byte("real response"))
|
|
<-readyCh
|
|
doneCh <- struct{}{}
|
|
}
|
|
s := &Server{
|
|
Handler: TimeoutHandler(h, 20*time.Millisecond, "timeout!!!"),
|
|
}
|
|
serverCh := make(chan struct{})
|
|
go func() {
|
|
if err := s.Serve(ln); err != nil {
|
|
t.Fatalf("unexepcted error: %s", err)
|
|
}
|
|
close(serverCh)
|
|
}()
|
|
|
|
concurrency := 20
|
|
clientCh := make(chan struct{}, concurrency)
|
|
for i := 0; i < concurrency; i++ {
|
|
go func() {
|
|
conn, err := ln.Dial()
|
|
if err != nil {
|
|
t.Fatalf("unexepcted error: %s", err)
|
|
}
|
|
if _, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
br := bufio.NewReader(conn)
|
|
verifyResponse(t, br, StatusRequestTimeout, string(defaultContentType), "timeout!!!")
|
|
clientCh <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
select {
|
|
case <-clientCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
close(readyCh)
|
|
for i := 0; i < concurrency; i++ {
|
|
select {
|
|
case <-doneCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
select {
|
|
case <-serverCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
|
|
func TestServerGetOnly(t *testing.T) {
|
|
h := func(ctx *RequestCtx) {
|
|
if !ctx.IsGet() {
|
|
t.Fatalf("non-get request: %q", ctx.Method())
|
|
}
|
|
ctx.Success("foo/bar", []byte("success"))
|
|
}
|
|
s := &Server{
|
|
Handler: h,
|
|
GetOnly: true,
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("POST /foo HTTP/1.1\r\nHost: google.com\r\nContent-Length: 5\r\nContent-Type: aaa\r\n\r\n12345")
|
|
|
|
ch := make(chan error)
|
|
go func() {
|
|
ch <- s.ServeConn(rw)
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err != errGetOnly {
|
|
t.Fatalf("Unexpected error from serveConn: %s. Expecting %s", err, errGetOnly)
|
|
}
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
var resp Response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
statusCode := resp.StatusCode()
|
|
if statusCode != StatusBadRequest {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusBadRequest)
|
|
}
|
|
if !resp.ConnectionClose() {
|
|
t.Fatalf("missing 'Connection: close' response header")
|
|
}
|
|
}
|
|
|
|
func TestServerTimeoutErrorWithResponse(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
go func() {
|
|
ctx.Success("aaa/bbb", []byte("xxxyyy"))
|
|
}()
|
|
|
|
var resp Response
|
|
|
|
resp.SetStatusCode(123)
|
|
resp.SetBodyString("foobar. Should be ignored")
|
|
ctx.TimeoutErrorWithResponse(&resp)
|
|
|
|
resp.SetStatusCode(456)
|
|
resp.ResetBody()
|
|
fmt.Fprintf(resp.BodyWriter(), "path=%s", ctx.Path())
|
|
resp.Header.SetContentType("foo/bar")
|
|
ctx.TimeoutErrorWithResponse(&resp)
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /bar HTTP/1.1\r\nHost: google.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, 456, "foo/bar", "path=/foo")
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("Unexpected data read after the first response %q. Expecting %q", data, "")
|
|
}
|
|
}
|
|
|
|
func TestServerTimeoutErrorWithCode(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
go func() {
|
|
ctx.Success("aaa/bbb", []byte("xxxyyy"))
|
|
}()
|
|
ctx.TimeoutErrorWithCode("should be ignored", 234)
|
|
ctx.TimeoutErrorWithCode("stolen ctx", StatusBadRequest)
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, StatusBadRequest, string(defaultContentType), "stolen ctx")
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("Unexpected data read after the first response %q. Expecting %q", data, "")
|
|
}
|
|
}
|
|
|
|
func TestServerTimeoutError(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
go func() {
|
|
ctx.Success("aaa/bbb", []byte("xxxyyy"))
|
|
}()
|
|
ctx.TimeoutError("should be ignored")
|
|
ctx.TimeoutError("stolen ctx")
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /foo HTTP/1.1\r\nHost: google.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, StatusRequestTimeout, string(defaultContentType), "stolen ctx")
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("Unexpected data read after the first response %q. Expecting %q", data, "")
|
|
}
|
|
}
|
|
|
|
func TestServerMaxKeepaliveDuration(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
time.Sleep(20 * time.Millisecond)
|
|
},
|
|
MaxKeepaliveDuration: 10 * time.Millisecond,
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /aaa HTTP/1.1\r\nHost: aa.com\r\n\r\n")
|
|
rw.r.WriteString("GET /bbbb HTTP/1.1\r\nHost: bbb.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
var resp Response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when parsing response: %s", err)
|
|
}
|
|
if !resp.ConnectionClose() {
|
|
t.Fatalf("Response must have 'connection: close' header")
|
|
}
|
|
verifyResponseHeader(t, &resp.Header, 200, 0, string(defaultContentType))
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("Unexpected data read after the first response %q. Expecting %q", data, "")
|
|
}
|
|
}
|
|
|
|
func TestServerMaxRequestsPerConn(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {},
|
|
MaxRequestsPerConn: 1,
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo1 HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /bar HTTP/1.1\r\nHost: aaa.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
var resp Response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when parsing response: %s", err)
|
|
}
|
|
if !resp.ConnectionClose() {
|
|
t.Fatalf("Response must have 'connection: close' header")
|
|
}
|
|
verifyResponseHeader(t, &resp.Header, 200, 0, string(defaultContentType))
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("Unexpected data read after the first response %q. Expecting %q", data, "")
|
|
}
|
|
}
|
|
|
|
func TestServerConnectionClose(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
ctx.SetConnectionClose()
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo1 HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /must/be/ignored HTTP/1.1\r\nHost: aaa.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
var resp Response
|
|
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when parsing response: %s", err)
|
|
}
|
|
if !resp.ConnectionClose() {
|
|
t.Fatalf("expecting Connection: close header")
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading remaining data: %s", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("Unexpected data read after the first response %q. Expecting %q", data, "")
|
|
}
|
|
}
|
|
|
|
func TestServerRequestNumAndTime(t *testing.T) {
|
|
n := uint64(0)
|
|
var connT time.Time
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
n++
|
|
if ctx.ConnRequestNum() != n {
|
|
t.Fatalf("unexpected request number: %d. Expecting %d", ctx.ConnRequestNum(), n)
|
|
}
|
|
if connT.IsZero() {
|
|
connT = ctx.ConnTime()
|
|
}
|
|
if ctx.ConnTime() != connT {
|
|
t.Fatalf("unexpected serve conn time: %s. Expecting %s", ctx.ConnTime(), connT)
|
|
}
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo1 HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /bar HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("GET /baz HTTP/1.1\r\nHost: google.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
if n != 3 {
|
|
t.Fatalf("unexpected number of requests served: %d. Expecting %d", n, 3)
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, 200, string(defaultContentType), "")
|
|
}
|
|
|
|
func TestServerEmptyResponse(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
// do nothing :)
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo1 HTTP/1.1\r\nHost: google.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, 200, string(defaultContentType), "")
|
|
}
|
|
|
|
type customLogger struct {
|
|
lock sync.Mutex
|
|
out string
|
|
}
|
|
|
|
func (cl *customLogger) Printf(format string, args ...interface{}) {
|
|
cl.lock.Lock()
|
|
cl.out += fmt.Sprintf(format, args...)[6:] + "\n"
|
|
cl.lock.Unlock()
|
|
}
|
|
|
|
func TestServerLogger(t *testing.T) {
|
|
cl := &customLogger{}
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
logger := ctx.Logger()
|
|
h := &ctx.Request.Header
|
|
logger.Printf("begin")
|
|
ctx.Success("text/html", []byte(fmt.Sprintf("requestURI=%s, body=%q, remoteAddr=%s",
|
|
h.RequestURI(), ctx.Request.Body(), ctx.RemoteAddr())))
|
|
logger.Printf("end")
|
|
},
|
|
Logger: cl,
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo1 HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
rw.r.WriteString("POST /foo2 HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 5\r\nContent-Type: aa\r\n\r\nabcde")
|
|
|
|
rwx := &readWriterRemoteAddr{
|
|
rw: rw,
|
|
addr: &net.TCPAddr{
|
|
IP: []byte{1, 2, 3, 4},
|
|
Port: 8765,
|
|
},
|
|
}
|
|
|
|
globalConnID = 0
|
|
ch := make(chan error)
|
|
go func() {
|
|
ch <- s.ServeConn(rwx)
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error from serveConn: %s", err)
|
|
}
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, 200, "text/html", "requestURI=/foo1, body=\"\", remoteAddr=1.2.3.4:8765")
|
|
verifyResponse(t, br, 200, "text/html", "requestURI=/foo2, body=\"abcde\", remoteAddr=1.2.3.4:8765")
|
|
|
|
expectedLogOut := `#0000000100000001 - 1.2.3.4:8765<->1.2.3.4:8765 - GET http://google.com/foo1 - begin
|
|
#0000000100000001 - 1.2.3.4:8765<->1.2.3.4:8765 - GET http://google.com/foo1 - end
|
|
#0000000100000002 - 1.2.3.4:8765<->1.2.3.4:8765 - POST http://aaa.com/foo2 - begin
|
|
#0000000100000002 - 1.2.3.4:8765<->1.2.3.4:8765 - POST http://aaa.com/foo2 - end
|
|
`
|
|
if cl.out != expectedLogOut {
|
|
t.Fatalf("Unexpected logger output: %q. Expected %q", cl.out, expectedLogOut)
|
|
}
|
|
}
|
|
|
|
func TestServerRemoteAddr(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
h := &ctx.Request.Header
|
|
ctx.Success("text/html", []byte(fmt.Sprintf("requestURI=%s, remoteAddr=%s, remoteIP=%s",
|
|
h.RequestURI(), ctx.RemoteAddr(), ctx.RemoteIP())))
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo1 HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
|
|
rwx := &readWriterRemoteAddr{
|
|
rw: rw,
|
|
addr: &net.TCPAddr{
|
|
IP: []byte{1, 2, 3, 4},
|
|
Port: 8765,
|
|
},
|
|
}
|
|
|
|
ch := make(chan error)
|
|
go func() {
|
|
ch <- s.ServeConn(rwx)
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error from serveConn: %s", err)
|
|
}
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, 200, "text/html", "requestURI=/foo1, remoteAddr=1.2.3.4:8765, remoteIP=1.2.3.4")
|
|
}
|
|
|
|
type readWriterRemoteAddr struct {
|
|
net.Conn
|
|
rw io.ReadWriteCloser
|
|
addr net.Addr
|
|
}
|
|
|
|
func (rw *readWriterRemoteAddr) Close() error {
|
|
return rw.rw.Close()
|
|
}
|
|
|
|
func (rw *readWriterRemoteAddr) Read(b []byte) (int, error) {
|
|
return rw.rw.Read(b)
|
|
}
|
|
|
|
func (rw *readWriterRemoteAddr) Write(b []byte) (int, error) {
|
|
return rw.rw.Write(b)
|
|
}
|
|
|
|
func (rw *readWriterRemoteAddr) RemoteAddr() net.Addr {
|
|
return rw.addr
|
|
}
|
|
|
|
func (rw *readWriterRemoteAddr) LocalAddr() net.Addr {
|
|
return rw.addr
|
|
}
|
|
|
|
func TestServerConnError(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
ctx.Error("foobar", 423)
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo/bar?baz HTTP/1.1\r\nHost: google.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
var resp Response
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when reading response: %s", err)
|
|
}
|
|
if resp.Header.StatusCode() != 423 {
|
|
t.Fatalf("Unexpected status code %d. Expected %d", resp.Header.StatusCode(), 423)
|
|
}
|
|
if resp.Header.ContentLength() != 6 {
|
|
t.Fatalf("Unexpected Content-Length %d. Expected %d", resp.Header.ContentLength(), 6)
|
|
}
|
|
if !bytes.Equal(resp.Header.Peek("Content-Type"), defaultContentType) {
|
|
t.Fatalf("Unexpected Content-Type %q. Expected %q", resp.Header.Peek("Content-Type"), defaultContentType)
|
|
}
|
|
if !bytes.Equal(resp.Body(), []byte("foobar")) {
|
|
t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), "foobar")
|
|
}
|
|
}
|
|
|
|
func TestServeConnSingleRequest(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
h := &ctx.Request.Header
|
|
ctx.Success("aaa", []byte(fmt.Sprintf("requestURI=%s, host=%s", h.RequestURI(), h.Peek("Host"))))
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo/bar?baz HTTP/1.1\r\nHost: google.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, 200, "aaa", "requestURI=/foo/bar?baz, host=google.com")
|
|
}
|
|
|
|
func TestServeConnMultiRequests(t *testing.T) {
|
|
s := &Server{
|
|
Handler: func(ctx *RequestCtx) {
|
|
h := &ctx.Request.Header
|
|
ctx.Success("aaa", []byte(fmt.Sprintf("requestURI=%s, host=%s", h.RequestURI(), h.Peek("Host"))))
|
|
},
|
|
}
|
|
|
|
rw := &readWriter{}
|
|
rw.r.WriteString("GET /foo/bar?baz HTTP/1.1\r\nHost: google.com\r\n\r\nGET /abc HTTP/1.1\r\nHost: foobar.com\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.Fatalf("timeout")
|
|
}
|
|
|
|
br := bufio.NewReader(&rw.w)
|
|
verifyResponse(t, br, 200, "aaa", "requestURI=/foo/bar?baz, host=google.com")
|
|
verifyResponse(t, br, 200, "aaa", "requestURI=/abc, host=foobar.com")
|
|
}
|
|
|
|
func verifyResponse(t *testing.T, r *bufio.Reader, expectedStatusCode int, expectedContentType, expectedBody string) {
|
|
var resp Response
|
|
if err := resp.Read(r); err != nil {
|
|
t.Fatalf("Unexpected error when parsing response: %s", err)
|
|
}
|
|
|
|
if !bytes.Equal(resp.Body(), []byte(expectedBody)) {
|
|
t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody))
|
|
}
|
|
verifyResponseHeader(t, &resp.Header, expectedStatusCode, len(resp.Body()), expectedContentType)
|
|
}
|
|
|
|
type readWriter struct {
|
|
net.Conn
|
|
r bytes.Buffer
|
|
w bytes.Buffer
|
|
}
|
|
|
|
func (rw *readWriter) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (rw *readWriter) Read(b []byte) (int, error) {
|
|
return rw.r.Read(b)
|
|
}
|
|
|
|
func (rw *readWriter) Write(b []byte) (int, error) {
|
|
return rw.w.Write(b)
|
|
}
|
|
|
|
func (rw *readWriter) RemoteAddr() net.Addr {
|
|
return zeroTCPAddr
|
|
}
|
|
|
|
func (rw *readWriter) LocalAddr() net.Addr {
|
|
return zeroTCPAddr
|
|
}
|
|
|
|
func (rw *readWriter) SetReadDeadline(t time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (rw *readWriter) SetWriteDeadline(t time.Time) error {
|
|
return nil
|
|
}
|