/*
* Copyright 2016 LINE Corporation
*
* LINE Corporation licenses this file to you 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 com.linecorp.armeria.common;
import java.net.SocketAddress;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.net.ssl.SSLSession;
import org.slf4j.LoggerFactory;
import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.common.logging.RequestLog;
import com.linecorp.armeria.common.logging.RequestLogBuilder;
import com.linecorp.armeria.common.util.Exceptions;
import com.linecorp.armeria.common.util.SafeCloseable;
import com.linecorp.armeria.server.ServiceRequestContext;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.util.Attribute;
import io.netty.util.AttributeMap;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Promise;
/**
* Provides information about a {@link Request}, its {@link Response} and related utilities.
* A server-side {@link Request} has a {@link ServiceRequestContext} and
* a client-side {@link Request} has a {@link ClientRequestContext}.
*/
public interface RequestContext extends AttributeMap {
/**
* Returns the context of the {@link Request} that is being handled in the current thread.
*
* @throws IllegalStateException if the context is unavailable in the current thread
*/
static <T extends RequestContext> T current() {
final T ctx = RequestContextThreadLocal.get();
if (ctx == null) {
throw new IllegalStateException(RequestContext.class.getSimpleName() + " unavailable");
}
return ctx;
}
/**
* Maps the context of the {@link Request} that is being handled in the current thread.
*
* @param mapper the {@link Function} that maps the {@link RequestContext}
* @param defaultValueSupplier the {@link Supplier} that provides the value when the context is unavailable
* in the current thread. If {@code null}, the {@code null} will be returned
* when the context is unavailable in the current thread.
*/
static <T> T mapCurrent(
Function<? super RequestContext, T> mapper, @Nullable Supplier<T> defaultValueSupplier) {
final RequestContext ctx = RequestContextThreadLocal.get();
if (ctx != null) {
return mapper.apply(ctx);
}
if (defaultValueSupplier != null) {
return defaultValueSupplier.get();
}
return null;
}
/**
* Pushes the specified context to the thread-local stack. To pop the context from the stack, call
* {@link SafeCloseable#close()}, which can be done using a {@code try-finally} block:
* <pre>{@code
* try (SafeCloseable ignored = RequestContext.push(ctx)) {
* ...
* }
* }</pre>
*
* <p>The callbacks added by {@link #onEnter(Consumer)} and {@link #onExit(Consumer)} will be invoked
* when the context is pushed to and removed from the thread-local stack respectively.
*
* <p>NOTE: In case of re-entrance, the callbacks will never run.
*/
static SafeCloseable push(RequestContext ctx) {
return push(ctx, true);
}
/**
* Pushes the specified context to the thread-local stack. To pop the context from the stack, call
* {@link SafeCloseable#close()}, which can be done using a {@code try-finally} block:
* <pre>{@code
* try (PushHandle ignored = RequestContext.push(ctx, true)) {
* ...
* }
* }</pre>
*
* <p>NOTE: This method is only useful when it is undesirable to invoke the callbacks, such as replacing
* the current context with another. Prefer {@link #push(RequestContext)} otherwise.
*
* @param runCallbacks if {@code true}, the callbacks added by {@link #onEnter(Consumer)} and
* {@link #onExit(Consumer)} will be invoked when the context is pushed to and
* removed from the thread-local stack respectively.
* If {@code false}, no callbacks will be executed.
* NOTE: In case of re-entrance, the callbacks will never run.
*/
static SafeCloseable push(RequestContext ctx, boolean runCallbacks) {
final RequestContext oldCtx = RequestContextThreadLocal.getAndSet(ctx);
if (oldCtx == ctx) {
// Reentrance
return () -> { /* no-op */ };
}
if (runCallbacks) {
if (oldCtx != null) {
oldCtx.invokeOnChildCallbacks(ctx);
ctx.invokeOnEnterCallbacks();
return () -> {
ctx.invokeOnExitCallbacks();
RequestContextThreadLocal.set(oldCtx);
};
} else {
ctx.invokeOnEnterCallbacks();
return () -> {
ctx.invokeOnExitCallbacks();
RequestContextThreadLocal.remove();
};
}
} else {
if (oldCtx != null) {
return () -> RequestContextThreadLocal.set(oldCtx);
} else {
return RequestContextThreadLocal::remove;
}
}
}
/**
* Returns the {@link SessionProtocol} of the current {@link Request}.
*/
SessionProtocol sessionProtocol();
/**
* Returns the remote address of this request, or {@code null} if the connection is not established yet.
*/
@Nullable
<A extends SocketAddress> A remoteAddress();
/**
* Returns the local address of this request, or {@code null} if the connection is not established yet.
*/
@Nullable
<A extends SocketAddress> A localAddress();
/**
* The {@link SSLSession} for this request if the connection is made over TLS, or {@code null} if
* the connection is not established yet or the connection is not a TLS connection.
*/
@Nullable
SSLSession sslSession();
/**
* Returns the session-layer method name of the current {@link Request}. e.g. "GET" or "POST" for HTTP
*/
String method();
/**
* Returns the absolute path part of the current {@link Request}, as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2">the
* section 5.1.2 of RFC2616</a>.
*/
String path();
/**
* Returns the {@link Request} associated with this context.
*/
<T> T request();
/**
* Returns the {@link RequestLog} that contains the information about the current {@link Request}.
*/
RequestLog log();
/**
* Returns the {@link RequestLogBuilder} that collects the information about the current {@link Request}.
*/
RequestLogBuilder logBuilder();
/**
* Returns all {@link Attribute}s set in this context.
*/
Iterator<Attribute<?>> attrs();
/**
* Returns the {@link EventLoop} that is handling the current {@link Request}.
*/
EventLoop eventLoop();
/**
* Returns the {@link ByteBufAllocator} for this {@link RequestContext}. Any buffers created by this
* {@link ByteBufAllocator} must be
* <a href="http://netty.io/wiki/reference-counted-objects.html">reference-counted</a>. If you don't know
* what this means, you should probably use {@code byte[]} or {@link java.nio.ByteBuffer} directly instead
* of calling this method.
*/
default ByteBufAllocator alloc() {
throw new UnsupportedOperationException("No ByteBufAllocator available for this RequestContext.");
}
/**
* Returns an {@link EventLoop} that will make sure this {@link RequestContext} is set as the current
* context before executing any callback. This should almost always be used for executing asynchronous
* callbacks in service code to make sure features that require the {@link RequestContext} work properly.
* Most asynchronous libraries like {@link CompletableFuture} provide methods that accept an
* {@link Executor} to run callbacks on.
*/
default EventLoop contextAwareEventLoop() {
return new RequestContextAwareEventLoop(this, eventLoop());
}
/**
* Returns an {@link Executor} that will execute callbacks in the given {@code executor}, making sure to
* propagate the current {@link RequestContext} into the callback execution. It is generally preferred to
* use {@link #contextAwareEventLoop()} to ensure the callback stays on the same thread as well.
*/
default Executor makeContextAware(Executor executor) {
return runnable -> executor.execute(makeContextAware(runnable));
}
/**
* Returns an {@link ExecutorService} that will execute callbacks in the given {@code executor}, making
* sure to propagate the current {@link RequestContext} into the callback execution.
*/
default ExecutorService makeContextAware(ExecutorService executor) {
return new RequestContextAwareExecutorService(this, executor);
}
/**
* Returns a {@link Callable} that makes sure the current {@link RequestContext} is set and then invokes
* the input {@code callable}.
*/
<T> Callable<T> makeContextAware(Callable<T> callable);
/**
* Returns a {@link Runnable} that makes sure the current {@link RequestContext} is set and then invokes
* the input {@code runnable}.
*/
Runnable makeContextAware(Runnable runnable);
/**
* Returns a {@link Function} that makes sure the current {@link RequestContext} is set and then invokes
* the input {@code function}.
*/
<T, R> Function<T, R> makeContextAware(Function<T, R> function);
/**
* Returns a {@link BiFunction} that makes sure the current {@link RequestContext} is set and then invokes
* the input {@code function}.
*/
<T, U, V> BiFunction<T, U, V> makeContextAware(BiFunction<T, U, V> function);
/**
* Returns a {@link Consumer} that makes sure the current {@link RequestContext} is set and then invokes
* the input {@code action}.
*/
<T> Consumer<T> makeContextAware(Consumer<T> action);
/**
* Returns a {@link BiConsumer} that makes sure the current {@link RequestContext} is set and then invokes
* the input {@code action}.
*/
<T, U> BiConsumer<T, U> makeContextAware(BiConsumer<T, U> action);
/**
* Returns a {@link FutureListener} that makes sure the current {@link RequestContext} is set and then
* invokes the input {@code listener}.
*
* @deprecated Use {@link CompletableFuture} instead.
*/
@Deprecated
<T> FutureListener<T> makeContextAware(FutureListener<T> listener);
/**
* Returns a {@link ChannelFutureListener} that makes sure the current {@link RequestContext} is set and
* then invokes the input {@code listener}.
*
* @deprecated Use {@link CompletableFuture} instead.
*/
@Deprecated
ChannelFutureListener makeContextAware(ChannelFutureListener listener);
/**
* Returns a {@link GenericFutureListener} that makes sure the current {@link RequestContext} is set and
* then invokes the input {@code listener}. Unlike other versions of {@code makeContextAware}, this one will
* invoke the listener with the future's result even if the context has already been timed out.
* @deprecated Use {@link CompletableFuture} instead.
*/
@Deprecated
<T extends Future<?>> GenericFutureListener<T> makeContextAware(GenericFutureListener<T> listener);
/**
* Returns a {@link CompletionStage} that makes sure the current {@link CompletionStage} is set and
* then invokes the input {@code stage}.
*/
<T> CompletionStage<T> makeContextAware(CompletionStage<T> stage);
/**
* Returns a {@link CompletableFuture} that makes sure the current {@link CompletableFuture} is set and
* then invokes the input {@code future}.
*/
default <T> CompletableFuture<T> makeContextAware(CompletableFuture<T> future) {
return makeContextAware((CompletionStage<T>) future).toCompletableFuture();
}
/**
* Registers {@code callback} to be run when re-entering this {@link RequestContext}, usually when using
* the {@link #makeContextAware} family of methods. Any thread-local state associated with this context
* should be restored by this callback.
*
* @param callback a {@link Consumer} whose argument is this context
*/
void onEnter(Consumer<? super RequestContext> callback);
/**
* @deprecated Use {@link #onEnter(Consumer)} instead.
*/
@Deprecated
default void onEnter(Runnable callback) {
onEnter(ctx -> callback.run());
}
/**
* Registers {@code callback} to be run when re-exiting this {@link RequestContext}, usually when using
* the {@link #makeContextAware} family of methods. Any thread-local state associated with this context
* should be reset by this callback.
*
* @param callback a {@link Consumer} whose argument is this context
*/
void onExit(Consumer<? super RequestContext> callback);
/**
* @deprecated Use {@link #onExit(Consumer)} instead.
*/
@Deprecated
default void onExit(Runnable callback) {
onExit(ctx -> callback.run());
}
/**
* Registers {@code callback} to be run when this context is replaced by a child context.
* You could use this method to inherit an attribute of this context to the child contexts or
* register a callback to the child contexts that may be created later:
* <pre>{@code
* ctx.onChild((curCtx, newCtx) -> {
* assert ctx == curCtx && curCtx != newCtx;
* // Inherit the value of the 'MY_ATTR' attribute to the child context.
* newCtx.attr(MY_ATTR).set(curCtx.attr(MY_ATTR).get());
* // Add a callback to the child context.
* newCtx.onExit(() -> { ... });
* });
* }</pre>
*
* @param callback a {@link BiConsumer} whose first argument is this context and
* whose second argument is the new context that replaces this context
*/
void onChild(BiConsumer<? super RequestContext, ? super RequestContext> callback);
/**
* Invokes all {@link #onEnter(Consumer)} callbacks. It is discouraged to use this method directly.
* Use {@link #makeContextAware(Runnable)} or {@link #push(RequestContext, boolean)} instead so that
* the callbacks are invoked automatically.
*/
void invokeOnEnterCallbacks();
/**
* Invokes all {@link #onExit(Consumer)} callbacks. It is discouraged to use this method directly.
* Use {@link #makeContextAware(Runnable)} or {@link #push(RequestContext, boolean)} instead so that
* the callbacks are invoked automatically.
*/
void invokeOnExitCallbacks();
/**
* Invokes all {@link #onChild(BiConsumer)} callbacks. It is discouraged to use this method directly.
* Use {@link #makeContextAware(Runnable)} or {@link #push(RequestContext, boolean)} instead so that
* the callbacks are invoked automatically.
*/
void invokeOnChildCallbacks(RequestContext newCtx);
/**
* Resolves the specified {@code promise} with the specified {@code result} so that the {@code promise} is
* marked as 'done'. If {@code promise} is done already, this method does the following:
* <ul>
* <li>Log a warning about the failure, and</li>
* <li>Release {@code result} if it is {@linkplain ReferenceCounted a reference-counted object},
* such as {@link ByteBuf} and {@link FullHttpResponse}.</li>
* </ul>
* Note that a {@link Promise} can be done already even if you did not call this method in the following
* cases:
* <ul>
* <li>Invocation timeout - The invocation associated with the {@link Promise} has been timed out.</li>
* <li>User error - A service implementation called any of the following methods more than once:
* <ul>
* <li>{@link #resolvePromise(Promise, Object)}</li>
* <li>{@link #rejectPromise(Promise, Throwable)}</li>
* <li>{@link Promise#setSuccess(Object)}</li>
* <li>{@link Promise#setFailure(Throwable)}</li>
* <li>{@link Promise#cancel(boolean)}</li>
* </ul>
* </li>
* </ul>
*
* @deprecated Use {@link CompletableFuture} instead.
*/
@Deprecated
default void resolvePromise(Promise<?> promise, Object result) {
@SuppressWarnings("unchecked")
final Promise<Object> castPromise = (Promise<Object>) promise;
if (castPromise.trySuccess(result)) {
// Resolved successfully.
return;
}
try {
if (!(promise.cause() instanceof TimeoutException)) {
// Log resolve failure unless it is due to a timeout.
LoggerFactory.getLogger(RequestContext.class).warn(
"Failed to resolve a completed promise ({}) with {}", promise, result);
}
} finally {
ReferenceCountUtil.safeRelease(result);
}
}
/**
* Rejects the specified {@code promise} with the specified {@code cause}. If {@code promise} is done
* already, this method logs a warning about the failure. Note that a {@link Promise} can be done already
* even if you did not call this method in the following cases:
* <ul>
* <li>Invocation timeout - The invocation associated with the {@link Promise} has been timed out.</li>
* <li>User error - A service implementation called any of the following methods more than once:
* <ul>
* <li>{@link #resolvePromise(Promise, Object)}</li>
* <li>{@link #rejectPromise(Promise, Throwable)}</li>
* <li>{@link Promise#setSuccess(Object)}</li>
* <li>{@link Promise#setFailure(Throwable)}</li>
* <li>{@link Promise#cancel(boolean)}</li>
* </ul>
* </li>
* </ul>
*
* @deprecated Use {@link CompletableFuture} instead.
*/
@Deprecated
default void rejectPromise(Promise<?> promise, Throwable cause) {
if (promise.tryFailure(cause)) {
// Fulfilled successfully.
return;
}
final Throwable firstCause = promise.cause();
if (firstCause instanceof TimeoutException) {
// Timed out already.
return;
}
if (Exceptions.isExpected(cause)) {
// The exception that was thrown after firstCause (often a transport-layer exception)
// was a usual expected exception, not an error.
return;
}
LoggerFactory.getLogger(RequestContext.class).warn(
"Failed to reject a completed promise ({}) with {}", promise, cause, cause);
}
}