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
| 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" } |