github.com/vicanso/pike@v1.0.1-0.20210630235453-9099e041f6ec/server/proxy.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 package server 24 25 import ( 26 "net/http" 27 "regexp" 28 "strconv" 29 "strings" 30 31 "github.com/vicanso/elton" 32 "github.com/vicanso/hes" 33 "github.com/vicanso/pike/cache" 34 "github.com/vicanso/pike/location" 35 "github.com/vicanso/pike/upstream" 36 "github.com/vicanso/pike/util" 37 "golang.org/x/net/context" 38 ) 39 40 var ( 41 noCacheReg = regexp.MustCompile(`no-cache|no-store|private`) 42 sMaxAgeReg = regexp.MustCompile(`s-maxage=(\d+)`) 43 maxAgeReg = regexp.MustCompile(`max-age=(\d+)`) 44 ) 45 46 // 根据Cache-Control的信息,获取s-maxage 或者max-age的值 47 func getCacheMaxAge(header http.Header) int { 48 // 如果有设置cookie,则为不可缓存 49 if header.Get(elton.HeaderSetCookie) != "" { 50 return 0 51 } 52 // 如果没有设置cache-control,则不可缓存 53 cc := strings.Join(header.Values(elton.HeaderCacheControl), ",") 54 if cc == "" { 55 return 0 56 } 57 58 // 如果设置不可缓存,返回0 59 if noCacheReg.MatchString(cc) { 60 return 0 61 } 62 // 优先从s-maxage中获取 63 var maxAge = 0 64 result := sMaxAgeReg.FindStringSubmatch(cc) 65 if len(result) == 2 { 66 maxAge, _ = strconv.Atoi(result[1]) 67 } else { 68 // 从max-age中获取缓存时间 69 result = maxAgeReg.FindStringSubmatch(cc) 70 if len(result) == 2 { 71 maxAge, _ = strconv.Atoi(result[1]) 72 } 73 } 74 75 // 如果有设置了 age 字段,则最大缓存时长减少 76 if age := header.Get(headerAge); age != "" { 77 v, _ := strconv.Atoi(age) 78 maxAge -= v 79 } 80 81 return maxAge 82 } 83 84 // NewProxy create proxy middleware 85 func NewProxy(s *server) elton.Handler { 86 return func(c *elton.Context) (err error) { 87 originalNext := c.Next 88 // 由于proxy中间件会调用next,因此直接覆盖, 89 // 避免导致先执行了后续的中间件(保证在函数调用next前是已完成此中间件处理) 90 c.Next = func() error { 91 return nil 92 } 93 94 l := location.Get(c.Request.Host, c.Request.RequestURI, s.GetLocations()...) 95 if l == nil { 96 err = ErrLocationNotFound 97 return 98 } 99 100 upstream := upstream.Get(l.Upstream) 101 if upstream == nil { 102 err = ErrUpstreamNotFound 103 return 104 } 105 106 reqHeader := c.Request.Header 107 var ifModifiedSince, ifNoneMatch string 108 status := getCacheStatus(c) 109 // 针对fetching的请求,由于其最终状态未知,因此需要删除有可能导致304的请求,避免无法生成缓存 110 if status == cache.StatusFetching { 111 ifModifiedSince = reqHeader.Get(elton.HeaderIfModifiedSince) 112 ifNoneMatch = reqHeader.Get(elton.HeaderIfNoneMatch) 113 if ifModifiedSince != "" { 114 reqHeader.Del(elton.HeaderIfModifiedSince) 115 } 116 if ifNoneMatch != "" { 117 reqHeader.Del(elton.HeaderIfNoneMatch) 118 } 119 } 120 121 // url rewrite 122 var originalPath string 123 if l.URLRewriter != nil { 124 originalPath = c.Request.URL.Path 125 l.URLRewriter(c.Request) 126 } 127 // 添加额外的请求头 128 l.AddRequestHeader(reqHeader) 129 130 // 添加query string 131 var originRawQuery string 132 if l.ShouldModifyQuery() { 133 originRawQuery = c.Request.URL.RawQuery 134 l.AddQuery(c.Request) 135 } 136 137 var acceptEncoding string 138 139 // 根据upstream设置可接受压缩编码调整 140 acceptEncodingChanged := upstream.Option.AcceptEncoding != "" 141 if acceptEncodingChanged { 142 acceptEncoding = reqHeader.Get(elton.HeaderAcceptEncoding) 143 reqHeader.Set(elton.HeaderAcceptEncoding, upstream.Option.AcceptEncoding) 144 } 145 146 if l.ProxyTimeout != 0 { 147 ctx, cancel := context.WithTimeout(c.Context(), l.ProxyTimeout) 148 defer cancel() 149 c.WithContext(ctx) 150 } 151 152 // clone当前header,用于后续恢复 153 originalHeader := c.Header().Clone() 154 c.ResetHeader() 155 err = upstream.Proxy(c) 156 // 如果出错超时,则转换为504 timeout,category:pike 157 if err != nil { 158 if he, ok := err.(*hes.Error); ok { 159 if he.Err == context.DeadlineExceeded { 160 err = util.NewError("Timeout", http.StatusGatewayTimeout) 161 } 162 } 163 } 164 165 // 恢复请求头 166 if ifModifiedSince != "" { 167 reqHeader.Set(elton.HeaderIfModifiedSince, ifModifiedSince) 168 } 169 if ifNoneMatch != "" { 170 reqHeader.Set(elton.HeaderIfNoneMatch, ifNoneMatch) 171 } 172 if acceptEncodingChanged { 173 reqHeader.Set(elton.HeaderAcceptEncoding, acceptEncoding) 174 } 175 176 // 恢复query 177 if originRawQuery != "" { 178 c.Request.URL.RawQuery = originRawQuery 179 } 180 181 header := c.Header() 182 // 添加额外的响应头 183 l.AddResponseHeader(header) 184 // 恢复原始url path 185 if originalPath != "" { 186 c.Request.URL.Path = originalPath 187 } 188 189 if err != nil { 190 return 191 } 192 193 var data []byte 194 if c.BodyBuffer != nil { 195 data = c.BodyBuffer.Bytes() 196 } 197 198 // 对于fetching的请求,从响应头中判断该请求缓存的有效期 199 if status == cache.StatusFetching { 200 maxAge := getCacheMaxAge(header) 201 if maxAge > 0 { 202 setHTTPCacheMaxAge(c, maxAge) 203 } 204 } 205 206 // 初始化http response时,如果已压缩,而且非gzip br,则会解压 207 httpResp, err := cache.NewHTTPResponse(c.StatusCode, header, header.Get(elton.HeaderContentEncoding), data) 208 if err != nil { 209 return 210 } 211 212 compressSrv, minLength, filter := s.GetCompress() 213 httpResp.CompressSrv = compressSrv 214 httpResp.CompressMinLength = minLength 215 httpResp.CompressContentTypeFilter = filter 216 setHTTPResp(c, httpResp) 217 218 // 重置context中由于proxy中间件影响的状态 statusCode, header, body 219 // 因为最终响应会从http response中生成,该响应会包括http响应头, 220 // 因此清除现在的header并恢复原来的header 221 c.ResetHeader() 222 c.MergeHeader(originalHeader) 223 c.BodyBuffer = nil 224 c.StatusCode = 0 225 c.Next = originalNext 226 return c.Next() 227 } 228 }