github.com/cloudwego/hertz@v0.9.3/pkg/protocol/http1/resp/header.go (about)

     1  /*
     2   * Copyright 2022 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * The MIT License (MIT)
    17   *
    18   * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors
    19   *
    20   * Permission is hereby granted, free of charge, to any person obtaining a copy
    21   * of this software and associated documentation files (the "Software"), to deal
    22   * in the Software without restriction, including without limitation the rights
    23   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    24   * copies of the Software, and to permit persons to whom the Software is
    25   * furnished to do so, subject to the following conditions:
    26   *
    27   * The above copyright notice and this permission notice shall be included in
    28   * all copies or substantial portions of the Software.
    29   *
    30   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    31   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    32   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    33   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    34   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    35   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    36   * THE SOFTWARE.
    37   *
    38   * This file may have been modified by CloudWeGo authors. All CloudWeGo
    39   * Modifications are Copyright 2022 CloudWeGo Authors.
    40   */
    41  
    42  package resp
    43  
    44  import (
    45  	"bytes"
    46  	"errors"
    47  	"fmt"
    48  	"io"
    49  	"strings"
    50  
    51  	"github.com/cloudwego/hertz/internal/bytesconv"
    52  	"github.com/cloudwego/hertz/internal/bytestr"
    53  	errs "github.com/cloudwego/hertz/pkg/common/errors"
    54  	"github.com/cloudwego/hertz/pkg/common/utils"
    55  	"github.com/cloudwego/hertz/pkg/network"
    56  	"github.com/cloudwego/hertz/pkg/protocol"
    57  	"github.com/cloudwego/hertz/pkg/protocol/consts"
    58  	"github.com/cloudwego/hertz/pkg/protocol/http1/ext"
    59  )
    60  
    61  var errTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, "read response header")
    62  
    63  // Read reads response header from r.
    64  //
    65  // io.EOF is returned if r is closed before reading the first header byte.
    66  func ReadHeader(h *protocol.ResponseHeader, r network.Reader) error {
    67  	n := 1
    68  	for {
    69  		err := tryRead(h, r, n)
    70  		if err == nil {
    71  			return nil
    72  		}
    73  		if !errors.Is(err, errs.ErrNeedMore) {
    74  			h.ResetSkipNormalize()
    75  			return err
    76  		}
    77  
    78  		// No more data available on the wire, try block peek(by netpoll)
    79  		if n == r.Len() {
    80  			n++
    81  
    82  			continue
    83  		}
    84  		n = r.Len()
    85  	}
    86  }
    87  
    88  // WriteHeader writes response header to w.
    89  func WriteHeader(h *protocol.ResponseHeader, w network.Writer) error {
    90  	header := h.Header()
    91  	h.SetHeaderLength(len(header))
    92  	_, err := w.WriteBinary(header)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	return nil
    97  }
    98  
    99  // ConnectionUpgrade returns true if 'Connection: Upgrade' header is set.
   100  func ConnectionUpgrade(h *protocol.ResponseHeader) bool {
   101  	return ext.HasHeaderValue(h.Peek(consts.HeaderConnection), bytestr.StrKeepAlive)
   102  }
   103  
   104  func tryRead(h *protocol.ResponseHeader, r network.Reader, n int) error {
   105  	h.ResetSkipNormalize()
   106  	b, err := r.Peek(n)
   107  	if len(b) == 0 {
   108  		// Return ErrTimeout on any timeout.
   109  		if err != nil && strings.Contains(err.Error(), "timeout") {
   110  			return errTimeout
   111  		}
   112  		// treat all other errors on the first byte read as EOF
   113  		if n == 1 || err == io.EOF {
   114  			return io.EOF
   115  		}
   116  
   117  		return fmt.Errorf("error when reading response headers: %s", err)
   118  	}
   119  	b = ext.MustPeekBuffered(r)
   120  	headersLen, errParse := parse(h, b)
   121  	if errParse != nil {
   122  		return ext.HeaderError("response", err, errParse, b)
   123  	}
   124  	ext.MustDiscard(r, headersLen)
   125  	return nil
   126  }
   127  
   128  func parseHeaders(h *protocol.ResponseHeader, buf []byte) (int, error) {
   129  	// 'identity' content-length by default
   130  	h.InitContentLengthWithValue(-2)
   131  
   132  	var s ext.HeaderScanner
   133  	s.B = buf
   134  	s.DisableNormalizing = h.IsDisableNormalizing()
   135  	var err error
   136  	for s.Next() {
   137  		if len(s.Key) > 0 {
   138  			switch s.Key[0] | 0x20 {
   139  			case 'c':
   140  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentType) {
   141  					h.SetContentTypeBytes(s.Value)
   142  					continue
   143  				}
   144  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentEncoding) {
   145  					h.SetContentEncodingBytes(s.Value)
   146  					continue
   147  				}
   148  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrContentLength) {
   149  					var contentLength int
   150  					if h.ContentLength() != -1 {
   151  						if contentLength, err = protocol.ParseContentLength(s.Value); err != nil {
   152  							h.InitContentLengthWithValue(-2)
   153  						} else {
   154  							h.InitContentLengthWithValue(contentLength)
   155  							h.SetContentLengthBytes(s.Value)
   156  						}
   157  					}
   158  					continue
   159  				}
   160  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrConnection) {
   161  					if bytes.Equal(s.Value, bytestr.StrClose) {
   162  						h.SetConnectionClose(true)
   163  					} else {
   164  						h.SetConnectionClose(false)
   165  						h.AddArgBytes(s.Key, s.Value, protocol.ArgsHasValue)
   166  					}
   167  					continue
   168  				}
   169  			case 's':
   170  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrServer) {
   171  					h.SetServerBytes(s.Value)
   172  					continue
   173  				}
   174  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrSetCookie) {
   175  					h.ParseSetCookie(s.Value)
   176  					continue
   177  				}
   178  			case 't':
   179  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrTransferEncoding) {
   180  					if !bytes.Equal(s.Value, bytestr.StrIdentity) {
   181  						h.InitContentLengthWithValue(-1)
   182  						h.SetArgBytes(bytestr.StrTransferEncoding, bytestr.StrChunked, protocol.ArgsHasValue)
   183  					}
   184  					continue
   185  				}
   186  				if utils.CaseInsensitiveCompare(s.Key, bytestr.StrTrailer) {
   187  					err = h.Trailer().SetTrailers(s.Value)
   188  					continue
   189  				}
   190  			}
   191  			h.AddArgBytes(s.Key, s.Value, protocol.ArgsHasValue)
   192  		}
   193  	}
   194  	if s.Err != nil {
   195  		h.SetConnectionClose(true)
   196  		return 0, s.Err
   197  	}
   198  
   199  	if h.ContentLength() < 0 {
   200  		h.SetContentLengthBytes(h.ContentLengthBytes()[:0])
   201  	}
   202  	if h.ContentLength() == -2 && !ConnectionUpgrade(h) && !h.MustSkipContentLength() {
   203  		h.SetArgBytes(bytestr.StrTransferEncoding, bytestr.StrIdentity, protocol.ArgsHasValue)
   204  		h.SetConnectionClose(true)
   205  	}
   206  	if !h.IsHTTP11() && !h.ConnectionClose() {
   207  		// close connection for non-http/1.1 response unless 'Connection: keep-alive' is set.
   208  		v := h.PeekArgBytes(bytestr.StrConnection)
   209  		h.SetConnectionClose(!ext.HasHeaderValue(v, bytestr.StrKeepAlive))
   210  	}
   211  
   212  	return len(buf) - len(s.B), err
   213  }
   214  
   215  func parse(h *protocol.ResponseHeader, buf []byte) (int, error) {
   216  	m, err := parseFirstLine(h, buf)
   217  	if err != nil {
   218  		return 0, err
   219  	}
   220  	n, err := parseHeaders(h, buf[m:])
   221  	if err != nil {
   222  		return 0, err
   223  	}
   224  	return m + n, nil
   225  }
   226  
   227  func parseFirstLine(h *protocol.ResponseHeader, buf []byte) (int, error) {
   228  	bNext := buf
   229  	var b []byte
   230  	var err error
   231  	for len(b) == 0 {
   232  		if b, bNext, err = utils.NextLine(bNext); err != nil {
   233  			return 0, err
   234  		}
   235  	}
   236  
   237  	// parse protocol
   238  	n := bytes.IndexByte(b, ' ')
   239  	if n < 0 {
   240  		return 0, fmt.Errorf("cannot find whitespace in the first line of response %q", buf)
   241  	}
   242  
   243  	isHTTP11 := bytes.Equal(b[:n], bytestr.StrHTTP11)
   244  	if !isHTTP11 {
   245  		h.SetProtocol(consts.HTTP10)
   246  	} else {
   247  		h.SetProtocol(consts.HTTP11)
   248  	}
   249  
   250  	b = b[n+1:]
   251  
   252  	// parse status code
   253  	var statusCode int
   254  	statusCode, n, err = bytesconv.ParseUintBuf(b)
   255  	h.SetStatusCode(statusCode)
   256  	if err != nil {
   257  		return 0, fmt.Errorf("cannot parse response status code: %s. Response %q", err, buf)
   258  	}
   259  	if len(b) > n && b[n] != ' ' {
   260  		return 0, fmt.Errorf("unexpected char at the end of status code. Response %q", buf)
   261  	}
   262  
   263  	return len(buf) - len(bNext), nil
   264  }