Beispiel #1
0
// compare time old
// time1 == time2 return 0
// time1 > time2 return 1
//time1 < time2 return -1
// if  time1 == "" or time2 == "" return error
func headerTimeCmp(time1, time2 string) (int, error) {
	if time1 == "" || time2 == "" {
		return 0, fmt.Errorf("time1 || time2 empty")
	}

	t1, err := time.Parse(TimeFormat, time1)
	if err != nil {
		if time1 == time2 {
			return 0, nil
		} else {
			glog.V(logVNum).Infof("headerTimeCmp err = %v", err)
			return 0, err
		}
	}

	t2, err := time.Parse(TimeFormat, time2)
	if err != nil {
		if time1 == time2 {
			return 0, nil
		} else {
			glog.V(logVNum).Infof("headerTimeCmp err = %v", err)
			return 0, err
		}
	}

	if t1.Equal(t2) {
		return 0, nil
	} else if t1.After(t2) {
		return 1, nil
	} else {
		return -1, nil
	}
}
Beispiel #2
0
// Get returns the []byte representation of the response and true if present, false if not
func (c *MemoryCache) Get(key string) (resp []byte, ok bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	resp, ok = c.items[key]
	if ok {
		glog.V(logVNum).Infof("Cache.Get key = %v", key)
	}

	return resp, ok
}
Beispiel #3
0
func newGatewayTimeoutResponse(req *http.Request) *http.Response {
	var braw bytes.Buffer
	braw.WriteString("HTTP/1.1 504 Gateway Timeout\r\n\r\n")
	resp, err := http.ReadResponse(bufio.NewReader(&braw), req)
	if err != nil {
		panic(err)
	}
	glog.V(logVNum).Infof("newGatewayTimeoutResponse 504")
	return resp
}
Beispiel #4
0
// varyMatches will return false unless all of the cached values for the headers listed in Vary
// match the new request
func varyMatches(cachedResp *http.Response, req *http.Request) bool {
	for _, header := range headerAllCommaSepValues(cachedResp.Header, "vary") {
		header = http.CanonicalHeaderKey(header)
		if header != "" && req.Header.Get(header) != cachedResp.Header.Get("X-Varied-"+header) {
			glog.V(logVNum).Infof("varyMatches  false header = %v", header)
			return false
		}
	}
	//glog.V(logVNum).Infof("varyMatches  true")
	return true
}
Beispiel #5
0
// Delete removes key from the cache
func (c *MemoryCache) Delete(key string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	delete(c.items, key)
	glog.V(logVNum).Infof("Cache.Delete key = %v", key)
}
Beispiel #6
0
// Set saves response resp to the cache with key
func (c *MemoryCache) Set(key string, resp []byte) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[key] = resp
	glog.V(logVNum).Infof("Cache.Set key = %v", key)
}
Beispiel #7
0
// getFreshness will return one of fresh/stale/transparent based on the cache-control
// values of the request and the response
//
// fresh indicates the response can be returned
// stale indicates that the response needs validating before it is returned
// transparent indicates the response should not be used to fulfil the request
//
// Because this is only a private cache, 'public' and 'private' in cache-control aren't
// signficant. Similarly, smax-age isn't used.
func getFreshness(respHeaders, reqHeaders http.Header) (freshness int) {
	respCacheControl := parseCacheControl(respHeaders)
	reqCacheControl := parseCacheControl(reqHeaders)
	if _, ok := reqCacheControl["no-cache"]; ok {
		glog.V(logVNum).Infof("transparent req no-cache")
		return transparent
	}
	if _, ok := respCacheControl["no-cache"]; ok {
		glog.V(logVNum).Infof("stale resp no-cache")
		return stale
	}
	if _, ok := reqCacheControl["only-if-cached"]; ok {
		//glog.V(logVNum).Infof("fresh req only-if-cached")
		return fresh
	}

	date, err := Date(respHeaders)
	if err != nil {
		glog.V(logVNum).Infof("stale resp Date err = %v", err)
		return stale
	}
	currentAge := clock.since(date)

	var lifetime time.Duration
	var zeroDuration time.Duration

	// If a response includes both an Expires header and a max-age directive,
	// the max-age directive overrides the Expires header, even if the Expires header is more restrictive.
	if maxAge, ok := respCacheControl["max-age"]; ok {
		lifetime, err = time.ParseDuration(maxAge + "s")
		if err != nil {
			glog.V(logVNum).Infof("resp max-age err = %v", err)
			lifetime = zeroDuration
		}
	} else {
		expiresHeader := respHeaders.Get("Expires")
		if expiresHeader != "" {
			expires, err := time.Parse(time.RFC1123, expiresHeader)
			if err != nil {
				glog.V(logVNum).Infof("resp Expires err = %v", err)
				lifetime = zeroDuration
			} else {
				lifetime = expires.Sub(date)
			}
		}
	}
	glog.V(logVNum+1).Infof("resp lifetime = %v, currentAge = %v", lifetime, currentAge)

	if maxAge, ok := reqCacheControl["max-age"]; ok {
		// the client is willing to accept a response whose age is no greater than the specified time in seconds
		lifetime, err = time.ParseDuration(maxAge + "s")
		if err != nil {
			glog.V(logVNum).Infof("req max-age err = %v", err)
			lifetime = zeroDuration
		}
	}
	if minfresh, ok := reqCacheControl["min-fresh"]; ok {
		//  the client wants a response that will still be fresh for at least the specified number of seconds.
		minfreshDuration, err := time.ParseDuration(minfresh + "s")
		if err == nil {
			currentAge = time.Duration(currentAge + minfreshDuration)
			glog.V(logVNum).Infof("req min-fresh, currentAge = %v", currentAge)
		}
	}

	if maxstale, ok := reqCacheControl["max-stale"]; ok {
		// Indicates that the client is willing to accept a response that has exceeded its expiration time.
		// If max-stale is assigned a value, then the client is willing to accept a response that has exceeded
		// its expiration time by no more than the specified number of seconds.
		// If no value is assigned to max-stale, then the client is willing to accept a stale response of any age.
		//
		// Responses served only because of a max-stale value are supposed to have a Warning header added to them,
		// but that seems like a  hassle, and is it actually useful? If so, then there needs to be a different
		// return-value available here.
		if maxstale == "" {
			//glog.V(logVNum).Infof("fresh maxstale == \"\"")
			return fresh
		}
		maxstaleDuration, err := time.ParseDuration(maxstale + "s")
		if err == nil {
			currentAge = time.Duration(currentAge - maxstaleDuration)
			glog.V(logVNum).Infof("req max-stale, currentAge = %v", currentAge)
		}
	}

	glog.V(logVNum+1).Infof("req lifetime = %v, currentAge = %v", lifetime, currentAge)

	if lifetime > currentAge {
		//glog.V(logVNum).Infof("fresh %v > %v", lifetime, currentAge)
		return fresh
	}

	return stale
}
Beispiel #8
0
// RoundTrip takes a Request and returns a Response
//
// If there is a fresh Response already in cache, then it will be returned without connecting to
// the server.
//
// If there is a stale Response, then any validators it contains will be set on the new request
// to give the server a chance to respond with NotModified. If this happens, then the cached Response
// will be returned.
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	cacheKey := cacheKey(req)
	cacheableMethod := req.Method == "GET" || req.Method == "HEAD"
	var cachedResp *http.Response
	if cacheableMethod {
		cachedResp, err = CachedResponse(t.Cache, req)
	} else {
		// Need to invalidate an existing value
		t.Cache.Delete(cacheKey)
	}

	transport := t.Transport
	if transport == nil {
		//john change 2015.11.3
		if true {
			var ok bool
			if transport, ok = http.DefaultTransport.(*http.Transport); !ok {
				return nil, err
			}
		} else {
			//transport = http.DefaultTransport
		}
	}

	if cachedResp != nil && err == nil && cacheableMethod && req.Header.Get("range") == "" {
		if t.MarkCachedResponses {
			cachedResp.Header.Set(XFromCache, "1")
		}

		if varyMatches(cachedResp, req) {
			// Can only use cached value if the new request doesn't Vary significantly
			freshness := getFreshness(cachedResp.Header, req.Header)
			if freshness == fresh {
				glog.V(logVNum).Infof("fresh url = %v", req.URL.String())
				return cachedResp, nil
			}

			if freshness == stale {
				var req2 *http.Request
				// Add validators if caller hasn't already done so
				//glog.V(logVNum).Infof("stale url = %v", req.URL.String())

				etag := cachedResp.Header.Get("etag")
				if etag != "" {
					reqEtag := req.Header.Get("etag")
					if etag == reqEtag {
						return RespNotModified(cachedResp, req)
					}

					reqIfNoneMatch := req.Header.Get("if-none-match")
					if etag == reqIfNoneMatch {
						return RespNotModified(cachedResp, req)
					}

					if reqEtag == "" && reqIfNoneMatch == "" {
						req2 = cloneRequest(req)
						req2.Header.Set("if-none-match", etag)
						glog.V(logVNum).Infof("Header.Set if-none-match : %v", etag)
					}
				}

				lastModified := cachedResp.Header.Get("last-modified")
				if lastModified != "" {
					reqLastModified := req.Header.Get("last-modified")
					if result, err := headerTimeCmp(lastModified, reqLastModified); err == nil {
						if result >= 0 {
							return RespNotModified(cachedResp, req)
						}
					}

					reqIfModifiedSince := req.Header.Get("if-modified-since")
					if result, err := headerTimeCmp(lastModified, reqIfModifiedSince); err == nil {
						if result >= 0 {
							return RespNotModified(cachedResp, req)
						}
					}

					if reqLastModified == "" && reqIfModifiedSince == "" {
						if req2 == nil {
							req2 = cloneRequest(req)
						}
						req2.Header.Set("if-modified-since", lastModified)
						glog.V(logVNum).Infof("Header.Set if-modified-since : %v", lastModified)
					}
				}
				if req2 != nil {
					// Associate original request with cloned request so we can refer to
					// it in CancelRequest()
					//t.setModReq(req, req2) john close 2015.11.4
					req = req2
					/* john close 2015.11.4 b
					defer func() {
						// Release req/clone mapping on error
						if err != nil {
							t.setModReq(req, nil)
						}
						if resp != nil {
							// Release req/clone mapping on body close/EOF
							resp.Body = &onEOFReader{
								rc: resp.Body,
								fn: func() { t.setModReq(req, nil) },
							}
						}
					}()
					john close 2015.11.4 e */
				}
			}
		}

		resp, err = transport.RoundTrip(req)
		glog.V(logVNum).Infof("transport.RoundTrip url = %v", req.URL.String())

		if err == nil && req.Method == "GET" && resp.StatusCode == http.StatusNotModified {
			return resp, nil

			// Replace the 304 response with the one from cache, but update with some new headers
			endToEndHeaders := getEndToEndHeaders(resp.Header)
			glog.V(logVNum).Infof("endToEndHeaders = %v", endToEndHeaders)
			for _, header := range endToEndHeaders {
				cachedResp.Header[header] = resp.Header[header]
			}
			cachedResp.Status = fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))
			cachedResp.StatusCode = http.StatusOK
			resp = cachedResp
		} else if (err != nil || (cachedResp != nil && resp.StatusCode >= 500)) &&
			req.Method == "GET" && canStaleOnError(cachedResp.Header, req.Header) {
			// In case of transport failure and stale-if-error activated, returns cached content
			// when available
			cachedResp.Status = fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))
			cachedResp.StatusCode = http.StatusOK
			return cachedResp, nil
		} else {
			if err != nil || resp.StatusCode != http.StatusOK {
				t.Cache.Delete(cacheKey)
			}
			if err != nil {
				glog.Errorf("err = %v", err)
				return nil, err
			}
		}
	} else {
		reqCacheControl := parseCacheControl(req.Header)
		if _, ok := reqCacheControl["only-if-cached"]; ok {
			resp = newGatewayTimeoutResponse(req)
		} else {
			resp, err = transport.RoundTrip(req)
			if err != nil {
				glog.Errorf("transport.RoundTrip err = %v", err)
				return nil, err
			}
		}
	}

	reqCacheControl := parseCacheControl(req.Header)
	respCacheControl := parseCacheControl(resp.Header)

	glog.V(logVNum+1).Infof("url = %v, reqCacheControl = %v, respCacheControl = %v", req.URL.String(), reqCacheControl, respCacheControl)

	if canStore(reqCacheControl, respCacheControl) {
		for _, varyKey := range headerAllCommaSepValues(resp.Header, "vary") {
			varyKey = http.CanonicalHeaderKey(varyKey)
			fakeHeader := "X-Varied-" + varyKey
			reqValue := req.Header.Get(varyKey)
			if reqValue != "" {
				resp.Header.Set(fakeHeader, reqValue)
				//glog.V(logVNum).Infof("Header.Set fakeHeader = %v, reqValue = %v", fakeHeader, reqValue)
			}
		}
		respBytes, err := httputil.DumpResponse(resp, true)
		if err == nil {
			t.Cache.Set(cacheKey, respBytes)
		}
	} else {
		t.Cache.Delete(cacheKey)
	}
	return resp, nil
}