package buffer import ( "bufio" "crypto/tls" "fmt" "io" "net" "net/http" "net/http/httptest" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vulcand/oxy/forward" "github.com/vulcand/oxy/testutils" "github.com/vulcand/oxy/utils" ) func TestSimple(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { _, _ = w.Write([]byte("hello")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, body, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusOK, re.StatusCode) assert.Equal(t, "hello", string(body)) } func TestChunkedEncodingSuccess(t *testing.T) { var reqBody string var contentLength int64 srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { body, err := io.ReadAll(req.Body) require.NoError(t, err) reqBody = string(body) contentLength = req.ContentLength _, _ = w.Write([]byte("hello")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() conn, err := net.Dial("tcp", testutils.ParseURI(proxy.URL).Host) require.NoError(t, err) _, _ = fmt.Fprintf(conn, "POST / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n5\r\ntest1\r\n5\r\ntest2\r\n0\r\n\r\n") status, err := bufio.NewReader(conn).ReadString('\n') require.NoError(t, err) assert.Equal(t, "testtest1test2", reqBody) assert.Equal(t, "HTTP/1.1 200 OK\r\n", status) assert.EqualValues(t, len(reqBody), contentLength) } func TestChunkedEncodingLimitReached(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { _, _ = w.Write([]byte("hello")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr, MemRequestBodyBytes(4), MaxRequestBodyBytes(8)) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() conn, err := net.Dial("tcp", testutils.ParseURI(proxy.URL).Host) require.NoError(t, err) _, _ = fmt.Fprint(conn, "POST / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n5\r\ntest1\r\n5\r\ntest2\r\n0\r\n\r\n") status, err := bufio.NewReader(conn).ReadString('\n') require.NoError(t, err) assert.Equal(t, "HTTP/1.1 413 Request Entity Too Large\r\n", status) } func TestChunkedResponse(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { h := w.(http.Hijacker) conn, _, _ := h.Hijack() _, _ = fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n5\r\ntest1\r\n5\r\ntest2\r\n0\r\n\r\n") _ = conn.Close() }) defer srv.Close() fwd := forward.New(false) rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) st, err := New(rdr) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, body, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, "testtest1test2", string(body)) assert.Equal(t, http.StatusOK, re.StatusCode) assert.Equal(t, strconv.Itoa(len("testtest1test2")), re.Header.Get("Content-Length")) } func TestRequestLimitReached(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { _, _ = w.Write([]byte("hello")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr, MaxRequestBodyBytes(4)) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, _, err := testutils.Get(proxy.URL, testutils.Body("this request is too long")) require.NoError(t, err) assert.Equal(t, http.StatusRequestEntityTooLarge, re.StatusCode) } func TestResponseLimitReached(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { _, _ = w.Write([]byte("hello, this response is too large")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr, MaxResponseBodyBytes(4)) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, _, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, re.StatusCode) } func TestFileStreamingResponse(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { _, _ = w.Write([]byte("hello, this response is too large to fit in memory")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr, MemResponseBodyBytes(4)) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, body, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusOK, re.StatusCode) assert.Equal(t, "hello, this response is too large to fit in memory", string(body)) } func TestCustomErrorHandler(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { _, _ = w.Write([]byte("hello, this response is too large")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect errHandler := utils.ErrorHandlerFunc(func(w http.ResponseWriter, req *http.Request, err error) { w.WriteHeader(http.StatusTeapot) _, _ = w.Write([]byte(http.StatusText(http.StatusTeapot))) }) st, err := New(rdr, MaxResponseBodyBytes(4), ErrorHandler(errHandler)) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, _, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusTeapot, re.StatusCode) } func TestNotModified(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusNotModified) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, _, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusNotModified, re.StatusCode) } func TestNoBody(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, _, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusOK, re.StatusCode) } // Make sure that stream handler preserves TLS settings. func TestPreservesTLS(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) var cs *tls.ConnectionState // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { cs = req.TLS req.URL = testutils.ParseURI(srv.URL) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr) require.NoError(t, err) proxy := httptest.NewUnstartedServer(st) proxy.StartTLS() defer proxy.Close() re, _, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusOK, re.StatusCode) assert.NotNil(t, cs) } func TestNotNilBody(t *testing.T) { srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) { _, _ = w.Write([]byte("hello")) }) defer srv.Close() // forwarder will proxy the request to whatever destination fwd := forward.New(false) // this is our redirect to server rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testutils.ParseURI(srv.URL) // During a request check if the request body is no nil before sending to the next middleware // Because we can send a POST request without body assert.NotNil(t, req.Body) fwd.ServeHTTP(w, req) }) // stream handler will forward requests to redirect st, err := New(rdr, MaxRequestBodyBytes(10)) require.NoError(t, err) proxy := httptest.NewServer(st) defer proxy.Close() re, body, err := testutils.Get(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusOK, re.StatusCode) assert.Equal(t, "hello", string(body)) re, body, err = testutils.Post(proxy.URL) require.NoError(t, err) assert.Equal(t, http.StatusOK, re.StatusCode) assert.Equal(t, "hello", string(body)) }