package org.glassfish.grizzly.http.server;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.ReadHandler;
import org.glassfish.grizzly.attributes.Attribute;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.filterchain.ShutdownEvent;
import org.glassfish.grizzly.http.HttpContent;
import org.glassfish.grizzly.http.HttpContext;
import org.glassfish.grizzly.http.HttpPacket;
import org.glassfish.grizzly.http.HttpRequestPacket;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.Method;
import org.glassfish.grizzly.http.server.util.HtmlHelper;
import org.glassfish.grizzly.http.util.HttpStatus;
import org.glassfish.grizzly.impl.FutureImpl;
import org.glassfish.grizzly.utils.DelayedExecutor;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.filterchain.FilterChainEvent;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.localization.LogMessages;
import org.glassfish.grizzly.monitoring.DefaultMonitoringConfig;
import org.glassfish.grizzly.monitoring.MonitoringAware;
import org.glassfish.grizzly.monitoring.MonitoringConfig;
import org.glassfish.grizzly.monitoring.MonitoringUtils;
import org.glassfish.grizzly.utils.Futures;
public class HttpServerFilter extends BaseFilter
implements MonitoringAware<HttpServerProbe> {
private final static Logger LOGGER = Grizzly.logger(HttpHandler.class);
private final FlushResponseHandler flushResponseHandler =
new FlushResponseHandler();
private final Attribute<Request> httpRequestInProgress;
private final DelayedExecutor.DelayQueue<Response.SuspendTimeout> suspendedResponseQueue;
private volatile HttpHandler httpHandler;
private final ServerFilterConfiguration config;
private AtomicBoolean shuttingDown = new AtomicBoolean();
private AtomicReference<FutureImpl<HttpServerFilter>> shutdownCompletionFuture;
private final AtomicInteger activeRequestsCounter = new AtomicInteger();
protected final DefaultMonitoringConfig<HttpServerProbe> monitoringConfig =
new DefaultMonitoringConfig<HttpServerProbe>(HttpServerProbe.class) {
@Override
public Object createManagementObject() {
return createJmxManagementObject();
}
};
public HttpServerFilter(final ServerFilterConfiguration config,
final DelayedExecutor delayedExecutor) {
this.config = config;
suspendedResponseQueue = Response.createDelayQueue(delayedExecutor);
httpRequestInProgress = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.
createAttribute("HttpServerFilter.Request");
}
@SuppressWarnings({"UnusedDeclaration"})
public HttpHandler getHttpHandler() {
return httpHandler;
}
public void setHttpHandler(final HttpHandler httpHandler) {
this.httpHandler = httpHandler;
}
public ServerFilterConfiguration getConfiguration() {
return config;
}
@SuppressWarnings({"unchecked", "ReturnInsideFinallyBlock"})
@Override
public NextAction handleRead(final FilterChainContext ctx)
throws IOException {
assert HttpContext.get(ctx) != null;
final Object message = ctx.getMessage();
final Connection connection = ctx.getConnection();
if (HttpPacket.isHttp(message)) {
final HttpContent httpContent = (HttpContent) message;
final HttpContext context = httpContent.getHttpHeader()
.getProcessingState().getHttpContext();
Request handlerRequest = httpRequestInProgress.get(context);
if (handlerRequest == null) {
final HttpRequestPacket request = (HttpRequestPacket) httpContent.getHttpHeader();
final HttpResponsePacket response = request.getResponse();
handlerRequest = Request.create();
handlerRequest.parameters.setLimit(config.getMaxRequestParameters());
httpRequestInProgress.set(context, handlerRequest);
final Response handlerResponse = handlerRequest.getResponse();
handlerRequest.initialize(request, ctx, this);
handlerResponse.initialize(handlerRequest, response,
ctx, suspendedResponseQueue, this);
if (config.isGracefulShutdownSupported()) {
activeRequestsCounter.incrementAndGet();
handlerRequest.addAfterServiceListener(flushResponseHandler);
}
HttpServerProbeNotifier.notifyRequestReceive(this, connection,
handlerRequest);
boolean wasSuspended = false;
try {
ctx.setMessage(handlerResponse);
if (shuttingDown.get()) {
handlerResponse.getResponse().getProcessingState().setError(true);
HtmlHelper.setErrorAndSendErrorPage(
handlerRequest, handlerResponse,
config.getDefaultErrorPageGenerator(),
503, HttpStatus.SERVICE_UNAVAILABLE_503.getReasonPhrase(),
"The server is being shutting down...", null);
} else if (!config.isPassTraceRequest()
&& request.getMethod() == Method.TRACE) {
onTraceRequest(handlerRequest, handlerResponse);
} else if (!checkMaxPostSize(request.getContentLength())) {
handlerResponse.getResponse().getProcessingState().setError(true);
HtmlHelper.setErrorAndSendErrorPage(
handlerRequest, handlerResponse,
config.getDefaultErrorPageGenerator(),
413, HttpStatus.REQUEST_ENTITY_TOO_LARGE_413.getReasonPhrase(),
"The request payload size exceeds the max post size limitation", null);
} else {
final HttpHandler httpHandlerLocal = httpHandler;
if (httpHandlerLocal != null) {
wasSuspended = !httpHandlerLocal.doHandle(
handlerRequest, handlerResponse);
}
}
} catch (Exception t) {
LOGGER.log(Level.WARNING,
LogMessages.WARNING_GRIZZLY_HTTP_SERVER_FILTER_HTTPHANDLER_INVOCATION_ERROR(), t);
request.getProcessingState().setError(true);
if (!response.isCommitted()) {
HtmlHelper.setErrorAndSendErrorPage(
handlerRequest, handlerResponse,
config.getDefaultErrorPageGenerator(),
500, HttpStatus.INTERNAL_SERVER_ERROR_500.getReasonPhrase(),
HttpStatus.INTERNAL_SERVER_ERROR_500.getReasonPhrase(),
t);
}
} catch (Throwable t) {
LOGGER.log(Level.WARNING,
LogMessages.WARNING_GRIZZLY_HTTP_SERVER_FILTER_UNEXPECTED(), t);
throw new IllegalStateException(t);
}
if (!wasSuspended) {
return afterService(ctx, connection,
handlerRequest, handlerResponse);
} else {
return ctx.getSuspendAction();
}
} else {
try {
ctx.suspend();
final NextAction action = ctx.getSuspendAction();
if (!handlerRequest.getInputBuffer().append(httpContent)) {
ctx.completeAndRecycle();
} else {
ctx.resume(ctx.getStopAction());
}
return action;
} finally {
httpContent.recycle();
}
}
} else {
final Response response = (Response) message;
final Request request = response.getRequest();
return afterService(ctx, connection, request, response);
}
}
@Override
public void exceptionOccurred(final FilterChainContext ctx,
final Throwable error) {
final HttpContext context = HttpContext.get(ctx);
if (context != null) {
final Request request = httpRequestInProgress.get(context);
if (request != null) {
final ReadHandler handler = request.getInputBuffer().getReadHandler();
if (handler != null) {
handler.onError(error);
}
}
}
}
@Override
public NextAction handleEvent(final FilterChainContext ctx, final FilterChainEvent event) throws IOException {
if (event.type() == ShutdownEvent.TYPE) {
if (shuttingDown.compareAndSet(false, true)) {
final ShutdownEvent shutDownEvent = (ShutdownEvent) event;
final FutureImpl<HttpServerFilter> future = Futures.createSafeFuture();
shutDownEvent.addShutdownTask(new Callable<Filter>() {
@Override
public Filter call() throws Exception {
return future.get();
}
});
shutdownCompletionFuture = new AtomicReference<>(future);
if (activeRequestsCounter.get() == 0) {
future.result(this);
}
}
}
return ctx.getInvokeAction();
}
@Override
public MonitoringConfig<HttpServerProbe> getMonitoringConfig() {
return monitoringConfig;
}
protected Object createJmxManagementObject() {
return MonitoringUtils.loadJmxObject(
"org.glassfish.grizzly.http.server.jmx.HttpServerFilter",
this, HttpServerFilter.class);
}
protected void onTraceRequest(final Request request,
final Response response) throws IOException {
if (config.isTraceEnabled()) {
HtmlHelper.writeTraceMessage(request, response);
} else {
response.setStatus(HttpStatus.METHOD_NOT_ALLOWED_405);
response.setHeader(Header.Allow, "POST, GET, DELETE, OPTIONS, PUT, HEAD");
}
}
protected String getFullServerName() {
return config.getHttpServerName() + " " + config.getHttpServerVersion();
}
private NextAction afterService(
final FilterChainContext ctx,
final Connection connection,
final Request request,
final Response response)
throws IOException {
final HttpContext context = request.getRequest()
.getProcessingState().getHttpContext();
httpRequestInProgress.remove(context);
response.finish();
request.onAfterService();
HttpServerProbeNotifier.notifyRequestComplete(this, connection, response);
final HttpRequestPacket httpRequest = request.getRequest();
final boolean isBroken = httpRequest.isContentBroken();
if (response.suspendState != Response.SuspendState.CANCELLED) {
response.recycle();
request.recycle();
}
if (isBroken) {
final NextAction suspendNextAction = ctx.getSuspendAction();
ctx.completeAndRecycle();
return suspendNextAction;
}
return ctx.getStopAction();
}
private void onRequestCompleteAndResponseFlushed() {
final int count = activeRequestsCounter.decrementAndGet();
if (count == 0 && shuttingDown.get()) {
final FutureImpl<HttpServerFilter> shutdownFuture =
shutdownCompletionFuture != null
? shutdownCompletionFuture.getAndSet(null)
: null;
if (shutdownFuture != null) {
shutdownFuture.result(this);
}
}
}
private boolean checkMaxPostSize(final long requestContentLength) {
final long maxPostSize = config.getMaxPostSize();
return requestContentLength <= 0 || maxPostSize < 0 ||
maxPostSize >= requestContentLength;
}
private final class FlushResponseHandler
extends EmptyCompletionHandler<Object>
implements AfterServiceListener{
private final FilterChainEvent event = TransportFilter.createFlushEvent(this);
@Override
public void cancelled() {
onRequestCompleteAndResponseFlushed();
}
@Override
public void failed(final Throwable throwable) {
onRequestCompleteAndResponseFlushed();
}
@Override
public void completed(final Object result) {
onRequestCompleteAndResponseFlushed();
}
@Override
public void onAfterService(final Request request) {
request.getContext().notifyDownstream(event);
}
}
}