Skip to content

Commit

Permalink
Enhance annotated service path mapping: HTTP method and media type ba…
Browse files Browse the repository at this point in the history
…sed mapping (line#696)

Motivation:

Currently we cannot bind two or more service methods with the same path even if they have different HTTP methods. Also, we need to bind them depending on 'content-type' or 'accept' header of the request for some cases.
These modifications will affect only to annotated service for now, and then apply this feature to the other services later.

Modifications:

- Fix a bug that might get a wrong service instance from the cache when finding a service.
- Add `PathMappingContext` and use it as a `PathMapping`'s argument instead of '(path, query)'.
- Introduce a simple content negotiation mechanism to find a service method by 'content-type' or 'accept' header.
- Add 'score' to `PathMappingResult` to find the best suitable service method.
- Add 'Router' and 'RoutingTrie' in order to find a service in an efficient way.
- Remove 'PathMappings' and use 'Router' instead.
- Apply caffeine cache to improve the efficiency of the service cache.

Result:

- Fixes line#579
  • Loading branch information
hyangtack authored and trustin committed Aug 7, 2017
1 parent 7b2e523 commit fbd4adc
Show file tree
Hide file tree
Showing 62 changed files with 3,787 additions and 721 deletions.
5 changes: 5 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ This product depends on Brave, distributed by Zipkin.io:
* License: licenses/LICENSE.brave.al20.txt (Apache License v2.0)
* Homepage: https://github.com/openzipkin/brave/

This product depends on Caffeine, distributed by Ben Manes:

* License: licenses/LICENSE.caffeine.al20.txt (Apache License v2.0)
* Homepage: https://github.com/ben-manes/caffeine

This product depends on EasyMock, distributed by Easymock contributors:

* License: licenses/LICENSE.easymock.al20.txt (Apache License v2.0)
Expand Down
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.yaml.snakeyaml.Yaml

import java.time.LocalDateTime
import java.util.stream.Streams

buildscript {
repositories {
Expand Down Expand Up @@ -48,6 +47,8 @@ ext {
shadedPackage = 'com.linecorp.armeria.internal.shaded'
relocations = [
// "groupId:artifactId", "original package name", "relocated package name"
['com.github.ben-manes.caffeine:caffeine', 'com.github.benmanes.caffeine', "${shadedPackage}.caffeine"],
['io.prometheus:simpleclient_caffeine', 'io.prometheus.client.cache.caffeine', "${shadedPackage}.prometheus_caffeine"],
['com.google.guava:guava', 'com.google.common', "${shadedPackage}.guava"],
['com.google.guava:guava', 'com.google.thirdparty.publicsuffix', "${shadedPackage}.publicsuffix"],
['com.spotify:completable-futures', 'com.spotify.futures', "${shadedPackage}.futures"],
Expand Down Expand Up @@ -916,6 +917,7 @@ allprojects {
linksOffline url, "${rootProject.projectDir}/site/src/apidocs/package-lists/${name}"
}

addOfflineLink 'caffeine', 'http://www.javadoc.io/doc/com.github.ben-manes.caffeine/caffeine/2.5.3/'
addOfflineLink 'dropwizard-metrics', 'http://metrics.dropwizard.io/3.2.2/apidocs/'
addOfflineLink 'grpc', 'http://www.grpc.io/grpc-java/javadoc/'
addOfflineLink 'jackson-databind', 'https://fasterxml.github.io/jackson-databind/javadoc/2.8/'
Expand Down
5 changes: 5 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ buildscript {
}

managedDependencies {
// Caffeine
compile 'com.github.ben-manes.caffeine:caffeine'

// Jackson
[ 'jackson-core', 'jackson-annotations', 'jackson-databind' ].each {
compile "com.fasterxml.jackson.core:$it"
Expand All @@ -17,6 +20,7 @@ managedDependencies {
compile 'io.dropwizard.metrics:metrics-core'

// Prometheus
compile 'io.prometheus:simpleclient_caffeine'
compile 'io.prometheus:simpleclient_common'

// Netty
Expand Down Expand Up @@ -102,6 +106,7 @@ task trimShadedJar(type: ProGuardTask,

keepattributes 'Signature, InnerClasses, Annotation'
keep "class !${shadedPackage}.**,com.linecorp.armeria.** { *; }"
keep "class ${shadedPackage}.caffeine.** { *; }" // To make the reflection work.
}

tasks.assemble.dependsOn tasks.trimShadedJar
Expand Down
94 changes: 86 additions & 8 deletions core/src/main/java/com/linecorp/armeria/common/Flags.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.linecorp.armeria.common;

import java.util.Optional;
import java.util.function.IntPredicate;
import java.util.function.LongPredicate;
import java.util.function.Predicate;
Expand All @@ -25,10 +26,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import com.google.common.base.Ascii;

import com.linecorp.armeria.client.ClientFactoryBuilder;
import com.linecorp.armeria.client.retry.Backoff;
import com.linecorp.armeria.server.PathMappingContext;
import com.linecorp.armeria.server.ServiceConfig;

import io.netty.channel.epoll.Epoll;
import io.netty.handler.ssl.OpenSsl;
Expand Down Expand Up @@ -122,6 +128,14 @@ public final class Flags {
}
});

private static final String DEFAULT_ROUTE_CACHE_SPEC = "maximumSize=4096";
private static final Optional<String> ROUTE_CACHE_SPEC =
caffeineSpec("routeCache", DEFAULT_ROUTE_CACHE_SPEC);

private static final String DEFAULT_COMPOSITE_SERVICE_CACHE_SPEC = "maximumSize=256";
private static final Optional<String> COMPOSITE_SERVICE_CACHE_SPEC =
caffeineSpec("compositeServiceCache", DEFAULT_COMPOSITE_SERVICE_CACHE_SPEC);

static {
if (!Epoll.isAvailable()) {
final Throwable cause = filterCause(Epoll.unavailabilityCause());
Expand Down Expand Up @@ -181,7 +195,7 @@ public static boolean useOpenSsl() {
* Note that this value has effect only if a user did not specify a worker group.
*
* <p>The default value of this flag is {@code 2 * <numCpuCores>}. Specify the
* {@code -Dcom.linecorp.armeria.numCommonWorkers=<integer>} to override the default value.
* {@code -Dcom.linecorp.armeria.numCommonWorkers=<integer>} JVM option to override the default value.
*/
public static int numCommonWorkers() {
return NUM_COMMON_WORKERS;
Expand All @@ -192,7 +206,8 @@ public static int numCommonWorkers() {
* threads. Note that this value has effect only if a user did not specify a blocking task executor.
*
* <p>The default value of this flag is {@value #DEFAULT_NUM_COMMON_BLOCKING_TASK_THREADS}. Specify the
* {@code -Dcom.linecorp.armeria.numCommonBlockingTaskThreads=<integer>} to override the default value.
* {@code -Dcom.linecorp.armeria.numCommonBlockingTaskThreads=<integer>} JVM option to override
* the default value.
*/
public static int numCommonBlockingTaskThreads() {
return NUM_COMMON_BLOCKING_TASK_THREADS;
Expand Down Expand Up @@ -251,7 +266,8 @@ public static long defaultResponseTimeoutMillis() {
* Note that this value has effect only if a user did not specify it.
*
* <p>The default value of this flag is {@value #DEFAULT_DEFAULT_CONNECT_TIMEOUT_MILLIS}. Specify the
* {@code -Dcom.linecorp.armeria.defaultConnectTimeoutMillis=<integer>} to override the default value.
* {@code -Dcom.linecorp.armeria.defaultConnectTimeoutMillis=<integer>} JVM option to override
* the default value.
*/
public static long defaultConnectTimeoutMillis() {
return DEFAULT_CONNECT_TIMEOUT_MILLIS;
Expand All @@ -262,7 +278,8 @@ public static long defaultConnectTimeoutMillis() {
* Note that this value has effect only if a user did not specify it.
*
* <p>The default value of this flag is {@value #DEFAULT_DEFAULT_SERVER_IDLE_TIMEOUT_MILLIS}. Specify the
* {@code -Dcom.linecorp.armeria.defaultServerIdleTimeoutMillis=<integer>} to override the default value.
* {@code -Dcom.linecorp.armeria.defaultServerIdleTimeoutMillis=<integer>} JVM option to override
* the default value.
*/
public static long defaultServerIdleTimeoutMillis() {
return DEFAULT_SERVER_IDLE_TIMEOUT_MILLIS;
Expand All @@ -273,7 +290,8 @@ public static long defaultServerIdleTimeoutMillis() {
* Note that this value has effect only if a user did not specify it.
*
* <p>This default value of this flag is {@value #DEFAULT_DEFAULT_CLIENT_IDLE_TIMEOUT_MILLIS}. Specify the
* {@code -Dcom.linecorp.armeria.defaultClientIdleTimeoutMillis=<integer>} to override the default value.
* {@code -Dcom.linecorp.armeria.defaultClientIdleTimeoutMillis=<integer>} JVM option to override
* the default value.
*/
public static long defaultClientIdleTimeoutMillis() {
return DEFAULT_CLIENT_IDLE_TIMEOUT_MILLIS;
Expand All @@ -284,7 +302,7 @@ public static long defaultClientIdleTimeoutMillis() {
* Note that this value has effect only if a user did not specify it.
*
* <p>This flag is disabled by default. Specify the
* {@code -Dcom.linecorp.armeria.defaultUseHttp2Preface=true} to enable it.
* {@code -Dcom.linecorp.armeria.defaultUseHttp2Preface=true} JVM option to enable it.
*/
public static boolean defaultUseHttp2Preface() {
return DEFAULT_USE_HTTP2_PREFACE;
Expand All @@ -295,7 +313,7 @@ public static boolean defaultUseHttp2Preface() {
* Note that this value has effect only if a user did not specify it.
*
* <p>This flag is enabled by default. Specify the
* {@code -Dcom.linecorp.armeria.defaultUseHttp1Pipelining=false} to disable it.
* {@code -Dcom.linecorp.armeria.defaultUseHttp1Pipelining=false} JVM option to disable it.
*/
public static boolean defaultUseHttp1Pipelining() {
return DEFAULT_USE_HTTP1_PIPELINING;
Expand All @@ -307,12 +325,55 @@ public static boolean defaultUseHttp1Pipelining() {
* {@code defaultBackoffSpec} in the constructor call.
*
* <p>The default value of this flag is {@value DEFAULT_DEFAULT_BACKOFF_SPEC}. Specify the
* {@code -Dcom.linecorp.armeria.defaultBackoffSpec=<spec>} to override the default value.
* {@code -Dcom.linecorp.armeria.defaultBackoffSpec=<spec>} JVM option to override the default value.
*/
public static String defaultBackoffSpec() {
return DEFAULT_BACKOFF_SPEC;
}

/**
* Returns the value of the {@code routeCache} parameter. It would be used to create a Caffeine
* {@link Cache} instance using {@link Caffeine#from(String)} for routing a request. The {@link Cache}
* would hold the mappings of {@link PathMappingContext} and the designated {@link ServiceConfig}
* for a request to improve server performance.
*
* <p>The default value of this flag is {@value DEFAULT_ROUTE_CACHE_SPEC}. Specify the
* {@code -Dcom.linecorp.armeria.routeCache=<spec>} JVM option to override the default value.
* Also, specify {@code -Dcom.linecorp.armeria.routeCache=off} JVM option to disable it.
*/
public static Optional<String> routeCacheSpec() {
return ROUTE_CACHE_SPEC;
}

/**
* Returns the value of the {@code compositeServiceCache} parameter. It would be used to create a
* Caffeine {@link Cache} instance using {@link Caffeine#from(String)} for routing a request.
* The {@link Cache} would hold the mappings of {@link PathMappingContext} and the designated
* {@link ServiceConfig} for a request to improve server performance.
*
* <p>The default value of this flag is {@value DEFAULT_COMPOSITE_SERVICE_CACHE_SPEC}. Specify the
* {@code -Dcom.linecorp.armeria.compositeServiceCache=<spec>} JVM option to override the default value.
* Also, specify {@code -Dcom.linecorp.armeria.compositeServiceCache=off} JVM option to disable it.
*/
public static Optional<String> compositeServiceCacheSpec() {
return COMPOSITE_SERVICE_CACHE_SPEC;
}

private static Optional<String> caffeineSpec(String name, String defaultValue) {
final String spec = get(name, defaultValue, value -> {
try {
if (!"off".equals(value)) {
CaffeineSpec.parse(value);
}
return true;
} catch (Exception e) {
return false;
}
});
return "off".equals(spec) ? Optional.empty()
: Optional.of(spec);
}

private static boolean getBoolean(String name, boolean defaultValue) {
return getBoolean(name, defaultValue, value -> true);
}
Expand Down Expand Up @@ -353,6 +414,23 @@ private static long getLong(String name, long defaultValue, LongPredicate valida
}));
}

private static String get(String name, String defaultValue, Predicate<String> validator) {
final String fullName = PREFIX + name;
final String value = System.getProperty(fullName);
if (value == null) {
logger.info("{}: {} (default)", fullName, defaultValue);
return defaultValue;
}

if (validator.test(value)) {
logger.info("{}: {}", fullName, value);
return value;
}

logger.info("{}: {} (default instead of: {})", fullName, defaultValue, value);
return defaultValue;
}

private static String getNormalized(String name, String defaultValue, Predicate<String> validator) {
final String fullName = PREFIX + name;
final String value = getLowerCased(fullName);
Expand Down
Loading

0 comments on commit fbd4adc

Please sign in to comment.