github.com/vicanso/pike@v1.0.1-0.20210630235453-9099e041f6ec/cache/http_response.go (about)

     1  // MIT License
     2  
     3  // Copyright (c) 2020 Tree Xie
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in all
    13  // copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    21  // SOFTWARE.
    22  
    23  // HTTP响应数据,只用于根据客户端支持编码以及最小压缩长度返回对应的数据
    24  
    25  // 对于可缓存且大于最小缓存则可使用compress方法保存gzip与br两种缓存数据,
    26  // 在客户端请求时根据客户端支持的编码返回,若不支持压缩,则从解压获取原始数据返回
    27  // 对于不可缓存数据,根据客户端支持的编码以及数据长度返回对应数据
    28  
    29  package cache
    30  
    31  import (
    32  	"bytes"
    33  	"encoding/json"
    34  	"errors"
    35  	"net/http"
    36  	"regexp"
    37  	"strings"
    38  
    39  	"github.com/vicanso/elton"
    40  	"github.com/vicanso/pike/compress"
    41  )
    42  
    43  var ignoreHeaders = []string{
    44  	"Content-Encoding",
    45  	"Content-Length",
    46  	"Connection",
    47  	"Date",
    48  }
    49  
    50  var ErrBodyIsNil = errors.New("body is nil")
    51  
    52  var defaultCompressContentTypeFilter = regexp.MustCompile(`text|javascript|json|wasm|xml|font`)
    53  
    54  type (
    55  	// HTTPResponse http response's cache
    56  	HTTPResponse struct {
    57  		// 压缩服务名称
    58  		CompressSrv string `json:"compressSrv,omitempty"`
    59  		// 压缩最小尺寸
    60  		CompressMinLength int `json:"compressMinLength,omitempty"`
    61  		// 压缩数据类型
    62  		CompressContentTypeFilter *regexp.Regexp `json:"-"`
    63  		// 响应头
    64  		Header http.Header `json:"header,omitempty"`
    65  		// 响应状态码
    66  		StatusCode int    `json:"statusCode,omitempty"`
    67  		GzipBody   []byte `json:"gzipBody,omitempty"`
    68  		BrBody     []byte `json:"brBody,omitempty"`
    69  		RawBody    []byte `json:"rawBody,omitempty"`
    70  	}
    71  )
    72  
    73  func cloneHeaderAndIgnore(header http.Header) http.Header {
    74  	h := header.Clone()
    75  	for _, key := range ignoreHeaders {
    76  		h.Del(key)
    77  	}
    78  	return h
    79  }
    80  
    81  // NewHTTPResponse new a http response
    82  func NewHTTPResponse(statusCode int, header http.Header, encoding string, data []byte) (*HTTPResponse, error) {
    83  	resp := &HTTPResponse{
    84  		StatusCode: statusCode,
    85  		Header:     cloneHeaderAndIgnore(header),
    86  	}
    87  	switch encoding {
    88  	case compress.EncodingGzip:
    89  		resp.GzipBody = data
    90  	case compress.EncodingBrotli:
    91  		resp.BrBody = data
    92  	case "":
    93  		resp.RawBody = data
    94  	default:
    95  		// 取默认的compress来解压
    96  		compressSrv := compress.Get("")
    97  		data, err := compressSrv.Decompress(encoding, data)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  		header.Del(elton.HeaderContentEncoding)
   102  		resp.RawBody = data
   103  	}
   104  	return resp, nil
   105  }
   106  
   107  // Bytes http response to bytes
   108  func (resp *HTTPResponse) Bytes() (data []byte, err error) {
   109  	var contentTypeFilter string
   110  	if resp.CompressContentTypeFilter != nil {
   111  		contentTypeFilter = resp.CompressContentTypeFilter.String()
   112  	}
   113  
   114  	// 压缩服务名称
   115  	compressSrvBuf := []byte(resp.CompressSrv)
   116  	compressSrvBufSize := uint32ToBytes(len(compressSrvBuf))
   117  
   118  	// 最小压缩尺寸,4个字节
   119  	compressMinLengthBuf := uint32ToBytes(resp.CompressMinLength)
   120  
   121  	// // 压缩类型,4个字节保存长度
   122  	filterBuf := []byte(contentTypeFilter)
   123  	filterBufSize := uint32ToBytes(len(filterBuf))
   124  
   125  	// 响应头,4个字节保存长度
   126  	headerBuf, err := json.Marshal(resp.Header)
   127  	if err != nil {
   128  		return
   129  	}
   130  	headerBufSize := uint32ToBytes(len(headerBuf))
   131  
   132  	// 响应码,4个字节
   133  	statusCodeBuf := uint32ToBytes(resp.StatusCode)
   134  
   135  	// gzip,4个字节保存长度
   136  	gzipBufSize := uint32ToBytes(len(resp.GzipBody))
   137  
   138  	// br,4个字节保存长度
   139  	brBufSize := uint32ToBytes(len(resp.BrBody))
   140  
   141  	// raw,4个字节保存长度
   142  	rawBufSize := uint32ToBytes(len(resp.RawBody))
   143  
   144  	return bytes.Join([][]byte{
   145  		compressSrvBufSize,
   146  		compressSrvBuf,
   147  		compressMinLengthBuf,
   148  		filterBufSize,
   149  		filterBuf,
   150  		headerBufSize,
   151  		headerBuf,
   152  		statusCodeBuf,
   153  		gzipBufSize,
   154  		resp.GzipBody,
   155  		brBufSize,
   156  		resp.BrBody,
   157  		rawBufSize,
   158  		resp.RawBody,
   159  	}, []byte("")), nil
   160  
   161  }
   162  
   163  // FromBytes http response from bytes
   164  func (resp *HTTPResponse) FromBytes(data []byte) (err error) {
   165  	if len(data) == 0 {
   166  		return
   167  	}
   168  	buffer := bytes.NewBuffer(data)
   169  
   170  	size, err := readUint32ToInt(buffer)
   171  	if err != nil {
   172  		return
   173  	}
   174  	resp.CompressSrv = string(buffer.Next(size))
   175  
   176  	resp.CompressMinLength, err = readUint32ToInt(buffer)
   177  	if err != nil {
   178  		return
   179  	}
   180  
   181  	size, err = readUint32ToInt(buffer)
   182  	if err != nil {
   183  		return
   184  	}
   185  	contentTypeFilter := string(buffer.Next(size))
   186  	if contentTypeFilter != "" {
   187  		resp.CompressContentTypeFilter, err = regexp.Compile(contentTypeFilter)
   188  		if err != nil {
   189  			return
   190  		}
   191  	}
   192  
   193  	size, err = readUint32ToInt(buffer)
   194  	if err != nil {
   195  		return
   196  	}
   197  	headerBuf := buffer.Next(size)
   198  	err = json.Unmarshal(headerBuf, &resp.Header)
   199  	if err != nil {
   200  		return
   201  	}
   202  
   203  	resp.StatusCode, err = readUint32ToInt(buffer)
   204  	if err != nil {
   205  		return
   206  	}
   207  
   208  	size, err = readUint32ToInt(buffer)
   209  	if err != nil {
   210  		return
   211  	}
   212  	resp.GzipBody = buffer.Next(size)
   213  
   214  	size, err = readUint32ToInt(buffer)
   215  	if err != nil {
   216  		return
   217  	}
   218  	resp.BrBody = buffer.Next(size)
   219  
   220  	size, err = readUint32ToInt(buffer)
   221  	if err != nil {
   222  		return
   223  	}
   224  	resp.RawBody = buffer.Next(size)
   225  
   226  	return
   227  }
   228  
   229  func (resp *HTTPResponse) shouldCompressed() bool {
   230  	// 如果数据都小于最小压缩长度,则表示无需压缩
   231  	if len(resp.RawBody) <= resp.CompressMinLength &&
   232  		len(resp.GzipBody) <= resp.CompressMinLength &&
   233  		len(resp.BrBody) <= resp.CompressMinLength {
   234  		return false
   235  	}
   236  	filter := resp.CompressContentTypeFilter
   237  	if filter == nil {
   238  		filter = defaultCompressContentTypeFilter
   239  	}
   240  	// 数据类型匹配才可压缩
   241  	return filter.MatchString(resp.Header.Get(elton.HeaderContentType))
   242  }
   243  
   244  // GetRawBody get raw body of http response(not compress)
   245  func (resp *HTTPResponse) GetRawBody() (rawBody []byte, err error) {
   246  	rawBody = resp.RawBody
   247  	if len(rawBody) != 0 {
   248  		return
   249  	}
   250  	compressSrv := compress.Get("")
   251  	// 原始数据为空,需要从gzip或br中解压
   252  	if len(resp.GzipBody) != 0 {
   253  		return compressSrv.Gunzip(resp.GzipBody)
   254  	}
   255  	if len(resp.BrBody) != 0 {
   256  		return compressSrv.BrotliDecode(resp.BrBody)
   257  	}
   258  	return
   259  }
   260  
   261  // Compress compress http response's data
   262  func (resp *HTTPResponse) Compress() (err error) {
   263  	// 如果数据不需要压缩,则直接返回
   264  	if !resp.shouldCompressed() {
   265  		return
   266  	}
   267  	// 如果gzip与br均已压缩
   268  	if len(resp.GzipBody) != 0 && len(resp.BrBody) != 0 {
   269  		return
   270  	}
   271  	rawBody, err := resp.GetRawBody()
   272  	if err != nil {
   273  		return
   274  	}
   275  	// 如果原始数据为空,则直接报错,因为如果数据为空,则在前置判断是否可压缩已返回
   276  	if len(rawBody) == 0 {
   277  		err = ErrBodyIsNil
   278  		return
   279  	}
   280  	compressSrv := compress.Get(resp.CompressSrv)
   281  	if len(resp.GzipBody) == 0 {
   282  		resp.GzipBody, err = compressSrv.Gzip(rawBody)
   283  		if err != nil {
   284  			return
   285  		}
   286  	}
   287  	if len(resp.BrBody) == 0 {
   288  		resp.BrBody, err = compressSrv.Brotli(rawBody)
   289  		if err != nil {
   290  			return
   291  		}
   292  	}
   293  	// 压缩后清空原始数据,因为基本所有的客户端都支持gzip,
   294  	// 没必要再保存原始数据,如果有需要,可以从gzip中解压
   295  	resp.RawBody = nil
   296  	return
   297  }
   298  
   299  func (resp *HTTPResponse) getBodyByAcceptEncoding(acceptEncoding string) (encoding string, body []byte, err error) {
   300  	compressSrv := compress.Get(resp.CompressSrv)
   301  
   302  	// 如果支持br,而且br有数据
   303  	acceptBr := strings.Contains(acceptEncoding, compress.EncodingBrotli)
   304  	if acceptBr && len(resp.BrBody) != 0 {
   305  		return compress.EncodingBrotli, resp.BrBody, nil
   306  	}
   307  
   308  	// 如果支持gzip,而且gzip有数据
   309  	acceptGzip := strings.Contains(acceptEncoding, compress.EncodingGzip)
   310  	if acceptGzip && len(resp.GzipBody) != 0 {
   311  		return compress.EncodingGzip, resp.GzipBody, nil
   312  	}
   313  
   314  	// 获取原始数据压缩
   315  	rawBody, err := resp.GetRawBody()
   316  	if err != nil {
   317  		return "", nil, err
   318  	}
   319  	shouldCompressed := resp.shouldCompressed()
   320  	// 数据不应该压缩,直接返回
   321  	if !shouldCompressed {
   322  		return "", rawBody, nil
   323  	}
   324  
   325  	// 支持br,数据从原始数据压缩
   326  	if acceptBr {
   327  		brBody, err := compressSrv.Brotli(rawBody)
   328  		if err != nil {
   329  			return "", nil, err
   330  		}
   331  		return compress.EncodingBrotli, brBody, nil
   332  	}
   333  
   334  	// 支持gzip,数据从原始数据压缩
   335  	if acceptGzip {
   336  		gzipBody, err := compressSrv.Gzip(rawBody)
   337  		if err != nil {
   338  			return "", nil, err
   339  		}
   340  		return compress.EncodingGzip, gzipBody, nil
   341  	}
   342  
   343  	// 都不支持,返回原始数据
   344  	return "", rawBody, nil
   345  }
   346  
   347  // Fill fill response to context
   348  func (resp *HTTPResponse) Fill(c *elton.Context) (err error) {
   349  	encoding, body, err := resp.getBodyByAcceptEncoding(c.GetRequestHeader(elton.HeaderAcceptEncoding))
   350  	if err != nil {
   351  		return
   352  	}
   353  	c.MergeHeader(resp.Header)
   354  	c.SetHeader(elton.HeaderContentEncoding, encoding)
   355  	c.StatusCode = resp.StatusCode
   356  
   357  	c.BodyBuffer = bytes.NewBuffer(body)
   358  	return
   359  }