All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.server.handlers.cache.ResponseCache Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.server.handlers.cache;

import java.io.IOException;
import java.nio.ByteBuffer;

import io.undertow.UndertowLogger;
import io.undertow.io.IoCallback;
import io.undertow.io.Sender;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import io.undertow.util.DateUtils;
import io.undertow.util.ETag;
import io.undertow.util.ETagUtils;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.StatusCodes;

import static io.undertow.util.Methods.GET;
import static io.undertow.util.Methods.HEAD;

/**
 * Facade for an underlying buffer cache that contains response information.
 * 

* This facade is attached to the exchange and provides a mechanism for handlers to * serve cached content. By default a request to serve cached content is interpreted * to mean that the resulting response is cacheable, and so by default this will result * in the current response being cached (as long as it meets the criteria for caching). *

* Calling tryServeResponse can also result in the exchange being ended with a not modified * response code, if the response headers indicate that this is justified (e.g. if the * If-Modified-Since or If-None-Match headers indicate that the client has a cached copy * of the response) *

* This should be installed early in the handler chain, before any content encoding handlers. * This allows it to cache compressed copies of the response, which can significantly reduce * CPU load. *

* NOTE: This cache has no concept of authentication, it assumes that if the underlying handler * indicates that a response is cachable, then the current user has been properly authenticated * to access that resource, and that the resource will not change per user. * * @author Stuart Douglas */ public class ResponseCache { public static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(ResponseCache.class); private final DirectBufferCache cache; private final HttpServerExchange exchange; private boolean responseCachable; public ResponseCache(final DirectBufferCache cache, final HttpServerExchange exchange) { this.cache = cache; this.exchange = exchange; } /** * Attempts to serve the response from a cache. *

* If this fails, then the response will be considered cachable, and may be cached * to be served by future handlers. *

* If this returns true then the caller should not modify the exchange any more, as this * can result in a handoff to an IO thread * * @return true if serving succeeded, */ public boolean tryServeResponse() { return tryServeResponse(true); } /** * Attempts to serve the response from a cache. *

* If this fails, and the markCachable parameter is true then the response will be considered cachable, * and may be cached to be served by future handlers. *

* If this returns true then the caller should not modify the exchange any more, as this * can result in a handoff to an IO thread * * @param markCacheable If this is true then the resulting response will be considered cachable * @return true if serving succeeded, */ public boolean tryServeResponse(boolean markCacheable) { final CachedHttpRequest key = new CachedHttpRequest(exchange); DirectBufferCache.CacheEntry entry = cache.get(key); //we only cache get and head requests if (!exchange.getRequestMethod().equals(GET) && !exchange.getRequestMethod().equals(HEAD)) { return false; } if (entry == null) { this.responseCachable = markCacheable; return false; } // It's loading retry later if (!entry.enabled() || !entry.reference()) { this.responseCachable = markCacheable; return false; } CachedHttpRequest existingKey = (CachedHttpRequest) entry.key(); //if any of the header matches fail we just return //we don't can the request, as it is possible the underlying handler //may have additional etags final ETag etag = existingKey.getEtag(); if (!ETagUtils.handleIfMatch(exchange, etag, false)) { return false; } //we do send a 304 if the if-none-match header matches if (!ETagUtils.handleIfNoneMatch(exchange, etag, true)) { exchange.setStatusCode(StatusCodes.NOT_MODIFIED); exchange.endExchange(); return true; } //the server may have a more up to date representation if (!DateUtils.handleIfUnmodifiedSince(exchange, existingKey.getLastModified())) { return false; } if (!DateUtils.handleIfModifiedSince(exchange, existingKey.getLastModified())) { exchange.setStatusCode(StatusCodes.NOT_MODIFIED); exchange.endExchange(); return true; } //we are going to proceed. Set the appropriate headers if(existingKey.getContentType() != null) { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, existingKey.getContentType()); } if(existingKey.getContentEncoding() != null && !Headers.IDENTITY.equals(HttpString.tryFromString(existingKey.getContentEncoding()))) { exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, existingKey.getContentEncoding()); } if(existingKey.getLastModified() != null) { exchange.getResponseHeaders().put(Headers.LAST_MODIFIED, DateUtils.toDateString(existingKey.getLastModified())); } if(existingKey.getContentLocation() != null) { exchange.getResponseHeaders().put(Headers.CONTENT_LOCATION, existingKey.getContentLocation()); } if(existingKey.getLanguage() != null) { exchange.getResponseHeaders().put(Headers.CONTENT_LANGUAGE, existingKey.getLanguage()); } if(etag != null) { exchange.getResponseHeaders().put(Headers.CONTENT_LANGUAGE, etag.toString()); } //TODO: support if-range exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Long.toString(entry.size())); if (exchange.getRequestMethod().equals(HEAD)) { exchange.endExchange(); return true; } final ByteBuffer[] buffers; boolean ok = false; try { LimitedBufferSlicePool.PooledByteBuffer[] pooled = entry.buffers(); buffers = new ByteBuffer[pooled.length]; for (int i = 0; i < buffers.length; i++) { // Keep position from mutating buffers[i] = pooled[i].getBuffer().duplicate(); } ok = true; } finally { if (!ok) { entry.dereference(); } } // Transfer Inline, or register and continue transfer // Pass off the entry dereference call to the listener exchange.getResponseSender().send(buffers, new DereferenceCallback(entry)); return true; } boolean isResponseCachable() { return responseCachable; } private static class DereferenceCallback implements IoCallback { private final DirectBufferCache.CacheEntry entry; DereferenceCallback(DirectBufferCache.CacheEntry entry) { this.entry = entry; } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { entry.dereference(); exchange.endExchange(); } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); entry.dereference(); exchange.endExchange(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy