@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 02:15 UTC

master
go 151 lines 4.73 KB
Raw
1 package fastcgi
2
3 import (
4 "context"
5 "errors"
6 "io"
7 "net"
8 "net/http"
9 "net/http/fcgi"
10 "net/http/httptest"
11 "strings"
12 "testing"
13
14 "github.com/caddyserver/caddy/v2"
15 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
16 "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
17 )
18
19 // fcgiBackend serves h over FastCGI and returns its address.
20 func fcgiBackend(t *testing.T, h http.Handler) string {
21 t.Helper()
22
23 ln, err := net.Listen("tcp", "127.0.0.1:0")
24 if err != nil {
25 t.Fatalf("listen: %v", err)
26 }
27 t.Cleanup(func() { ln.Close() })
28 go fcgi.Serve(ln, h)
29
30 return ln.Addr().String()
31 }
32
33 // fcgiRoundTrip sends req to a FastCGI backend at addr, the way the reverse
34 // proxy does once it has picked an upstream and decided not to buffer.
35 func fcgiRoundTrip(t *testing.T, addr string, req *http.Request) (*http.Response, error) {
36 t.Helper()
37
38 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
39 t.Cleanup(cancel)
40
41 transport := Transport{Root: "/", SplitPath: []string{".php"}}
42 if err := transport.Provision(ctx); err != nil {
43 t.Fatalf("provisioning transport: %v", err)
44 }
45
46 req.URL.Host = addr
47 req = caddyhttp.PrepareRequest(req, caddy.NewReplacer(), nil, &caddyhttp.Server{})
48
49 res, err := transport.RoundTrip(req)
50 if res != nil {
51 t.Cleanup(func() { res.Body.Close() })
52 }
53
54 return res, err
55 }
56
57 // A request whose length is known carries its own CONTENT_LENGTH, so fastcgi
58 // streams it without the request buffer it otherwise needs: refusing to
59 // forward it incrementally would deny a request that works fine unbuffered.
60 func TestIncrementalUnbufferedRequestWithKnownLength(t *testing.T) {
61 var gotBody string
62 var gotLength int64
63 addr := fcgiBackend(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
64 b, _ := io.ReadAll(r.Body)
65 gotBody, gotLength = string(b), r.ContentLength
66 }))
67
68 req := httptest.NewRequest(http.MethodPost, "http://example.com/index.php", strings.NewReader("hello"))
69 req.Header.Set("Incremental", "?1")
70
71 res, err := fcgiRoundTrip(t, addr, req)
72 if err != nil {
73 t.Fatalf("RoundTrip() error = %v", err)
74 }
75 if res.StatusCode != http.StatusOK {
76 t.Errorf("status = %d, want 200", res.StatusCode)
77 }
78 if gotBody != "hello" {
79 t.Errorf("upstream body = %q, want %q", gotBody, "hello")
80 }
81 if gotLength != 5 {
82 t.Errorf("upstream CONTENT_LENGTH = %d, want 5", gotLength)
83 }
84 }
85
86 // A request whose length is unknown, as a chunked request is, has no
87 // CONTENT_LENGTH to pass on, and the fastcgi client rejects it with 411 rather
88 // than let the upstream hang. Only the request buffer can supply that length,
89 // which is why incremental forwarding is refused instead in this case.
90 func TestIncrementalUnbufferedRequestWithUnknownLength(t *testing.T) {
91 var reached bool
92 addr := fcgiBackend(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
93 reached = true
94 }))
95
96 req := httptest.NewRequest(http.MethodPost, "http://example.com/index.php", io.NopCloser(strings.NewReader("hello")))
97 req.Header.Set("Incremental", "?1")
98 if req.ContentLength != -1 {
99 t.Fatalf("ContentLength = %d, want -1", req.ContentLength)
100 }
101
102 _, err := fcgiRoundTrip(t, addr, req)
103
104 var handlerErr caddyhttp.HandlerError
105 if !errors.As(err, &handlerErr) {
106 t.Fatalf("expected a caddyhttp.HandlerError, got %T: %v", err, err)
107 }
108 if handlerErr.StatusCode != http.StatusLengthRequired {
109 t.Errorf("status = %d, want %d", handlerErr.StatusCode, http.StatusLengthRequired)
110 }
111 if reached {
112 t.Error("request reached the upstream despite having no CONTENT_LENGTH")
113 }
114 }
115
116 // Declaring that a length is required is what makes the reverse proxy refuse
117 // to forward a body of unknown length incrementally rather than send it on to
118 // the 411 above.
119 func TestRequiresContentLength(t *testing.T) {
120 if !(Transport{}).RequiresContentLength() {
121 t.Error("RequiresContentLength() = false, want true")
122 }
123 }
124
125 func TestRoundTripDialError(t *testing.T) {
126 req := httptest.NewRequest(http.MethodPost, "http://example.com/index.php", strings.NewReader("body"))
127 _, err := fcgiRoundTrip(t, "invalid address", req)
128 if err == nil {
129 t.Fatal("RoundTrip() error = nil, want a dial error")
130 }
131
132 var dialErr reverseproxy.DialError
133 if !errors.As(err, &dialErr) {
134 t.Fatalf("RoundTrip() error type = %T, want reverseproxy.DialError", err)
135 }
136 if !strings.HasPrefix(err.Error(), "dialing backend:") {
137 t.Errorf("RoundTrip() error = %q, want dialing context", err)
138 }
139 }
140
141 // The request buffer fastcgi asks for by default is what supplies the
142 // CONTENT_LENGTH above, so these values matter beyond this package.
143 func TestDefaultBufferSizes(t *testing.T) {
144 reqBuffers, respBuffers := Transport{}.DefaultBufferSizes()
145 if reqBuffers != 4096 {
146 t.Errorf("request buffers = %d, want 4096", reqBuffers)
147 }
148 if respBuffers != 0 {
149 t.Errorf("response buffers = %d, want 0", respBuffers)
150 }
151 }