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 }