/** * 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 {@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); }
final TApplicationException cause = new TApplicationException( TApplicationException.INVALID_MESSAGE_TYPE, "unexpected TMessageType: " + typeString(typeValue)); handlePreDecodeException(ctx, httpRes, cause, serializationFormat, seqId, methodName); return; final ThriftServiceEntry entry = entries().get(serviceName); f = entry != null ? entry.metadata.function(methodName) : null; if (f == null) { TApplicationException.UNKNOWN_METHOD, "unknown method: " + header.name); handlePreDecodeException(ctx, httpRes, cause, serializationFormat, seqId, methodName); return; inProto.readMessageEnd(); decodedReq = toRpcRequest(f.serviceType(), header.name, args); ctx.logBuilder().requestContent(decodedReq, new ThriftCall(header, args)); } catch (Exception e) { TApplicationException.PROTOCOL_ERROR, "failed to decode arguments: " + e); handlePreDecodeException(ctx, httpRes, cause, serializationFormat, seqId, methodName); return; invoke(ctx, serializationFormat, seqId, f, decodedReq, httpRes);
/** * 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 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); }
@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; }
@Override public ServiceSpecification generateSpecification(Set<ServiceConfig> serviceConfigs) { final Map<Class<?>, EntryBuilder> map = new LinkedHashMap<>(); for (ServiceConfig c : serviceConfigs) { final THttpService service = c.service().as(THttpService.class).get(); service.entries().forEach((serviceName, entry) -> { for (Class<?> iface : entry.interfaces()) { final Class<?> serviceClass = iface.getEnclosingClass(); final EntryBuilder builder = map.computeIfAbsent(serviceClass, cls -> new EntryBuilder(serviceClass)); // Add all available endpoints. Accept only the services with exact and prefix path // mappings, whose endpoint path can be determined. final PathMapping pathMapping = c.pathMapping(); final String path = pathMapping.exactPath().orElse(pathMapping.prefix().orElse(null)); if (path != null) { builder.endpoint(new EndpointInfoBuilder(c.virtualHost().hostnamePattern(), path) .fragment(serviceName) .defaultFormat(service.defaultSerializationFormat()) .availableFormats(service.allowedSerializationFormats()) .build()); } } }); } final List<Entry> entries = map.values().stream() .map(EntryBuilder::build) .collect(Collectors.toList()); return generate(entries); }
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); }
@Override protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) { final SerializationFormat serializationFormat = determineSerializationFormat(req); if (serializationFormat == null) { return HttpResponse.of(HttpStatus.UNSUPPORTED_MEDIA_TYPE, if (!validateAcceptHeaders(req, serializationFormat)) { return HttpResponse.of(HttpStatus.NOT_ACCEPTABLE, MediaType.PLAIN_TEXT_UTF_8, ACCEPT_THRIFT_PROTOCOL_MUST_MATCH_CONTENT_TYPE); decodeAndInvoke(ctx, aReq, serializationFormat, responseFuture); return null; }).exceptionally(CompletionActions::log);
@Override public Map<String, String> loadDocStrings(Set<ServiceConfig> serviceConfigs) { return serviceConfigs.stream() .flatMap(c -> c.service().as(THttpService.class).get().entries().values().stream()) .flatMap(entry -> entry.interfaces().stream().map(Class::getClassLoader)) .flatMap(loader -> docstringExtractor.getAllDocStrings(loader) .entrySet().stream()) .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); }
/** * 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)); }
@Override protected void configure(ServerBuilder sb) throws Exception { sb.meterRegistry(registry); sb.service("/helloservice", THttpService.of((Iface) name -> { if ("world".equals(name)) { return "success"; } throw new IllegalArgumentException("bad argument"); }).decorate(MetricCollectingService.newDecorator( MeterIdPrefixFunction.ofDefault("armeria.server.HelloService")))); } };
/** * 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); }
@Override public ServiceSpecification generateSpecification(Set<ServiceConfig> serviceConfigs) { final Map<Class<?>, EntryBuilder> map = new LinkedHashMap<>(); for (ServiceConfig c : serviceConfigs) { final THttpService service = c.service().as(THttpService.class).get(); service.entries().forEach((serviceName, entry) -> { for (Class<?> iface : entry.interfaces()) { final Class<?> serviceClass = iface.getEnclosingClass(); final EntryBuilder builder = map.computeIfAbsent(serviceClass, cls -> new EntryBuilder(serviceClass)); // Add all available endpoints. Accept only the services with exact and prefix path // mappings, whose endpoint path can be determined. final PathMapping pathMapping = c.pathMapping(); final String path = pathMapping.exactPath().orElse(pathMapping.prefix().orElse(null)); if (path != null) { builder.endpoint(new EndpointInfo( c.virtualHost().hostnamePattern(), path, serviceName, service.defaultSerializationFormat(), service.allowedSerializationFormats())); } } }); } final List<Entry> entries = map.values().stream() .map(EntryBuilder::build) .collect(Collectors.toList()); return generate(entries); }
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); }
@Override protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) { final SerializationFormat serializationFormat = determineSerializationFormat(req); if (serializationFormat == null) { return HttpResponse.of(HttpStatus.UNSUPPORTED_MEDIA_TYPE, if (!validateAcceptHeaders(req, serializationFormat)) { return HttpResponse.of(HttpStatus.NOT_ACCEPTABLE, MediaType.PLAIN_TEXT_UTF_8, ACCEPT_THRIFT_PROTOCOL_MUST_MATCH_CONTENT_TYPE); decodeAndInvoke(ctx, aReq, serializationFormat, responseFuture); })).exceptionally(CompletionActions::log); return res;
@Override protected void configure(ServerBuilder sb) throws Exception { final THttpService helloAndSleepService = THttpService.of(ImmutableMap.of( "hello", HELLO_SERVICE_HANDLER, "sleep", SLEEP_SERVICE_HANDLER)); final THttpService fooService = THttpService.ofFormats(mock(FooService.AsyncIface.class), COMPACT); final THttpService cassandraService = THttpService.ofFormats(mock(Cassandra.AsyncIface.class), BINARY); final THttpService cassandraServiceDebug = THttpService.ofFormats(mock(Cassandra.AsyncIface.class), TEXT); final THttpService hbaseService = THttpService.of(mock(Hbase.AsyncIface.class)); final THttpService onewayHelloService = THttpService.of(mock(OnewayHelloService.AsyncIface.class));
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); }
@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; }