@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 477 lines 13.5 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Forked Jan. 2015 from http://bitbucket.org/PinIdea/fcgi_client
16 // (which is forked from https://code.google.com/p/go-fastcgi-client/).
17 // This fork contains several fixes and improvements by Matt Holt and
18 // other contributors to the Caddy project.
19
20 // Copyright 2012 Junqing Tan <ivan@mysqlab.net> and The Go Authors
21 // Use of this source code is governed by a BSD-style
22 // Part of source code is from Go fcgi package
23
24 package fastcgi
25
26 import (
27 "bufio"
28 "bytes"
29 "errors"
30 "fmt"
31 "io"
32 "mime/multipart"
33 "net"
34 "net/http"
35 "net/http/httputil"
36 "net/textproto"
37 "net/url"
38 "os"
39 "path/filepath"
40 "strconv"
41 "strings"
42 "time"
43
44 "go.uber.org/zap"
45 "go.uber.org/zap/zapcore"
46
47 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
48 )
49
50 // FCGIListenSockFileno describes listen socket file number.
51 const FCGIListenSockFileno uint8 = 0
52
53 // FCGIHeaderLen describes header length.
54 const FCGIHeaderLen uint8 = 8
55
56 // Version1 describes the version.
57 const Version1 uint8 = 1
58
59 // FCGINullRequestID describes the null request ID.
60 const FCGINullRequestID uint8 = 0
61
62 // FCGIKeepConn describes keep connection mode.
63 const FCGIKeepConn uint8 = 1
64
65 const (
66 // BeginRequest is the begin request flag.
67 BeginRequest uint8 = iota + 1
68 // AbortRequest is the abort request flag.
69 AbortRequest
70 // EndRequest is the end request flag.
71 EndRequest
72 // Params is the parameters flag.
73 Params
74 // Stdin is the standard input flag.
75 Stdin
76 // Stdout is the standard output flag.
77 Stdout
78 // Stderr is the standard error flag.
79 Stderr
80 // Data is the data flag.
81 Data
82 // GetValues is the get values flag.
83 GetValues
84 // GetValuesResult is the get values result flag.
85 GetValuesResult
86 // UnknownType is the unknown type flag.
87 UnknownType
88 // MaxType is the maximum type flag.
89 MaxType = UnknownType
90 )
91
92 const (
93 // Responder is the responder flag.
94 Responder uint8 = iota + 1
95 // Authorizer is the authorizer flag.
96 Authorizer
97 // Filter is the filter flag.
98 Filter
99 )
100
101 const (
102 // RequestComplete is the completed request flag.
103 RequestComplete uint8 = iota
104 // CantMultiplexConns is the multiplexed connections flag.
105 CantMultiplexConns
106 // Overloaded is the overloaded flag.
107 Overloaded
108 // UnknownRole is the unknown role flag.
109 UnknownRole
110 )
111
112 const (
113 // MaxConns is the maximum connections flag.
114 MaxConns string = "MAX_CONNS"
115 // MaxRequests is the maximum requests flag.
116 MaxRequests string = "MAX_REQS"
117 // MultiplexConns is the multiplex connections flag.
118 MultiplexConns string = "MPXS_CONNS"
119 )
120
121 const (
122 maxWrite = 65500 // 65530 may work, but for compatibility
123 maxPad = 255
124 )
125
126 // for padding so we don't have to allocate all the time
127 // not synchronized because we don't care what the contents are
128 var pad [maxPad]byte
129
130 // FastCGI has no way to express a body of unknown length: CONTENT_LENGTH is
131 // required by CGI/1.1, and php-fpm hangs when it is absent or wrong and the
132 // body is not empty. A request that arrives without a length therefore has to
133 // be buffered in full before it can be forwarded.
134 var (
135 errNoContentLength = errors.New("request body has no known length; increase request_buffers so the body can be buffered in full to determine it")
136 errBadContentLength = errors.New("request body length is not a valid CONTENT_LENGTH")
137 )
138
139 // maxSurplus and maxSurplusWait bound how far a body reads on once a responder
140 // writes past its declared Content-Length, so that stderr written after it is
141 // still logged without the responder holding the connection.
142 const (
143 maxSurplus = 64 << 10
144 maxSurplusWait = time.Second
145 )
146
147 // client implements a FastCGI client, which is a standard for
148 // interfacing external applications with Web servers.
149 type client struct {
150 rwc net.Conn
151 // keepAlive bool // TODO: implement
152 reqID uint16
153 stderr bool
154 logger *zap.Logger
155
156 readDeadline time.Time // set by SetReadTimeout
157 }
158
159 // Do makes the request and returns an io.Reader that translates the data read
160 // from the FastCGI responder out of FastCGI packets before returning it.
161 func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) {
162 // check for CONTENT_LENGTH, since the lack of it or wrong value will cause the backend to hang.
163 // A request with no length of its own (Transfer-Encoding: chunked, or an HTTP/2 or HTTP/3
164 // request sent without the header) only acquires one by being buffered in full, so say so:
165 // the bare status alone has sent people looking for the fault in their backend.
166 if clStr, ok := p["CONTENT_LENGTH"]; !ok || clStr == "" {
167 return nil, caddyhttp.Error(http.StatusLengthRequired, errNoContentLength)
168 } else if l, err := strconv.ParseInt(clStr, 10, 64); err != nil {
169 return nil, caddyhttp.Error(http.StatusLengthRequired, fmt.Errorf("%w: %w", errBadContentLength, err))
170 } else if l < 0 {
171 // net/http reports -1 for a body whose length it does not know, which is
172 // what a chunked request looks like by the time it reaches here. This is
173 // the case operators actually hit, so it gets the actionable message.
174 return nil, caddyhttp.Error(http.StatusLengthRequired, errNoContentLength)
175 }
176
177 writer := &streamWriter{c: c}
178 writer.buf = bufPool.Get().(*bytes.Buffer)
179 writer.buf.Reset()
180 defer bufPool.Put(writer.buf)
181
182 err = writer.writeBeginRequest(uint16(Responder), 0)
183 if err != nil {
184 return r, err
185 }
186
187 writer.recType = Params
188 err = writer.writePairs(p)
189 if err != nil {
190 return r, err
191 }
192
193 writer.recType = Stdin
194 if req != nil {
195 _, err = io.Copy(writer, req)
196 if err != nil {
197 return nil, err
198 }
199 }
200 err = writer.FlushStream()
201 if err != nil {
202 return nil, err
203 }
204
205 r = &streamReader{c: c}
206 return r, err
207 }
208
209 // clientCloser is an io.ReadCloser. It wraps an io.Reader with a Closer
210 // that closes the client connection.
211 type clientCloser struct {
212 rwc net.Conn
213 r *streamReader
214 io.Reader
215
216 status int
217 logger *zap.Logger
218 }
219
220 func (f clientCloser) Close() error {
221 stderr := f.r.stderr.Bytes()
222 if len(stderr) == 0 {
223 return f.rwc.Close()
224 }
225
226 logLevel := zapcore.WarnLevel
227 if f.status >= 400 {
228 logLevel = zapcore.ErrorLevel
229 }
230
231 if c := f.logger.Check(logLevel, "stderr"); c != nil {
232 c.Write(zap.ByteString("body", stderr))
233 }
234
235 return f.rwc.Close()
236 }
237
238 // declaredBody limits a response body to its declared Content-Length, since
239 // any bytes past it would make the response invalid for the client.
240 type declaredBody struct {
241 c *client
242 r *bufio.Reader
243 length int64
244 remaining int64
245 err error // returned by every Read once set
246 }
247
248 func (b *declaredBody) Read(p []byte) (int, error) {
249 if b.err != nil {
250 return 0, b.err
251 }
252 if b.remaining == 0 {
253 // The body still ends where the response does, as before the limit.
254 b.err = b.discardSurplus()
255 return 0, b.err
256 }
257 if int64(len(p)) > b.remaining {
258 p = p[:b.remaining]
259 }
260 n, err := b.r.Read(p)
261 b.remaining -= int64(n)
262 if err == io.EOF && b.remaining > 0 {
263 err = io.ErrUnexpectedEOF
264 }
265 b.err = err
266 return n, err
267 }
268
269 // discardSurplus reads the rest of the response after the declared body, so
270 // that stderr written after it is still collected, and warns about any bytes
271 // past the declared length. It returns io.EOF, or the error that cut the
272 // response short.
273 func (b *declaredBody) discardSurplus() error {
274 // Wait for the end of the response as reading the body used to; only a
275 // surplus byte starts the clock.
276 if _, err := b.r.Peek(1); err != nil {
277 return err
278 }
279 deadline := time.Now().Add(maxSurplusWait)
280 if !b.c.readDeadline.IsZero() && b.c.readDeadline.Before(deadline) {
281 deadline = b.c.readDeadline
282 }
283 if err := b.c.rwc.SetReadDeadline(deadline); err != nil {
284 return err
285 }
286 n, _ := io.CopyN(io.Discard, b.r, maxSurplus)
287 if c := b.c.logger.Check(zapcore.WarnLevel, "discarding upstream response bytes beyond the declared Content-Length"); c != nil {
288 c.Write(zap.Int64("content_length", b.length), zap.Int64("surplus_bytes", n))
289 }
290 return io.EOF
291 }
292
293 // bodyAllowed reports whether a response can have a body, by the rule
294 // net/http applies when serving one.
295 func bodyAllowed(method string, status int) bool {
296 return method != http.MethodHead && (status < 100 || status > 199) &&
297 status != http.StatusNoContent && status != http.StatusNotModified
298 }
299
300 // Request returns an HTTP response with header and body
301 // from the FastCGI responder.
302 func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
303 r, err := c.Do(p, req)
304 if err != nil {
305 return resp, err
306 }
307
308 rb := bufio.NewReader(r)
309 tp := textproto.NewReader(rb)
310 resp = new(http.Response)
311
312 // Parse the response headers.
313 mimeHeader, err := tp.ReadMIMEHeader()
314 if err != nil && err != io.EOF {
315 return resp, err
316 }
317 resp.Header = http.Header(mimeHeader)
318
319 if resp.Header.Get("Status") != "" {
320 statusNumber, statusInfo, statusIsCut := strings.Cut(resp.Header.Get("Status"), " ")
321 resp.StatusCode, err = strconv.Atoi(statusNumber)
322 if err != nil {
323 return resp, err
324 }
325 if statusIsCut {
326 resp.Status = statusInfo
327 }
328 } else if resp.Header.Get("Location") != "" {
329 resp.StatusCode = http.StatusFound
330 } else {
331 resp.StatusCode = http.StatusOK
332 }
333
334 // TODO: fixTransferEncoding ?
335 resp.TransferEncoding = resp.Header["Transfer-Encoding"]
336 length, lengthErr := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
337 resp.ContentLength = length
338
339 // wrap the response body in our closer
340 closer := clientCloser{
341 rwc: c.rwc,
342 r: r.(*streamReader),
343 Reader: rb,
344 status: resp.StatusCode,
345 logger: noopLogger,
346 }
347 switch {
348 case chunked(resp.TransferEncoding):
349 closer.Reader = httputil.NewChunkedReader(rb)
350 case lengthErr == nil && length >= 0 && bodyAllowed(p["REQUEST_METHOD"], resp.StatusCode):
351 closer.Reader = &declaredBody{c: c, r: rb, length: length, remaining: length}
352 }
353 if c.stderr {
354 closer.logger = c.logger
355 }
356 resp.Body = closer
357
358 return resp, err
359 }
360
361 // Get issues a GET request to the fcgi responder.
362 func (c *client) Get(p map[string]string, body io.Reader, l int64) (resp *http.Response, err error) {
363 p["REQUEST_METHOD"] = "GET"
364 p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10)
365
366 return c.Request(p, body)
367 }
368
369 // Head issues a HEAD request to the fcgi responder.
370 func (c *client) Head(p map[string]string) (resp *http.Response, err error) {
371 p["REQUEST_METHOD"] = "HEAD"
372 p["CONTENT_LENGTH"] = "0"
373
374 return c.Request(p, nil)
375 }
376
377 // Options issues an OPTIONS request to the fcgi responder.
378 func (c *client) Options(p map[string]string) (resp *http.Response, err error) {
379 p["REQUEST_METHOD"] = "OPTIONS"
380 p["CONTENT_LENGTH"] = "0"
381
382 return c.Request(p, nil)
383 }
384
385 // Post issues a POST request to the fcgi responder. with request body
386 // in the format that bodyType specified
387 func (c *client) Post(p map[string]string, method string, bodyType string, body io.Reader, l int64) (resp *http.Response, err error) {
388 if p == nil {
389 p = make(map[string]string)
390 }
391
392 p["REQUEST_METHOD"] = strings.ToUpper(method)
393
394 if len(p["REQUEST_METHOD"]) == 0 || p["REQUEST_METHOD"] == "GET" {
395 p["REQUEST_METHOD"] = "POST"
396 }
397
398 p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10)
399 if len(bodyType) > 0 {
400 p["CONTENT_TYPE"] = bodyType
401 } else {
402 p["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
403 }
404
405 return c.Request(p, body)
406 }
407
408 // PostForm issues a POST to the fcgi responder, with form
409 // as a string key to a list values (url.Values)
410 func (c *client) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) {
411 body := bytes.NewReader([]byte(data.Encode()))
412 return c.Post(p, "POST", "application/x-www-form-urlencoded", body, int64(body.Len()))
413 }
414
415 // PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard,
416 // with form as a string key to a list values (url.Values),
417 // and/or with file as a string key to a list file path.
418 func (c *client) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) {
419 buf := &bytes.Buffer{}
420 writer := multipart.NewWriter(buf)
421 bodyType := writer.FormDataContentType()
422
423 for key, val := range data {
424 for _, v0 := range val {
425 err = writer.WriteField(key, v0)
426 if err != nil {
427 return resp, err
428 }
429 }
430 }
431
432 for key, val := range file {
433 fd, e := os.Open(val)
434 if e != nil {
435 return nil, e
436 }
437 defer fd.Close()
438
439 part, e := writer.CreateFormFile(key, filepath.Base(val))
440 if e != nil {
441 return nil, e
442 }
443 _, err = io.Copy(part, fd)
444 if err != nil {
445 return resp, err
446 }
447 }
448
449 err = writer.Close()
450 if err != nil {
451 return resp, err
452 }
453
454 return c.Post(p, "POST", bodyType, buf, int64(buf.Len()))
455 }
456
457 // SetReadTimeout sets the read timeout for future calls that read from the
458 // fcgi responder. A zero value for t means no timeout will be set.
459 func (c *client) SetReadTimeout(t time.Duration) error {
460 if t != 0 {
461 c.readDeadline = time.Now().Add(t)
462 return c.rwc.SetReadDeadline(c.readDeadline)
463 }
464 return nil
465 }
466
467 // SetWriteTimeout sets the write timeout for future calls that send data to
468 // the fcgi responder. A zero value for t means no timeout will be set.
469 func (c *client) SetWriteTimeout(t time.Duration) error {
470 if t != 0 {
471 return c.rwc.SetWriteDeadline(time.Now().Add(t))
472 }
473 return nil
474 }
475
476 // Checks whether chunked is part of the encodings stack
477 func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }