Java tutorial
/* * 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: * * https://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.server.thrift; import static com.linecorp.armeria.common.util.Functions.voidFunction; import static java.util.Objects.requireNonNull; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import javax.annotation.Nullable; import org.apache.thrift.TApplicationException; import org.apache.thrift.TBase; import org.apache.thrift.TException; import org.apache.thrift.TFieldIdEnum; import org.apache.thrift.meta_data.FieldMetaData; import org.apache.thrift.protocol.TMessage; import org.apache.thrift.protocol.TMessageType; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TTransport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.linecorp.armeria.common.AggregatedHttpMessage; import com.linecorp.armeria.common.DefaultRpcResponse; import com.linecorp.armeria.common.HttpData; import com.linecorp.armeria.common.HttpHeaderNames; import com.linecorp.armeria.common.HttpHeaders; import com.linecorp.armeria.common.HttpRequest; import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.MediaType; import com.linecorp.armeria.common.RequestContext; import com.linecorp.armeria.common.RpcRequest; import com.linecorp.armeria.common.RpcResponse; import com.linecorp.armeria.common.SerializationFormat; import com.linecorp.armeria.common.thrift.ThriftCall; import com.linecorp.armeria.common.thrift.ThriftProtocolFactories; import com.linecorp.armeria.common.thrift.ThriftReply; import com.linecorp.armeria.common.thrift.ThriftSerializationFormats; import com.linecorp.armeria.common.util.CompletionActions; import com.linecorp.armeria.common.util.SafeCloseable; import com.linecorp.armeria.internal.thrift.ThriftFieldAccess; import com.linecorp.armeria.internal.thrift.ThriftFunction; import com.linecorp.armeria.server.AbstractHttpService; import com.linecorp.armeria.server.Service; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.unsafe.ByteBufHttpData; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufHolder; /** * A {@link Service} that handles a Thrift call. * * @see ThriftProtocolFactories */ public final class THttpService extends AbstractHttpService { private static final Logger logger = LoggerFactory.getLogger(THttpService.class); private static final String PROTOCOL_NOT_SUPPORTED = "Specified content-type not supported"; private static final String ACCEPT_THRIFT_PROTOCOL_MUST_MATCH_CONTENT_TYPE = "Thrift protocol specified in Accept header must match " + "the one specified in the content-type header"; /** * Creates a new {@link THttpService} with the specified service implementation, supporting all thrift * protocols and defaulting to {@link ThriftSerializationFormats#BINARY TBinary} protocol when the client * doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session * protocol and setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param implementation an implementation of {@code *.Iface} or {@code *.AsyncIface} service interface * generated by the Apache Thrift compiler */ public static THttpService of(Object implementation) { return of(implementation, ThriftSerializationFormats.BINARY); } /** * Creates a new multiplexed {@link THttpService} with the specified service implementations, supporting * all thrift protocols and defaulting to {@link ThriftSerializationFormats#BINARY TBinary} protocol when * the client doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session * protocol and setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param implementations a {@link Map} whose key is service name and value is the implementation of * {@code *.Iface} or {@code *.AsyncIface} service interface generated by * the Apache Thrift compiler */ public static THttpService of(Map<String, ?> implementations) { return of(implementations, ThriftSerializationFormats.BINARY); } /** * Creates a new {@link THttpService} with the specified service implementation, supporting all thrift * protocols and defaulting to the specified {@code defaultSerializationFormat} when the client doesn't * specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session * protocol and setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * * @param implementation an implementation of {@code *.Iface} or {@code *.AsyncIface} service interface * generated by the Apache Thrift compiler * @param defaultSerializationFormat the default serialization format to use when not specified by the * client */ public static THttpService of(Object implementation, SerializationFormat defaultSerializationFormat) { return new THttpService(ThriftCallService.of(implementation), newAllowedSerializationFormats(defaultSerializationFormat, ThriftSerializationFormats.values())); } /** * Creates a new multiplexed {@link THttpService} with the specified service implementations, supporting * all thrift protocols and defaulting to the specified {@code defaultSerializationFormat} when the client * doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session * protocol and setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * * @param implementations a {@link Map} whose key is service name and value is the implementation of * {@code *.Iface} or {@code *.AsyncIface} service interface generated by * the Apache Thrift compiler * @param defaultSerializationFormat the default serialization format to use when not specified by the * client */ public static THttpService of(Map<String, ?> implementations, SerializationFormat defaultSerializationFormat) { return new THttpService(ThriftCallService.of(implementations), newAllowedSerializationFormats(defaultSerializationFormat, ThriftSerializationFormats.values())); } /** * Creates a new {@link THttpService} with the specified service implementation, supporting only the * formats specified and defaulting to the specified {@code defaultSerializationFormat} when the client * doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session protocol and * setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param implementation an implementation of {@code *.Iface} or {@code *.AsyncIface} service interface * generated by the Apache Thrift compiler * @param defaultSerializationFormat the default serialization format to use when not specified by the * client * @param otherAllowedSerializationFormats other serialization formats that should be supported by this * service in addition to the default */ public static THttpService ofFormats(Object implementation, SerializationFormat defaultSerializationFormat, SerializationFormat... otherAllowedSerializationFormats) { requireNonNull(otherAllowedSerializationFormats, "otherAllowedSerializationFormats"); return ofFormats(implementation, defaultSerializationFormat, Arrays.asList(otherAllowedSerializationFormats)); } /** * Creates a new multiplexed {@link THttpService} with the specified service implementations, supporting * only the formats specified and defaulting to the specified {@code defaultSerializationFormat} when the * client doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session protocol and * setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param implementations a {@link Map} whose key is service name and value is the implementation of * {@code *.Iface} or {@code *.AsyncIface} service interface generated by * the Apache Thrift compiler * @param defaultSerializationFormat the default serialization format to use when not specified by the * client * @param otherAllowedSerializationFormats other serialization formats that should be supported by this * service in addition to the default */ public static THttpService ofFormats(Map<String, ?> implementations, SerializationFormat defaultSerializationFormat, SerializationFormat... otherAllowedSerializationFormats) { requireNonNull(otherAllowedSerializationFormats, "otherAllowedSerializationFormats"); return ofFormats(implementations, defaultSerializationFormat, Arrays.asList(otherAllowedSerializationFormats)); } /** * Creates a new {@link THttpService} with the specified service implementation, supporting the protocols * specified in {@code allowedSerializationFormats} and defaulting to the specified * {@code defaultSerializationFormat} when the client doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session protocol and * setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param implementation an implementation of {@code *.Iface} or {@code *.AsyncIface} service interface * generated by the Apache Thrift compiler * @param defaultSerializationFormat the default serialization format to use when not specified by the * client * @param otherAllowedSerializationFormats other serialization formats that should be supported by this * service in addition to the default */ public static THttpService ofFormats(Object implementation, SerializationFormat defaultSerializationFormat, Iterable<SerializationFormat> otherAllowedSerializationFormats) { return new THttpService(ThriftCallService.of(implementation), newAllowedSerializationFormats(defaultSerializationFormat, otherAllowedSerializationFormats)); } /** * Creates a new multiplexed {@link THttpService} with the specified service implementations, supporting * the protocols specified in {@code allowedSerializationFormats} and defaulting to the specified * {@code defaultSerializationFormat} when the client doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session protocol and * setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param implementations a {@link Map} whose key is service name and value is the implementation of * {@code *.Iface} or {@code *.AsyncIface} service interface generated by * the Apache Thrift compiler * @param defaultSerializationFormat the default serialization format to use when not specified by the * client * @param otherAllowedSerializationFormats other serialization formats that should be supported by this * service in addition to the default */ public static THttpService ofFormats(Map<String, ?> implementations, SerializationFormat defaultSerializationFormat, Iterable<SerializationFormat> otherAllowedSerializationFormats) { return new THttpService(ThriftCallService.of(implementations), newAllowedSerializationFormats(defaultSerializationFormat, otherAllowedSerializationFormats)); } /** * Creates a new decorator that supports all thrift protocols and defaults to * {@link ThriftSerializationFormats#BINARY TBinary} protocol when the client doesn't specify one. * * <p>Currently, the only way to specify a serialization format is by using the HTTP session * protocol and setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. */ public static Function<Service<RpcRequest, RpcResponse>, THttpService> newDecorator() { return newDecorator(ThriftSerializationFormats.BINARY); } /** * Creates a new decorator that supports all thrift protocols and defaults to the specified * {@code defaultSerializationFormat} when the client doesn't specify one. * Currently, the only way to specify a serialization format is by using the HTTP session * protocol and setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param defaultSerializationFormat the default serialization format to use when not specified by the * client */ public static Function<Service<RpcRequest, RpcResponse>, THttpService> newDecorator( SerializationFormat defaultSerializationFormat) { final SerializationFormat[] allowedSerializationFormatArray = newAllowedSerializationFormats( defaultSerializationFormat, ThriftSerializationFormats.values()); return delegate -> new THttpService(delegate, allowedSerializationFormatArray); } /** * Creates a new decorator that supports only the formats specified and defaults to the specified * {@code defaultSerializationFormat} when the client doesn't specify one. * Currently, the only way to specify a serialization format is by using the HTTP session protocol and * setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param defaultSerializationFormat the default serialization format to use when not specified by the * client * @param otherAllowedSerializationFormats other serialization formats that should be supported by this * service in addition to the default */ public static Function<Service<RpcRequest, RpcResponse>, THttpService> newDecorator( SerializationFormat defaultSerializationFormat, SerializationFormat... otherAllowedSerializationFormats) { requireNonNull(otherAllowedSerializationFormats, "otherAllowedSerializationFormats"); return newDecorator(defaultSerializationFormat, Arrays.asList(otherAllowedSerializationFormats)); } /** * Creates a new decorator that supports the protocols specified in {@code allowedSerializationFormats} and * defaults to the specified {@code defaultSerializationFormat} when the client doesn't specify one. * Currently, the only way to specify a serialization format is by using the HTTP session protocol and * setting the Content-Type header to the appropriate {@link SerializationFormat#mediaType()}. * * @param defaultSerializationFormat the default serialization format to use when not specified by the * client * @param otherAllowedSerializationFormats other serialization formats that should be supported by this * service in addition to the default */ public static Function<Service<RpcRequest, RpcResponse>, THttpService> newDecorator( SerializationFormat defaultSerializationFormat, Iterable<SerializationFormat> otherAllowedSerializationFormats) { final SerializationFormat[] allowedSerializationFormatArray = newAllowedSerializationFormats( defaultSerializationFormat, otherAllowedSerializationFormats); return delegate -> new THttpService(delegate, allowedSerializationFormatArray); } private static SerializationFormat[] newAllowedSerializationFormats( SerializationFormat defaultSerializationFormat, Iterable<SerializationFormat> otherAllowedSerializationFormats) { requireNonNull(defaultSerializationFormat, "defaultSerializationFormat"); requireNonNull(otherAllowedSerializationFormats, "otherAllowedSerializationFormats"); final Set<SerializationFormat> set = new LinkedHashSet<>(); set.add(defaultSerializationFormat); Iterables.addAll(set, otherAllowedSerializationFormats); return set.toArray(new SerializationFormat[set.size()]); } private final Service<RpcRequest, RpcResponse> delegate; private final SerializationFormat[] allowedSerializationFormatArray; private final Set<SerializationFormat> allowedSerializationFormats; private final ThriftCallService thriftService; private THttpService(Service<RpcRequest, RpcResponse> delegate, SerializationFormat[] allowedSerializationFormatArray) { requireNonNull(delegate, "delegate"); this.delegate = delegate; thriftService = findThriftService(delegate); this.allowedSerializationFormatArray = allowedSerializationFormatArray; allowedSerializationFormats = ImmutableSet.copyOf(allowedSerializationFormatArray); } private static ThriftCallService findThriftService(Service<?, ?> delegate) { return delegate.as(ThriftCallService.class).orElseThrow( () -> new IllegalStateException("service being decorated is not a ThriftCallService: " + delegate)); } /** * Returns the information about the Thrift services being served. * * @return a {@link Map} whose key is a service name, which could be an empty string if this service * is not multiplexed */ public Map<String, ThriftServiceEntry> entries() { return thriftService.entries(); } /** * Returns the allowed serialization formats of this service. */ public Set<SerializationFormat> allowedSerializationFormats() { return allowedSerializationFormats; } /** * Returns the default serialization format of this service. */ public SerializationFormat defaultSerializationFormat() { return allowedSerializationFormatArray[0]; } @Override protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) { final SerializationFormat serializationFormat = determineSerializationFormat(req); if (serializationFormat == null) { return HttpResponse.of(HttpStatus.UNSUPPORTED_MEDIA_TYPE, MediaType.PLAIN_TEXT_UTF_8, PROTOCOL_NOT_SUPPORTED); } if (!validateAcceptHeaders(req, serializationFormat)) { return HttpResponse.of(HttpStatus.NOT_ACCEPTABLE, MediaType.PLAIN_TEXT_UTF_8, ACCEPT_THRIFT_PROTOCOL_MUST_MATCH_CONTENT_TYPE); } final CompletableFuture<HttpResponse> responseFuture = new CompletableFuture<>(); final HttpResponse res = HttpResponse.from(responseFuture); ctx.logBuilder().serializationFormat(serializationFormat); ctx.logBuilder().deferRequestContent(); req.aggregateWithPooledObjects(ctx.eventLoop(), ctx.alloc()).handle(voidFunction((aReq, cause) -> { if (cause != null) { responseFuture.complete(HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, MediaType.PLAIN_TEXT_UTF_8, Throwables.getStackTraceAsString(cause))); return; } decodeAndInvoke(ctx, aReq, serializationFormat, responseFuture); })).exceptionally(CompletionActions::log); return res; } @Nullable private SerializationFormat determineSerializationFormat(HttpRequest req) { final HttpHeaders headers = req.headers(); final MediaType contentType = headers.contentType(); final SerializationFormat serializationFormat; if (contentType != null) { serializationFormat = findSerializationFormat(contentType); if (serializationFormat == null) { return null; } } else { serializationFormat = defaultSerializationFormat(); } return serializationFormat; } private static boolean validateAcceptHeaders(HttpRequest req, SerializationFormat serializationFormat) { // If accept header is present, make sure it is sane. Currently, we do not support accept // headers with a different format than the content type header. final List<String> acceptHeaders = req.headers().getAll(HttpHeaderNames.ACCEPT); if (!acceptHeaders.isEmpty() && !serializationFormat.mediaTypes().matchHeaders(acceptHeaders).isPresent()) { return false; } return true; } @Nullable private SerializationFormat findSerializationFormat(MediaType contentType) { for (SerializationFormat format : allowedSerializationFormatArray) { if (format.isAccepted(contentType)) { return format; } } return null; } private void decodeAndInvoke(ServiceRequestContext ctx, AggregatedHttpMessage req, SerializationFormat serializationFormat, CompletableFuture<HttpResponse> httpRes) { final HttpData content = req.content(); final ByteBuf buf; if (content instanceof ByteBufHolder) { buf = ((ByteBufHolder) content).content(); } else { buf = ctx.alloc().buffer(content.length()); buf.writeBytes(content.array(), content.offset(), content.length()); } final TByteBufTransport inTransport = new TByteBufTransport(buf); final TProtocol inProto = ThriftProtocolFactories.get(serializationFormat).getProtocol(inTransport); final int seqId; final ThriftFunction f; final RpcRequest decodedReq; try { final TMessage header; final TBase<?, ?> args; try { header = inProto.readMessageBegin(); } catch (Exception e) { logger.debug("{} Failed to decode Thrift header:", ctx, e); httpRes.complete(HttpResponse.of(HttpStatus.BAD_REQUEST, MediaType.PLAIN_TEXT_UTF_8, "Failed to decode Thrift header: " + Throwables.getStackTraceAsString(e))); return; } seqId = header.seqid; final byte typeValue = header.type; final int colonIdx = header.name.indexOf(':'); final String serviceName; final String methodName; if (colonIdx < 0) { serviceName = ""; methodName = header.name; } else { serviceName = header.name.substring(0, colonIdx); methodName = header.name.substring(colonIdx + 1); } // Basic sanity check. We usually should never fail here. if (typeValue != TMessageType.CALL && typeValue != TMessageType.ONEWAY) { final TApplicationException cause = new TApplicationException( TApplicationException.INVALID_MESSAGE_TYPE, "unexpected TMessageType: " + typeString(typeValue)); handlePreDecodeException(ctx, httpRes, cause, serializationFormat, seqId, methodName); return; } // Ensure that such a method exists. final ThriftServiceEntry entry = entries().get(serviceName); f = entry != null ? entry.metadata.function(methodName) : null; if (f == null) { final TApplicationException cause = new TApplicationException(TApplicationException.UNKNOWN_METHOD, "unknown method: " + header.name); handlePreDecodeException(ctx, httpRes, cause, serializationFormat, seqId, methodName); return; } // Decode the invocation parameters. try { args = f.newArgs(); args.read(inProto); inProto.readMessageEnd(); decodedReq = toRpcRequest(f.serviceType(), header.name, args); ctx.logBuilder().requestContent(decodedReq, new ThriftCall(header, args)); } catch (Exception e) { // Failed to decode the invocation parameters. logger.debug("{} Failed to decode Thrift arguments:", ctx, e); final TApplicationException cause = new TApplicationException(TApplicationException.PROTOCOL_ERROR, "failed to decode arguments: " + e); handlePreDecodeException(ctx, httpRes, cause, serializationFormat, seqId, methodName); return; } } finally { buf.release(); ctx.logBuilder().requestContent(null, null); } invoke(ctx, serializationFormat, seqId, f, decodedReq, httpRes); } private static String typeString(byte typeValue) { switch (typeValue) { case TMessageType.CALL: return "CALL"; case TMessageType.REPLY: return "REPLY"; case TMessageType.EXCEPTION: return "EXCEPTION"; case TMessageType.ONEWAY: return "ONEWAY"; default: return "UNKNOWN(" + (typeValue & 0xFF) + ')'; } } private void invoke(ServiceRequestContext ctx, SerializationFormat serializationFormat, int seqId, ThriftFunction func, RpcRequest call, CompletableFuture<HttpResponse> res) { final RpcResponse reply; try (SafeCloseable ignored = RequestContext.push(ctx)) { reply = delegate.serve(ctx, call); } catch (Throwable cause) { handleException(ctx, new DefaultRpcResponse(cause), res, serializationFormat, seqId, func, cause); return; } reply.handle(voidFunction((result, cause) -> { if (func.isOneWay()) { handleOneWaySuccess(ctx, reply, res, serializationFormat); return; } if (cause != null) { handleException(ctx, reply, res, serializationFormat, seqId, func, cause); return; } try { handleSuccess(ctx, reply, res, serializationFormat, seqId, func, result); } catch (Throwable t) { handleException(ctx, new DefaultRpcResponse(t), res, serializationFormat, seqId, func, t); } })).exceptionally(CompletionActions::log); } private static RpcRequest toRpcRequest(Class<?> serviceType, String method, TBase<?, ?> thriftArgs) { requireNonNull(thriftArgs, "thriftArgs"); // NB: The map returned by FieldMetaData.getStructMetaDataMap() is an EnumMap, // so the parameter ordering is preserved correctly during iteration. final Set<? extends TFieldIdEnum> fields = FieldMetaData.getStructMetaDataMap(thriftArgs.getClass()) .keySet(); // Handle the case where the number of arguments is 0 or 1. final int numFields = fields.size(); switch (numFields) { case 0: return RpcRequest.of(serviceType, method); case 1: return RpcRequest.of(serviceType, method, ThriftFieldAccess.get(thriftArgs, fields.iterator().next())); } // Handle the case where the number of arguments is greater than 1. final List<Object> list = new ArrayList<>(numFields); for (TFieldIdEnum field : fields) { list.add(ThriftFieldAccess.get(thriftArgs, field)); } return RpcRequest.of(serviceType, method, list); } private static void handleSuccess(ServiceRequestContext ctx, RpcResponse rpcRes, CompletableFuture<HttpResponse> httpRes, SerializationFormat serializationFormat, int seqId, ThriftFunction func, Object returnValue) { final TBase<?, ?> wrappedResult = func.newResult(); func.setSuccess(wrappedResult, returnValue); respond(serializationFormat, encodeSuccess(ctx, rpcRes, serializationFormat, func.name(), seqId, wrappedResult), httpRes); } private static void handleOneWaySuccess(ServiceRequestContext ctx, RpcResponse rpcRes, CompletableFuture<HttpResponse> httpRes, SerializationFormat serializationFormat) { ctx.logBuilder().responseContent(rpcRes, null); respond(serializationFormat, HttpData.EMPTY_DATA, httpRes); } private static void handleException(ServiceRequestContext ctx, RpcResponse rpcRes, CompletableFuture<HttpResponse> httpRes, SerializationFormat serializationFormat, int seqId, ThriftFunction func, Throwable cause) { final TBase<?, ?> result = func.newResult(); final HttpData content; if (func.setException(result, cause)) { content = encodeSuccess(ctx, rpcRes, serializationFormat, func.name(), seqId, result); } else { content = encodeException(ctx, rpcRes, serializationFormat, seqId, func.name(), cause); } respond(serializationFormat, content, httpRes); } private static void handlePreDecodeException(ServiceRequestContext ctx, CompletableFuture<HttpResponse> httpRes, Throwable cause, SerializationFormat serializationFormat, int seqId, String methodName) { final HttpData content = encodeException(ctx, new DefaultRpcResponse(cause), serializationFormat, seqId, methodName, cause); respond(serializationFormat, content, httpRes); } private static void respond(SerializationFormat serializationFormat, HttpData content, CompletableFuture<HttpResponse> res) { res.complete(HttpResponse.of(HttpStatus.OK, serializationFormat.mediaType(), content)); } private static HttpData encodeSuccess(ServiceRequestContext ctx, RpcResponse reply, SerializationFormat serializationFormat, String methodName, int seqId, TBase<?, ?> result) { final ByteBuf buf = ctx.alloc().buffer(128); boolean success = false; try { final TTransport transport = new TByteBufTransport(buf); final TProtocol outProto = ThriftProtocolFactories.get(serializationFormat).getProtocol(transport); final TMessage header = new TMessage(methodName, TMessageType.REPLY, seqId); outProto.writeMessageBegin(header); result.write(outProto); outProto.writeMessageEnd(); ctx.logBuilder().responseContent(reply, new ThriftReply(header, result)); final HttpData encoded = new ByteBufHttpData(buf, false); success = true; return encoded; } catch (TException e) { throw new Error(e); // Should never reach here. } finally { if (!success) { buf.release(); } } } private static HttpData encodeException(ServiceRequestContext ctx, RpcResponse reply, SerializationFormat serializationFormat, int seqId, String methodName, Throwable cause) { final TApplicationException appException; if (cause instanceof TApplicationException) { appException = (TApplicationException) cause; } else { appException = new TApplicationException(TApplicationException.INTERNAL_ERROR, "internal server error:" + System.lineSeparator() + "---- BEGIN server-side trace ----" + System.lineSeparator() + Throwables.getStackTraceAsString(cause) + "---- END server-side trace ----"); } final ByteBuf buf = ctx.alloc().buffer(128); boolean success = false; try { final TTransport transport = new TByteBufTransport(buf); final TProtocol outProto = ThriftProtocolFactories.get(serializationFormat).getProtocol(transport); final TMessage header = new TMessage(methodName, TMessageType.EXCEPTION, seqId); outProto.writeMessageBegin(header); appException.write(outProto); outProto.writeMessageEnd(); ctx.logBuilder().responseContent(reply, new ThriftReply(header, appException)); final HttpData encoded = new ByteBufHttpData(buf, false); success = true; return encoded; } catch (TException e) { throw new Error(e); // Should never reach here. } finally { if (!success) { buf.release(); } } } }