Source file src/pkg/net/http/request.go
1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // HTTP Request reading and parsing. 6 7 package http 8 9 import ( 10 "bufio" 11 "bytes" 12 "crypto/tls" 13 "encoding/base64" 14 "errors" 15 "fmt" 16 "io" 17 "io/ioutil" 18 "mime" 19 "mime/multipart" 20 "net/textproto" 21 "net/url" 22 "strings" 23 ) 24 25 const ( 26 maxValueLength = 4096 27 maxHeaderLines = 1024 28 chunkSize = 4 << 10 // 4 KB chunks 29 defaultMaxMemory = 32 << 20 // 32 MB 30 ) 31 32 // ErrMissingFile is returned by FormFile when the provided file field name 33 // is either not present in the request or not a file field. 34 var ErrMissingFile = errors.New("http: no such file") 35 36 // HTTP request parsing errors. 37 type ProtocolError struct { 38 ErrorString string 39 } 40 41 func (err *ProtocolError) Error() string { return err.ErrorString } 42 43 var ( 44 ErrHeaderTooLong = &ProtocolError{"header too long"} 45 ErrShortBody = &ProtocolError{"entity body too short"} 46 ErrNotSupported = &ProtocolError{"feature not supported"} 47 ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"} 48 ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"} 49 ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"} 50 ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"} 51 ) 52 53 type badStringError struct { 54 what string 55 str string 56 } 57 58 func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } 59 60 // Headers that Request.Write handles itself and should be skipped. 61 var reqWriteExcludeHeader = map[string]bool{ 62 "Host": true, // not in Header map anyway 63 "User-Agent": true, 64 "Content-Length": true, 65 "Transfer-Encoding": true, 66 "Trailer": true, 67 } 68 69 // A Request represents an HTTP request received by a server 70 // or to be sent by a client. 71 type Request struct { 72 Method string // GET, POST, PUT, etc. 73 URL *url.URL 74 75 // The protocol version for incoming requests. 76 // Outgoing requests always use HTTP/1.1. 77 Proto string // "HTTP/1.0" 78 ProtoMajor int // 1 79 ProtoMinor int // 0 80 81 // A header maps request lines to their values. 82 // If the header says 83 // 84 // accept-encoding: gzip, deflate 85 // Accept-Language: en-us 86 // Connection: keep-alive 87 // 88 // then 89 // 90 // Header = map[string][]string{ 91 // "Accept-Encoding": {"gzip, deflate"}, 92 // "Accept-Language": {"en-us"}, 93 // "Connection": {"keep-alive"}, 94 // } 95 // 96 // HTTP defines that header names are case-insensitive. 97 // The request parser implements this by canonicalizing the 98 // name, making the first character and any characters 99 // following a hyphen uppercase and the rest lowercase. 100 Header Header 101 102 // The message body. 103 Body io.ReadCloser 104 105 // ContentLength records the length of the associated content. 106 // The value -1 indicates that the length is unknown. 107 // Values >= 0 indicate that the given number of bytes may 108 // be read from Body. 109 // For outgoing requests, a value of 0 means unknown if Body is not nil. 110 ContentLength int64 111 112 // TransferEncoding lists the transfer encodings from outermost to 113 // innermost. An empty list denotes the "identity" encoding. 114 // TransferEncoding can usually be ignored; chunked encoding is 115 // automatically added and removed as necessary when sending and 116 // receiving requests. 117 TransferEncoding []string 118 119 // Close indicates whether to close the connection after 120 // replying to this request. 121 Close bool 122 123 // The host on which the URL is sought. 124 // Per RFC 2616, this is either the value of the Host: header 125 // or the host name given in the URL itself. 126 Host string 127 128 // Form contains the parsed form data, including both the URL 129 // field's query parameters and the POST or PUT form data. 130 // This field is only available after ParseForm is called. 131 // The HTTP client ignores Form and uses Body instead. 132 Form url.Values 133 134 // MultipartForm is the parsed multipart form, including file uploads. 135 // This field is only available after ParseMultipartForm is called. 136 // The HTTP client ignores MultipartForm and uses Body instead. 137 MultipartForm *multipart.Form 138 139 // Trailer maps trailer keys to values. Like for Header, if the 140 // response has multiple trailer lines with the same key, they will be 141 // concatenated, delimited by commas. 142 // For server requests, Trailer is only populated after Body has been 143 // closed or fully consumed. 144 // Trailer support is only partially complete. 145 Trailer Header 146 147 // RemoteAddr allows HTTP servers and other software to record 148 // the network address that sent the request, usually for 149 // logging. This field is not filled in by ReadRequest and 150 // has no defined format. The HTTP server in this package 151 // sets RemoteAddr to an "IP:port" address before invoking a 152 // handler. 153 // This field is ignored by the HTTP client. 154 RemoteAddr string 155 156 // RequestURI is the unmodified Request-URI of the 157 // Request-Line (RFC 2616, Section 5.1) as sent by the client 158 // to a server. Usually the URL field should be used instead. 159 // It is an error to set this field in an HTTP client request. 160 RequestURI string 161 162 // TLS allows HTTP servers and other software to record 163 // information about the TLS connection on which the request 164 // was received. This field is not filled in by ReadRequest. 165 // The HTTP server in this package sets the field for 166 // TLS-enabled connections before invoking a handler; 167 // otherwise it leaves the field nil. 168 // This field is ignored by the HTTP client. 169 TLS *tls.ConnectionState 170 } 171 172 // ProtoAtLeast returns whether the HTTP protocol used 173 // in the request is at least major.minor. 174 func (r *Request) ProtoAtLeast(major, minor int) bool { 175 return r.ProtoMajor > major || 176 r.ProtoMajor == major && r.ProtoMinor >= minor 177 } 178 179 // UserAgent returns the client's User-Agent, if sent in the request. 180 func (r *Request) UserAgent() string { 181 return r.Header.Get("User-Agent") 182 } 183 184 // Cookies parses and returns the HTTP cookies sent with the request. 185 func (r *Request) Cookies() []*Cookie { 186 return readCookies(r.Header, "") 187 } 188 189 var ErrNoCookie = errors.New("http: named cookie not present") 190 191 // Cookie returns the named cookie provided in the request or 192 // ErrNoCookie if not found. 193 func (r *Request) Cookie(name string) (*Cookie, error) { 194 for _, c := range readCookies(r.Header, name) { 195 return c, nil 196 } 197 return nil, ErrNoCookie 198 } 199 200 // AddCookie adds a cookie to the request. Per RFC 6265 section 5.4, 201 // AddCookie does not attach more than one Cookie header field. That 202 // means all cookies, if any, are written into the same line, 203 // separated by semicolon. 204 func (r *Request) AddCookie(c *Cookie) { 205 s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) 206 if c := r.Header.Get("Cookie"); c != "" { 207 r.Header.Set("Cookie", c+"; "+s) 208 } else { 209 r.Header.Set("Cookie", s) 210 } 211 } 212 213 // Referer returns the referring URL, if sent in the request. 214 // 215 // Referer is misspelled as in the request itself, a mistake from the 216 // earliest days of HTTP. This value can also be fetched from the 217 // Header map as Header["Referer"]; the benefit of making it available 218 // as a method is that the compiler can diagnose programs that use the 219 // alternate (correct English) spelling req.Referrer() but cannot 220 // diagnose programs that use Header["Referrer"]. 221 func (r *Request) Referer() string { 222 return r.Header.Get("Referer") 223 } 224 225 // multipartByReader is a sentinel value. 226 // Its presence in Request.MultipartForm indicates that parsing of the request 227 // body has been handed off to a MultipartReader instead of ParseMultipartFrom. 228 var multipartByReader = &multipart.Form{ 229 Value: make(map[string][]string), 230 File: make(map[string][]*multipart.FileHeader), 231 } 232 233 // MultipartReader returns a MIME multipart reader if this is a 234 // multipart/form-data POST request, else returns nil and an error. 235 // Use this function instead of ParseMultipartForm to 236 // process the request body as a stream. 237 func (r *Request) MultipartReader() (*multipart.Reader, error) { 238 if r.MultipartForm == multipartByReader { 239 return nil, errors.New("http: MultipartReader called twice") 240 } 241 if r.MultipartForm != nil { 242 return nil, errors.New("http: multipart handled by ParseMultipartForm") 243 } 244 r.MultipartForm = multipartByReader 245 return r.multipartReader() 246 } 247 248 func (r *Request) multipartReader() (*multipart.Reader, error) { 249 v := r.Header.Get("Content-Type") 250 if v == "" { 251 return nil, ErrNotMultipart 252 } 253 d, params, err := mime.ParseMediaType(v) 254 if err != nil || d != "multipart/form-data" { 255 return nil, ErrNotMultipart 256 } 257 boundary, ok := params["boundary"] 258 if !ok { 259 return nil, ErrMissingBoundary 260 } 261 return multipart.NewReader(r.Body, boundary), nil 262 } 263 264 // Return value if nonempty, def otherwise. 265 func valueOrDefault(value, def string) string { 266 if value != "" { 267 return value 268 } 269 return def 270 } 271 272 const defaultUserAgent = "Go http package" 273 274 // Write writes an HTTP/1.1 request -- header and body -- in wire format. 275 // This method consults the following fields of the request: 276 // Host 277 // URL 278 // Method (defaults to "GET") 279 // Header 280 // ContentLength 281 // TransferEncoding 282 // Body 283 // 284 // If Body is present, Content-Length is <= 0 and TransferEncoding 285 // hasn't been set to "identity", Write adds "Transfer-Encoding: 286 // chunked" to the header. Body is closed after it is sent. 287 func (r *Request) Write(w io.Writer) error { 288 return r.write(w, false, nil) 289 } 290 291 // WriteProxy is like Write but writes the request in the form 292 // expected by an HTTP proxy. In particular, WriteProxy writes the 293 // initial Request-URI line of the request with an absolute URI, per 294 // section 5.1.2 of RFC 2616, including the scheme and host. 295 // In either case, WriteProxy also writes a Host header, using 296 // either r.Host or r.URL.Host. 297 func (r *Request) WriteProxy(w io.Writer) error { 298 return r.write(w, true, nil) 299 } 300 301 // extraHeaders may be nil 302 func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error { 303 host := req.Host 304 if host == "" { 305 if req.URL == nil { 306 return errors.New("http: Request.Write on Request with no Host or URL set") 307 } 308 host = req.URL.Host 309 } 310 311 ruri := req.URL.RequestURI() 312 if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" { 313 ruri = req.URL.Scheme + "://" + host + ruri 314 } else if req.Method == "CONNECT" && req.URL.Path == "" { 315 // CONNECT requests normally give just the host and port, not a full URL. 316 ruri = host 317 } 318 // TODO(bradfitz): escape at least newlines in ruri? 319 320 bw := bufio.NewWriter(w) 321 fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri) 322 323 // Header lines 324 fmt.Fprintf(bw, "Host: %s\r\n", host) 325 326 // Use the defaultUserAgent unless the Header contains one, which 327 // may be blank to not send the header. 328 userAgent := defaultUserAgent 329 if req.Header != nil { 330 if ua := req.Header["User-Agent"]; len(ua) > 0 { 331 userAgent = ua[0] 332 } 333 } 334 if userAgent != "" { 335 fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent) 336 } 337 338 // Process Body,ContentLength,Close,Trailer 339 tw, err := newTransferWriter(req) 340 if err != nil { 341 return err 342 } 343 err = tw.WriteHeader(bw) 344 if err != nil { 345 return err 346 } 347 348 // TODO: split long values? (If so, should share code with Conn.Write) 349 err = req.Header.WriteSubset(bw, reqWriteExcludeHeader) 350 if err != nil { 351 return err 352 } 353 354 if extraHeaders != nil { 355 err = extraHeaders.Write(bw) 356 if err != nil { 357 return err 358 } 359 } 360 361 io.WriteString(bw, "\r\n") 362 363 // Write body and trailer 364 err = tw.WriteBody(bw) 365 if err != nil { 366 return err 367 } 368 369 return bw.Flush() 370 } 371 372 // Convert decimal at s[i:len(s)] to integer, 373 // returning value, string position where the digits stopped, 374 // and whether there was a valid number (digits, not too big). 375 func atoi(s string, i int) (n, i1 int, ok bool) { 376 const Big = 1000000 377 if i >= len(s) || s[i] < '0' || s[i] > '9' { 378 return 0, 0, false 379 } 380 n = 0 381 for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { 382 n = n*10 + int(s[i]-'0') 383 if n > Big { 384 return 0, 0, false 385 } 386 } 387 return n, i, true 388 } 389 390 // ParseHTTPVersion parses a HTTP version string. 391 // "HTTP/1.0" returns (1, 0, true). 392 func ParseHTTPVersion(vers string) (major, minor int, ok bool) { 393 if len(vers) < 5 || vers[0:5] != "HTTP/" { 394 return 0, 0, false 395 } 396 major, i, ok := atoi(vers, 5) 397 if !ok || i >= len(vers) || vers[i] != '.' { 398 return 0, 0, false 399 } 400 minor, i, ok = atoi(vers, i+1) 401 if !ok || i != len(vers) { 402 return 0, 0, false 403 } 404 return major, minor, true 405 } 406 407 // NewRequest returns a new Request given a method, URL, and optional body. 408 func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { 409 u, err := url.Parse(urlStr) 410 if err != nil { 411 return nil, err 412 } 413 rc, ok := body.(io.ReadCloser) 414 if !ok && body != nil { 415 rc = ioutil.NopCloser(body) 416 } 417 req := &Request{ 418 Method: method, 419 URL: u, 420 Proto: "HTTP/1.1", 421 ProtoMajor: 1, 422 ProtoMinor: 1, 423 Header: make(Header), 424 Body: rc, 425 Host: u.Host, 426 } 427 if body != nil { 428 switch v := body.(type) { 429 case *strings.Reader: 430 req.ContentLength = int64(v.Len()) 431 case *bytes.Buffer: 432 req.ContentLength = int64(v.Len()) 433 } 434 } 435 436 return req, nil 437 } 438 439 // SetBasicAuth sets the request's Authorization header to use HTTP 440 // Basic Authentication with the provided username and password. 441 // 442 // With HTTP Basic Authentication the provided username and password 443 // are not encrypted. 444 func (r *Request) SetBasicAuth(username, password string) { 445 s := username + ":" + password 446 r.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(s))) 447 } 448 449 // ReadRequest reads and parses a request from b. 450 func ReadRequest(b *bufio.Reader) (req *Request, err error) { 451 452 tp := textproto.NewReader(b) 453 req = new(Request) 454 455 // First line: GET /index.html HTTP/1.0 456 var s string 457 if s, err = tp.ReadLine(); err != nil { 458 return nil, err 459 } 460 defer func() { 461 if err == io.EOF { 462 err = io.ErrUnexpectedEOF 463 } 464 }() 465 466 var f []string 467 if f = strings.SplitN(s, " ", 3); len(f) < 3 { 468 return nil, &badStringError{"malformed HTTP request", s} 469 } 470 req.Method, req.RequestURI, req.Proto = f[0], f[1], f[2] 471 rawurl := req.RequestURI 472 var ok bool 473 if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok { 474 return nil, &badStringError{"malformed HTTP version", req.Proto} 475 } 476 477 // CONNECT requests are used two different ways, and neither uses a full URL: 478 // The standard use is to tunnel HTTPS through an HTTP proxy. 479 // It looks like "CONNECT www.google.com:443 HTTP/1.1", and the parameter is 480 // just the authority section of a URL. This information should go in req.URL.Host. 481 // 482 // The net/rpc package also uses CONNECT, but there the parameter is a path 483 // that starts with a slash. It can be parsed with the regular URL parser, 484 // and the path will end up in req.URL.Path, where it needs to be in order for 485 // RPC to work. 486 justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/") 487 if justAuthority { 488 rawurl = "http://" + rawurl 489 } 490 491 if req.URL, err = url.ParseRequestURI(rawurl); err != nil { 492 return nil, err 493 } 494 495 if justAuthority { 496 // Strip the bogus "http://" back off. 497 req.URL.Scheme = "" 498 } 499 500 // Subsequent lines: Key: value. 501 mimeHeader, err := tp.ReadMIMEHeader() 502 if err != nil { 503 return nil, err 504 } 505 req.Header = Header(mimeHeader) 506 507 // RFC2616: Must treat 508 // GET /index.html HTTP/1.1 509 // Host: www.google.com 510 // and 511 // GET http://www.google.com/index.html HTTP/1.1 512 // Host: doesntmatter 513 // the same. In the second case, any Host line is ignored. 514 req.Host = req.URL.Host 515 if req.Host == "" { 516 req.Host = req.Header.Get("Host") 517 } 518 req.Header.Del("Host") 519 520 fixPragmaCacheControl(req.Header) 521 522 // TODO: Parse specific header values: 523 // Accept 524 // Accept-Encoding 525 // Accept-Language 526 // Authorization 527 // Cache-Control 528 // Connection 529 // Date 530 // Expect 531 // From 532 // If-Match 533 // If-Modified-Since 534 // If-None-Match 535 // If-Range 536 // If-Unmodified-Since 537 // Max-Forwards 538 // Proxy-Authorization 539 // Referer [sic] 540 // TE (transfer-codings) 541 // Trailer 542 // Transfer-Encoding 543 // Upgrade 544 // User-Agent 545 // Via 546 // Warning 547 548 err = readTransfer(req, b) 549 if err != nil { 550 return nil, err 551 } 552 553 return req, nil 554 } 555 556 // MaxBytesReader is similar to io.LimitReader but is intended for 557 // limiting the size of incoming request bodies. In contrast to 558 // io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a 559 // non-EOF error for a Read beyond the limit, and Closes the 560 // underlying reader when its Close method is called. 561 // 562 // MaxBytesReader prevents clients from accidentally or maliciously 563 // sending a large request and wasting server resources. 564 func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { 565 return &maxBytesReader{w: w, r: r, n: n} 566 } 567 568 type maxBytesReader struct { 569 w ResponseWriter 570 r io.ReadCloser // underlying reader 571 n int64 // max bytes remaining 572 stopped bool 573 } 574 575 func (l *maxBytesReader) Read(p []byte) (n int, err error) { 576 if l.n <= 0 { 577 if !l.stopped { 578 l.stopped = true 579 if res, ok := l.w.(*response); ok { 580 res.requestTooLarge() 581 } 582 } 583 return 0, errors.New("http: request body too large") 584 } 585 if int64(len(p)) > l.n { 586 p = p[:l.n] 587 } 588 n, err = l.r.Read(p) 589 l.n -= int64(n) 590 return 591 } 592 593 func (l *maxBytesReader) Close() error { 594 return l.r.Close() 595 } 596 597 // ParseForm parses the raw query from the URL. 598 // 599 // For POST or PUT requests, it also parses the request body as a form. 600 // If the request Body's size has not already been limited by MaxBytesReader, 601 // the size is capped at 10MB. 602 // 603 // ParseMultipartForm calls ParseForm automatically. 604 // It is idempotent. 605 func (r *Request) ParseForm() (err error) { 606 if r.Form != nil { 607 return 608 } 609 if r.URL != nil { 610 r.Form, err = url.ParseQuery(r.URL.RawQuery) 611 } 612 if r.Method == "POST" || r.Method == "PUT" { 613 if r.Body == nil { 614 return errors.New("missing form body") 615 } 616 ct := r.Header.Get("Content-Type") 617 ct, _, err = mime.ParseMediaType(ct) 618 switch { 619 case ct == "application/x-www-form-urlencoded": 620 var reader io.Reader = r.Body 621 maxFormSize := int64(1<<63 - 1) 622 if _, ok := r.Body.(*maxBytesReader); !ok { 623 maxFormSize = int64(10 << 20) // 10 MB is a lot of text. 624 reader = io.LimitReader(r.Body, maxFormSize+1) 625 } 626 b, e := ioutil.ReadAll(reader) 627 if e != nil { 628 if err == nil { 629 err = e 630 } 631 break 632 } 633 if int64(len(b)) > maxFormSize { 634 return errors.New("http: POST too large") 635 } 636 var newValues url.Values 637 newValues, e = url.ParseQuery(string(b)) 638 if err == nil { 639 err = e 640 } 641 if r.Form == nil { 642 r.Form = make(url.Values) 643 } 644 // Copy values into r.Form. TODO: make this smoother. 645 for k, vs := range newValues { 646 for _, value := range vs { 647 r.Form.Add(k, value) 648 } 649 } 650 case ct == "multipart/form-data": 651 // handled by ParseMultipartForm (which is calling us, or should be) 652 // TODO(bradfitz): there are too many possible 653 // orders to call too many functions here. 654 // Clean this up and write more tests. 655 // request_test.go contains the start of this, 656 // in TestRequestMultipartCallOrder. 657 } 658 } 659 return err 660 } 661 662 // ParseMultipartForm parses a request body as multipart/form-data. 663 // The whole request body is parsed and up to a total of maxMemory bytes of 664 // its file parts are stored in memory, with the remainder stored on 665 // disk in temporary files. 666 // ParseMultipartForm calls ParseForm if necessary. 667 // After one call to ParseMultipartForm, subsequent calls have no effect. 668 func (r *Request) ParseMultipartForm(maxMemory int64) error { 669 if r.MultipartForm == multipartByReader { 670 return errors.New("http: multipart handled by MultipartReader") 671 } 672 if r.Form == nil { 673 err := r.ParseForm() 674 if err != nil { 675 return err 676 } 677 } 678 if r.MultipartForm != nil { 679 return nil 680 } 681 682 mr, err := r.multipartReader() 683 if err == ErrNotMultipart { 684 return nil 685 } else if err != nil { 686 return err 687 } 688 689 f, err := mr.ReadForm(maxMemory) 690 if err != nil { 691 return err 692 } 693 for k, v := range f.Value { 694 r.Form[k] = append(r.Form[k], v...) 695 } 696 r.MultipartForm = f 697 698 return nil 699 } 700 701 // FormValue returns the first value for the named component of the query. 702 // FormValue calls ParseMultipartForm and ParseForm if necessary. 703 func (r *Request) FormValue(key string) string { 704 if r.Form == nil { 705 r.ParseMultipartForm(defaultMaxMemory) 706 } 707 if vs := r.Form[key]; len(vs) > 0 { 708 return vs[0] 709 } 710 return "" 711 } 712 713 // FormFile returns the first file for the provided form key. 714 // FormFile calls ParseMultipartForm and ParseForm if necessary. 715 func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { 716 if r.MultipartForm == multipartByReader { 717 return nil, nil, errors.New("http: multipart handled by MultipartReader") 718 } 719 if r.MultipartForm == nil { 720 err := r.ParseMultipartForm(defaultMaxMemory) 721 if err != nil { 722 return nil, nil, err 723 } 724 } 725 if r.MultipartForm != nil && r.MultipartForm.File != nil { 726 if fhs := r.MultipartForm.File[key]; len(fhs) > 0 { 727 f, err := fhs[0].Open() 728 return f, fhs[0], err 729 } 730 } 731 return nil, nil, ErrMissingFile 732 } 733 734 func (r *Request) expectsContinue() bool { 735 return strings.ToLower(r.Header.Get("Expect")) == "100-continue" 736 } 737 738 func (r *Request) wantsHttp10KeepAlive() bool { 739 if r.ProtoMajor != 1 || r.ProtoMinor != 0 { 740 return false 741 } 742 return strings.Contains(strings.ToLower(r.Header.Get("Connection")), "keep-alive") 743 }