コード例 #1
0
/*
 * select a specific URL entity in the cache
 *
 * It is possible to store more than one entity per URL. Content
 * negotiation is used to select an entity. Once an entity is
 * selected, details of it are stored in the per request
 * config to save time when serving the request later.
 *
 * This function returns OK if successful, DECLINED if no
 * cached entity fits the bill.
 */
int cache_select(cache_request_rec *cache, request_rec *r)
{
    cache_provider_list *list;
    apr_status_t rv;
    cache_handle_t *h;

    if (!cache) {
        /* This should never happen */
        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00693)
                "cache: No cache request information available for key"
                " generation");
        return DECLINED;
    }

    /* if no-cache, we can't serve from the cache, but we may store to the
     * cache.
     */
    if (!ap_cache_check_no_cache(cache, r)) {
        return DECLINED;
    }

    if (!cache->key) {
        rv = cache_generate_key(r, r->pool, &cache->key);
        if (rv != APR_SUCCESS) {
            return DECLINED;
        }
    }

    /* go through the cache types till we get a match */
    h = apr_palloc(r->pool, sizeof(cache_handle_t));

    list = cache->providers;

    while (list) {
        switch ((rv = list->provider->open_entity(h, r, cache->key))) {
        case OK: {
            char *vary = NULL;
            int mismatch = 0;
            char *last = NULL;

            if (list->provider->recall_headers(h, r) != APR_SUCCESS) {
                /* try again with next cache type */
                list = list->next;
                continue;
            }

            /*
             * Check Content-Negotiation - Vary
             *
             * At this point we need to make sure that the object we found in
             * the cache is the same object that would be delivered to the
             * client, when the effects of content negotiation are taken into
             * effect.
             *
             * In plain english, we want to make sure that a language-negotiated
             * document in one language is not given to a client asking for a
             * language negotiated document in a different language by mistake.
             *
             * This code makes the assumption that the storage manager will
             * cache the req_hdrs if the response contains a Vary
             * header.
             *
             * RFC2616 13.6 and 14.44 describe the Vary mechanism.
             */
            vary = cache_strqtok(
                    apr_pstrdup(r->pool,
                            cache_table_getm(r->pool, h->resp_hdrs, "Vary")),
                    CACHE_SEPARATOR, &last);
            while (vary) {
                const char *h1, *h2;

                /*
                 * is this header in the request and the header in the cached
                 * request identical? If not, we give up and do a straight get
                 */
                h1 = cache_table_getm(r->pool, r->headers_in, vary);
                h2 = cache_table_getm(r->pool, h->req_hdrs, vary);
                if (h1 == h2) {
                    /* both headers NULL, so a match - do nothing */
                }
                else if (h1 && h2 && !strcmp(h1, h2)) {
                    /* both headers exist and are equal - do nothing */
                }
                else {
                    /* headers do not match, so Vary failed */
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
                            r, APLOGNO(00694) "cache_select(): Vary header mismatch.");
                    mismatch = 1;
                    break;
                }
                vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
            }

            /* no vary match, try next provider */
            if (mismatch) {
                /* try again with next cache type */
                list = list->next;
                continue;
            }

            cache->provider = list->provider;
            cache->provider_name = list->provider_name;

            /*
             * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified
             * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request
             * that includes both a Last-Modified date and one or more entity tags as
             * cache validators, MUST NOT return a locally cached response to the
             * client unless that cached response is consistent with all of the
             * conditional header fields in the request.
             */
            if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH
                    || ap_condition_if_unmodified_since(r, h->resp_hdrs)
                            == AP_CONDITION_NOMATCH
                    || ap_condition_if_none_match(r, h->resp_hdrs)
                            == AP_CONDITION_NOMATCH
                    || ap_condition_if_modified_since(r, h->resp_hdrs)
                            == AP_CONDITION_NOMATCH
                    || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) {
                mismatch = 1;
            }

            /* Is our cached response fresh enough? */
            if (mismatch || !cache_check_freshness(h, cache, r)) {
                const char *etag, *lastmod;

                /* Cache-Control: only-if-cached and revalidation required, try
                 * the next provider
                 */
                if (cache->control_in.only_if_cached) {
                    /* try again with next cache type */
                    list = list->next;
                    continue;
                }

                /* set aside the stale entry for accessing later */
                cache->stale_headers = apr_table_copy(r->pool,
                        r->headers_in);
                cache->stale_handle = h;

                /* if no existing conditionals, use conditionals of our own */
                if (!mismatch) {

                    ap_log_rerror(
                            APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding "
                            "conditional request headers.", r->uri);

                    /* Remove existing conditionals that might conflict with ours */
                    apr_table_unset(r->headers_in, "If-Match");
                    apr_table_unset(r->headers_in, "If-Modified-Since");
                    apr_table_unset(r->headers_in, "If-None-Match");
                    apr_table_unset(r->headers_in, "If-Range");
                    apr_table_unset(r->headers_in, "If-Unmodified-Since");

                    etag = apr_table_get(h->resp_hdrs, "ETag");
                    lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");

                    if (etag || lastmod) {
                        /* If we have a cached etag and/or Last-Modified add in
                         * our own conditionals.
                         */

                        if (etag) {
                            apr_table_set(r->headers_in, "If-None-Match", etag);
                        }

                        if (lastmod) {
                            apr_table_set(r->headers_in, "If-Modified-Since",
                                    lastmod);
                        }

                        /*
                         * Do not do Range requests with our own conditionals: If
                         * we get 304 the Range does not matter and otherwise the
                         * entity changed and we want to have the complete entity
                         */
                        apr_table_unset(r->headers_in, "Range");

                    }

                }

                /* ready to revalidate, pretend we were never here */
                return DECLINED;
            }

            /* Okay, this response looks okay.  Merge in our stuff and go. */
            cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 0);

            cache->handle = h;
            return OK;
        }
        case DECLINED: {
            /* try again with next cache type */
            list = list->next;
            continue;
        }
        default: {
            /* oo-er! an error */
            return rv;
        }
        }
    }

    /* if Cache-Control: only-if-cached, and not cached, return 504 */
    if (cache->control_in.only_if_cached) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00696)
                "cache: 'only-if-cached' requested and no cached entity, "
                "returning 504 Gateway Timeout for: %s", r->uri);
        return HTTP_GATEWAY_TIME_OUT;
    }

    return DECLINED;
}
コード例 #2
0
/**
 * Parse the Cache-Control and Pragma headers in one go, marking
 * which tokens appear within the header. Populate the structure
 * passed in.
 */
int ap_cache_control(request_rec *r, cache_control_t *cc,
        const char *cc_header, const char *pragma_header, apr_table_t *headers)
{
    char *last;

    if (cc->parsed) {
        return cc->cache_control || cc->pragma;
    }

    cc->parsed = 1;
    cc->max_age_value = -1;
    cc->max_stale_value = -1;
    cc->min_fresh_value = -1;
    cc->s_maxage_value = -1;

    if (pragma_header) {
        char *header = apr_pstrdup(r->pool, pragma_header);
        const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last);
        while (token) {
            /* handle most common quickest case... */
            if (!strcmp(token, "no-cache")) {
                cc->no_cache = 1;
            }
            /* ...then try slowest case */
            else if (!strcasecmp(token, "no-cache")) {
                cc->no_cache = 1;
            }
            token = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
        }
        cc->pragma = 1;
    }

    if (cc_header) {
        char *endp;
        apr_off_t offt;
        char *header = apr_pstrdup(r->pool, cc_header);
        const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last);
        while (token) {
            switch (token[0]) {
            case 'n':
            case 'N': {
                /* handle most common quickest cases... */
                if (!strcmp(token, "no-cache")) {
                    cc->no_cache = 1;
                }
                else if (!strcmp(token, "no-store")) {
                    cc->no_store = 1;
                }
                /* ...then try slowest cases */
                else if (!strncasecmp(token, "no-cache", 8)) {
                    if (token[8] == '=') {
                        cc->no_cache_header = 1;
                    }
                    else if (!token[8]) {
                        cc->no_cache = 1;
                    }
                    break;
                }
                else if (!strcasecmp(token, "no-store")) {
                    cc->no_store = 1;
                }
                else if (!strcasecmp(token, "no-transform")) {
                    cc->no_transform = 1;
                }
                break;
            }
            case 'm':
            case 'M': {
                /* handle most common quickest cases... */
                if (!strcmp(token, "max-age=0")) {
                    cc->max_age = 1;
                    cc->max_age_value = 0;
                }
                else if (!strcmp(token, "must-revalidate")) {
                    cc->must_revalidate = 1;
                }
                /* ...then try slowest cases */
                else if (!strncasecmp(token, "max-age", 7)) {
                    if (token[7] == '='
                            && !apr_strtoff(&offt, token + 8, &endp, 10)
                            && endp > token + 8 && !*endp) {
                        cc->max_age = 1;
                        cc->max_age_value = offt;
                    }
                    break;
                }
                else if (!strncasecmp(token, "max-stale", 9)) {
                    if (token[9] == '='
                            && !apr_strtoff(&offt, token + 10, &endp, 10)
                            && endp > token + 10 && !*endp) {
                        cc->max_stale = 1;
                        cc->max_stale_value = offt;
                    }
                    else if (!token[9]) {
                        cc->max_stale = 1;
                        cc->max_stale_value = -1;
                    }
                    break;
                }
                else if (!strncasecmp(token, "min-fresh", 9)) {
                    if (token[9] == '='
                            && !apr_strtoff(&offt, token + 10, &endp, 10)
                            && endp > token + 10 && !*endp) {
                        cc->min_fresh = 1;
                        cc->min_fresh_value = offt;
                    }
                    break;
                }
                else if (!strcasecmp(token, "must-revalidate")) {
                    cc->must_revalidate = 1;
                }
                break;
            }
            case 'o':
            case 'O': {
                if (!strcasecmp(token, "only-if-cached")) {
                    cc->only_if_cached = 1;
                }
                break;
            }
            case 'p':
            case 'P': {
                /* handle most common quickest cases... */
                if (!strcmp(token, "private")) {
                    cc->private = 1;
                }
                /* ...then try slowest cases */
                else if (!strcasecmp(token, "public")) {
                    cc->public = 1;
                }
                else if (!strncasecmp(token, "private", 7)) {
                    if (token[7] == '=') {
                        cc->private_header = 1;
                    }
                    else if (!token[7]) {
                        cc->private = 1;
                    }
                    break;
                }