This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Restlight Starter

Restlight StarterRestlight Spring 基础上封装的Spring Boot Starter提供基于Spring Boot的自动配置

1 - Thread-Scheduling

ThreadingModel

线程调度允许用户根据需要随意制定ControllerIO线程上执行还是在Biz线程上执行还是在自定义线程上运行。

使用@Scheduled注解进行线程调度

eg.

IO线程上执行

@Scheduled(Schedulers.IO)
@GetMapping("/foo")
public String io() {
    // ....
    return "";
}

BIz线程上执行

// 在业务线程池中执行
@Scheduled(Schedulers.BIZ)
@GetMapping("/bar")
public String biz() {
    // ....
    return "";
}

不加注解默认Scheduler上执行

@GetMapping("/baz")
public String bizBatching() {
    // ....
    return "";
}

自定义Scheduler

Scheduler实现注入Spring

@Bean
public Scheduler scheduler() {
    return Schedulers.fromExecutor("foo", Executors.newCachedThreadPool());
}

在自定义Scheduler上执行

// 在业务线程池中执行
@Scheduled("foo")
@GetMapping("/foo")
public String foo() {
    // ....
    return "";
}

配置

所有配置均以restlight.server.scheduling开头

配置项 默认 说明
default-scheduler BIZ 在不加@Scheduled注解时采用的Scheduler

2 - Filter

基本使用

@Bean
public Filter addHeaderFilter() {
    return new Filter() {
        @Override
        public CompletableFuture<Void> doFilter(AsyncRequest request, AsyncResponse response, FilterChain chain) {
            request.setHeader("foo", "bar");
            return chain.doFilter(request, response);
        }
    };
}

上面的例子将会给所有到来的请求都加上一个固定的Header。

异步Filter

@Bean
public Filter filter() {
    return new Filter() {
        @Override
        public CompletableFuture<Void> doFilter(AsyncRequest request, AsyncResponse response, FilterChain chain) {
            return CompletableFuture.runAsync(() -> {
                // do something...
            }).thenCompose(r -> {
                // invoke next filter
                return chain.doFilter(request, response);
            });
        }
    };
}

上面的例子演示了在doFilter(xxx)中进行异步操作,并在该操作完成后回调FilterChain继续执行后续操作。

终止Filter的执行

当不期望执行后续的Filter时可返回一个CompletableFuture.completedFuture(null)实例。

@Override
public CompletableFuture<Void> doFilter(AsyncRequest request, AsyncResponse response, FilterChain chain) {
    return CompletableFuture.completedFuture(null);
}

3 - 拦截器

Restlight支持多种拦截器,适用于不同性能/功能场景

  • RouteInterceptor
  • MappingInterceptor
  • HandlerInterceptor
  • InterceptorFactory

拦截器定位

面向Controller/Route, 同时支持按需匹配的拦截器

  1. 面向Controller/Route: 拦截器一定能让用户根据需求选择匹配到哪个Controller接口
  2. 按需匹配: 支持按照AsyncRequest条件让用户灵活决定拦截器的匹配规则

InternalInterceptor

核心拦截器实现, 封装了拦截器核心行为

  • CompletableFuture<Boolean> preHandle0(AsyncRequest, AsyncResponse, Object)

    Controller执行前执行。返回布尔值表示是否允许当前请求继续往下执行。

  • CompletableFuture<Void> postHandle0(AsyncRequest, AsyncResponse, Object)

    Controller刚执行完之后执行

  • CompletableFuture<Void> afterCompletion0(AsyncRequest, AsyncResponse, Exception)

    请求行完之后执行

  • int getOrder()

    返回当前拦截器的优先级(默认为最低优先级),我们保证getOrder返回值决定拦截器的执行顺序,但是不支持使用@Order(int)注解情况下的顺序(虽然有时候看似顺序和@Order一致,但那只是巧合)。

拦截器匹配

初始化阶段: 初始化阶段为每一个Controller确定所有可能匹配到当前Controller的拦截器列表(包含可能匹配以及一定会匹配到当前Controller的拦截器)。

运行时阶段: 一个请求AsyncRequest到来时将通过将AsyncRequest作为参数传递到拦截器做路由判定, 决定是否匹配。

Affinity亲和性

public interface Affinity {

    /**
     * Current component is always attaching with the target subject.
     */
    int ATTACHED = 0;

    /**
     * Current component has a high affinity with the target subject this is the highest value.
     */
    int HIGHEST = 1;
    /**
     * Current component has no affinity with the target subject.
     */
    int DETACHED = -1;

    /**
     * Gets the affinity value.
     *
     * @return affinity.
     */
    int affinity();
}

用于表达拦截器与Controller/Route之间的亲和性

  • affinity()小于0表示拦截器不可能Controller匹配
  • affinity()等于0表示拦截器一定会与Controller匹配
  • affinity()大于0表示拦截器可能会与Controller匹配, 并且值越小匹配可能性越高(因此1为最高可能性), 相反值越大匹配可能性越小, 同时匹配的开销越大(用于拦截器匹配性能优化)。

InterceptorPredicate

public interface InterceptorPredicate extends RequestPredicate {
    InterceptorPredicate ALWAYS = request -> Boolean.TRUE;
}

public interface RequestPredicate extends Predicate<AsyncRequest> {
    // ignore this
    default boolean mayAmbiguousWith(RequestPredicate another) {
        return false;
    }
}

test(AsyncRequest)方法用于对每个请求的匹配。可以满足根据AsyncRequest运行时的任意条件的匹配(而不仅仅是局限于URL匹配)

Interceptor

Interceptor接口为同时拥有AffinityController/Route匹配)以及InterceptorPredicate(请求AsyncRequest匹配)的接口

public interface Interceptor extends InternalInterceptor, Affinity {

    /**
     * Gets the predicate of current interceptor. determines whether current interceptor should be matched to a {@link
     * esa.httpserver.core.AsyncRequest}.
     *
     * @return predicate, or {@code null} if {@link #affinity()} return's a negative value.
     */
    InterceptorPredicate predicate();

    /**
     * Default to highest affinity.
     * <p>
     * Whether a {@link Interceptor} should be matched to a {@link esa.restlight.server.route.Route} is depends on it.
     *
     * @return affinity
     */
    @Override
    default int affinity() {
        return HIGHEST;
    }
}

由于int affinity()根据不同的Controller/Route可能得出不同的结果, 因此需要使用InterceptorFactory进行创建

eg.

实现一个拦截器, 拦截所有GET接口(仅包含GET)且Header中包含X-Foo请求头的请求

@Bean
public InterceptorFactory interceptor() {
    return (ctx, route) -> new Interceptor() {
        @Override
        public CompletableFuture<Boolean> preHandle0(AsyncRequest request,
                                                     AsyncResponse response,
                                                     Object handler) {
            // biz logic
            return CompletableFuture.completedFuture(true);
        }


        @Override
        public InterceptorPredicate predicate() {
            return request -> request.containsHeader("X-Foo");
        }

        @Override
        public int affinity() {
            HttpMethod[] method = route.mapping().method();
            if (method.length == 1 && method[0] == HttpMethod.GET) {
                return ATTACHED;
            }
            return DETACHED;
        }
    };
}

RouteInterceptor

只绑定到固定的Controller/Route的拦截器

public interface RouteInterceptor extends InternalInterceptor {

    /**
     * Gets the affinity value between current interceptor and the given {@link Route}.
     *
     * @param ctx   context
     * @param route route to match
     *
     * @return affinity value.
     */
    boolean match(DeployContext<? extends RestlightOptions> ctx, Route route);
}

eg. 实现一个拦截器, 拦截所有GET请求(仅包含GET)

@Bean
public RouteInterceptor interceptor() {
    return new RouteInterceptor() {

        @Override
        public CompletableFuture<Boolean> preHandle0(AsyncRequest request,
                                                     AsyncResponse response,
                                                     Object handler) {
            // biz logic
            return CompletableFuture.completedFuture(true);
        }

        @Override
        public boolean match(DeployContext<? extends RestlightOptions> ctx, Route route) {
            HttpMethod[] method = route.mapping().method();
            return method.length == 1 && method[0] == HttpMethod.GET;
        }
    };
}

MappingInterceptor

绑定到所有Controller/Route, 并匹配请求的拦截器

public interface MappingInterceptor extends InternalInterceptor, InterceptorPredicate {
}

相当于affinity()固定返回Affinity.ATTACHED

eg.

实现一个拦截器, 拦截所有Header中包含X-Foo请求头的请求

@Bean
public MappingInterceptor interceptor() {
    return new MappingInterceptor() {

        @Override
        public CompletableFuture<Boolean> preHandle0(AsyncRequest request,
                                                     AsyncResponse response,
                                                     Object handler) {
            // biz logic
            return CompletableFuture.completedFuture(true);
        }
        
        @Override
        public boolean test(AsyncRequest request) {
            return request.containsHeader("X-Foo");
        }
    };
}

HandlerInterceptor

支持基于URI匹配的拦截器接口

  • includes(): 指定拦截器作用范围的Path, 默认作用于所有请求。
  • excludes(): 指定拦截器排除的Path(优先级高于includes)默认为空。
public interface HandlerInterceptor extends InternalInterceptor {

    String PATTERN_FOR_ALL = "/**";

    default String[] includes() {
        return null;
    }

    default String[] excludes() {
        return null;
    }

}

eg.

实现一个拦截器, 拦截除/foo/bar意外所有/foo/开头的请求

@Bean
public HandlerInterceptor interceptor() {
    return new HandlerInterceptor() {

        @Override
        public CompletableFuture<Boolean> preHandle0(AsyncRequest request,
                                                     AsyncResponse response,
                                                     Object handler) {
            // biz logic
            return CompletableFuture.completedFuture(true);
        }

        @Override
        public String[] includes() {
            return new String[] {"/foo/**"};
        }

        @Override
        public String[] excludes() {
            return new String[] {"/foo/bar"};
        }
    };
}

InterceptorFactory

拦截器工厂类, 用于为每一个Controller/Route生成一个Interceptor

public interface InterceptorFactory {

    /**
     * Create an instance of {@link Interceptor} for given target handler before starting Restlight server..
     *
     * @param ctx deploy context
     *
     * @param route target route.
     * @return interceptor
     */
    Optional<Interceptor> create(DeployContext<? extends RestlightOptions> ctx, Route route);
}

4 - 异常处理

Spring MVC异常处理

Restlight for Spring MVC支持使用Spring MVC中的@ExceptionHandler, @ControllerAdvice等注解,并且对此能力进行了增强 参考ExceptionHandler支持

ExceptionResolver

eg.

处理RuntimeException

@Component
public class GlobalExceptionResolver implements ExceptionResolver<RuntimeException> {
    
    @Override
    public CompletableFuture<Void> handleException(AsyncRequest request,
                                                   AsyncResponse response,
                                                   RuntimeException e) {
        // handle exception here
        return CompletableFuture.completedFuture(null);
    }
}

5 - 参数解析

参数解析指将从请求中解析出Controller参数值的过程(包含反序列化)

典型的有

  • @RequestParam
  • @RequestHeader
  • @RequestBody
  • @PathVariable
  • @CookieValue
  • @MatrixVariable
  • @QueryBean
  • AsyncRequest
  • AsyncResponse

接口定义

public interface ArgumentResolver {

    /**
     * 从AsyncRequest中解析出对应参数的值
     * 解析后的Object必须能和Param类型匹配
     */
    Object resolve(AsyncRequest request, AsyncResponse response) throws Exception;
    
}

框架会在启动的初始化阶段试图为每一个Controller中的每一个参数都找到一个与之匹配的ArgumentResolver用于请求的参数解析。

框架如何确定ArgumentResolver

ArgumentResolverAdapter


public interface ArgumentResolverPredicate {

    /**
     * 判断当前ArgumentResolver是否支持给定Param解析
     * Controller中的每个参数都对应一个Param实例, 
     * 可以通过Param获取注解等各类反射相关的元数据信息
     */
    boolean supports(Param param);

}

public interface ArgumentResolverAdapter extends ArgumentResolverPredicate, ArgumentResolver, Ordered {
    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }

}

初始化逻辑:

  1. 按照getOrder()方法的返回将Spring容器中所有的ArgumentResolver进行排序
  2. 按照排序后的顺序依次调用supports(Param param)方法, 返回true则将其作为该参数的ArgumentResolver, 运行时每次请求都将调用resolve(AsyncRequest request, AsyncResponse response)方法进行参数解析, 并且运行时不会改变。
  3. 未找到则启动报错。

细心的人可能会发现该设计可能并不能覆盖到以下场景

  • 因为resolve(AsyncRequest request, AsyncResponse response)方法参数中并没有传递Param参数, 虽然初始化阶段能根据supports(Param param)方法获取参数元数据信息(获取某个注解, 获取参数类型等等)判断是否支持, 但是如果运行时也需要获取参数的元数据信息(某个注解的值等)的话,此接口则无法满足需求。
  • 假如ArgumentResolver实现中需要做序列化操作, 因此期望获取到Spring容器中的序列化器时,则该接口无法支持(例如@RequestBody的场景)。

针对以上问题, 答案是确实无法支持。因为Restlight的设计理念是

  • 能在初始化阶段解决的问题就在初始化阶段解决

因此不期望用户以及Restlight的开发人员大量的在运行时去频繁获取一些JVM启动后就不会变动的内容(如: 注解的值), 甚至针对某些元数据信息使用ConcurrentHashMap进行缓存(看似是为了提高性能的缓存, 实际上初始化就固定了的内容反而增加了并发性能的损耗)。

基于以上原因我们提供了另一个ArgumentResolver的实现方式

ArgumentResolverFactory

public interface ArgumentResolverFactory extends ArgumentResolverPredicate, Ordered {

    ArgumentResolver createResolver(Param param,
                                    List<? extends HttpRequestSerializer> serializers);

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

与上面的ArgumentResolver类似

初始化逻辑:

  1. 按照getOrder()方法的返回将所有的ArgumentResolverFactory进行排序
  2. 按照排序后的顺序依次调用supports(Param param)方法, 返回true则将其作为该参数的ArgumentResolverFactory, 同时调用createResolver(Param param, List<? extends HttpRequestSerializer> serializers)方法创建出对应的ArgumentResolver
  3. 未找到则启动报错。

由于初始化时通过createResolver(Param param, List<? extends HttpRequestSerializer> serializers)方法传入了Param以及序列化器, 因此能满足上面的要求。

两种模式的定位

  • ArgumentResolver: 适用于参数解析器不依赖方法元数据信息以及序列化的场景。例如: 如果参数上使用了@XXX注解则返回某个固定的值。
  • ArgumentResolverFactory: 适用于参数解析器依赖方法元数据信息以及序列化的场景。例如: @RequestBody@RequestParameter(name = "foo")

自定义参数解析器

将自定义实现的ArgumentResolverAdapter或者ArgumentResolverFactory注入到Spring容器即可。

  • ArgumentResolverAdapter案例

场景: 当参数上有@AppId注解时, 使用固定的AppId

// 自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AppId {
}
@Bean
public ArgumentResolverAdapter resolver() {
    return new ArgumentResolverAdapter() {
        @Override
        public boolean supports(Param param) {
            // 当方法上有此注解时生效
            return param.hasParameterAnnotation(AppId.class);
        }
 
        @Override
        public Object resolve(AsyncRequest request, AsyncResponse response) {
            return "your appid";
        }
    };
}

controller使用


@GetMapping("/foo")
public String foo(@AppId String appId) {
    return appId;
}

上面的代码自定义实现了依据自定义注解获取固定appId的功能

  • ArgumentResolverFactory

场景: 通过自定义注解获取固定前缀x-custom的Header

// 自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomHeader {
    String value();
}
@Bean
public ArgumentResolverFactory resolver() {
    return new ArgumentResolverFactory() {
        @Override
        public boolean supports(Param param) {
            // 当方法上有此注解时生效
            return param.hasParameterAnnotation(CustomHeader.class);
        }

        @Override
        ArgumentResolver createResolver(Param param,
                                        List<? extends HttpRequestSerializer> serializers) {
            return new Resolver(param);
        }
    };
}

/**
 * 实际ArgumentResolver实现
 */
private static class Resolver implements ArgumentResolver {

    private final String headerName;

    private Resolver(Param param) {
        CustomHeader anno = param.getParameterAnnotation(CustomHeader.class);
        if (anno.value().length() == 0) {
            throw new IllegalArgumentException("Name of header must not be empty.");
        }
        // 初始化时组装好需要的参数
        this.headerName = "x-custom" + anno.value();
    }

    @Override
    public Object resolve(AsyncRequest request, AsyncResponse response) {
        // 运行时直接获取Header
        return request.getHeader(headerName);
    }
}

controller使用


@GetMapping("/foo")
public String foo(@CustomHeader("foo") String foo) {
    return foo;
}

6 - ArgumentResolverAdvice

ArgumentResolverAdvice允许用户在ArgumentResolver参数解析器解析参数的前后添加业务逻辑以及修改解析后的参数。

接口定义

public interface ArgumentResolverAdvice {

    /**
     * 在ArgumentResolver.resolve()之前被调用
     */
    void beforeResolve(AsyncRequest request, AsyncResponse response);

    /**
     * 在ArgumentResolver.resolve()之后被调用, 并使用此方法的返回值作为参数绑定到对应的Controller参数上
     */
    Object afterResolved(Object arg, AsyncRequest request, AsyncResponse response);
}

自定义ArgumentResolverAdvice

ArgumentResovler相同, ArgumentResolverAdvice自定应时同样需要实现ArgumentResolverAdviceAdapterArgumentResolverAdviceFactory接口

方式1:实现 ArgumentResolverAdviceAdapter

接口定义

public interface ArgumentResolverAdviceAdapter
        extends ArgumentResolverPredicate, ArgumentResolverAdvice, Ordered {

    @Override
    default void beforeResolve(AsyncRequest request, AsyncResponse response) {
    }

    @Override
    default Object afterResolved(Object arg, AsyncRequest request, AsyncResponse response) {
        return arg;
    }

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

方式2:实现ArgumentResolverAdviceFactory

接口定义

public interface ArgumentResolverAdviceFactory extends ArgumentResolverPredicate, Ordered {
    /**
     * 生成ArgumentResolverAdvice
     */
    ArgumentResolverAdvice createResolverAdvice(Param param, ArgumentResolver resolver);

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

ArgumentResolverAdviceAdapter接口以及ArgumentResolverAdviceFactory接口与ArgumentResolver中的ArgumentResolverAdapter接口以及ArgumentResolverFactory接口的使用方式相同, 这里不过多赘述。

7 - 返回值解析

返回值解析指将Controller返回值写入到AsyncResponse中的过程(包含序列化)

返回值解析逻辑

Restlight默认支持的返回值解析方式包括

  • @ResponseBody
  • @ResponseStatus
  • 普通类型(String, byte[], int 等)

与参数解析类似, 每个功能都对应了一个返回值解析器的实现。

接口定义

public interface ReturnValueResolver {

    /**
     * 解析出对应返回值为byte[]
     */
    byte[] resolve(Object returnValue,
                   AsyncRequest request,
                   AsyncReponse response) throws Exception;

}

框架会在启动的初始化阶段试图为每一个Controller中的每一个参数都找到一个与之匹配的ReturnValueResolver用于响应的返回值解析。

框架如何确定ReturnValueResolver

ReturnValueResolverAdapter


public interface ReturnValueResolverPredicate {

    /**
     * 判断当前ReturnValueResolver是否支持给定InvocableMethod解析
     * 每一个Controller都对应一个InvocableMethod实例, 
     * 可以通过InvocableMethod获取注解, 返回值类型等各类反射相关的元数据信息
     */
    boolean supports(InvocableMethod invocableMethod);

}

public interface ReturnValueResolverAdapter extends ReturnValueResolverPredicate, ReturnValueResolver, Ordered {

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }

}

初始化逻辑:

  1. 按照getOrder()方法的返回将Srping容器中所有的ReturnValueResolverAdapter进行排序
  2. 按照排序后的顺序依次调用supports(InvocableMethod invocableMethod)方法, 返回true则将其作为该参数的ReturnValueResolver, 运行时每次请求都将调用resolve(Object returnValue, AsyncRequest request, AsyncReponse response)方法进行参数解析, 并且运行时不会改变。
  3. 未找到则启动报错。

细心的人可能会发现该设计可能并不能覆盖到以下场景

  • 因为resolve(Object returnValue, AsyncRequest request, AsyncReponse response)方法参数中并没有传递InvocableMethod参数, 虽然初始化阶段能根据supports(InvocableMethod invocableMethod)方法获取Controller方法元数据信息(获取某个注解, 获取参数类型等等)判断是否支持, 但是如果运行时也需要获取Controller方法的元数据信息(某个注解的值等)的话,此接口则无法满足需求。
  • 假如ReturnValueResolverAdapter实现中需要做序列化操作, 因此期望获取到Spring容器中的序列化器时,则该接口无法支持(例如@ResponseBody的场景)。

针对以上问题, 答案是确实无法支持。因为Restlight的设计理念是

  • 能在初始化阶段解决的问题就在初始化阶段解决

因此不期望用户以及Restlight的开发人员大量的在运行时去频繁获取一些JVM启动后就不会变动的内容(如: 注解的值), 甚至针对某些元数据信息使用ConcurrentHashMap进行缓存(看似是为了提高性能的缓存, 实际上初始化就固定了的内容反而增加了并发性能的损耗)。

基于以上原因我们提供了另一个ReturnValueResolver的实现方式

ReturnValueResolverFactory

public interface ReturnValueResolverFactory extends ReturnValueResolverPredicate, Ordered {

    ReturnValueResolver createResolver(InvocableMethod invocableMethod,
                                    List<? extends HttpResponseSerializer> serializers);

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

与上面的ReturnValueResolver类似

初始化逻辑:

  1. 按照getOrder()方法的返回将所有的ReturnValueResolverFactory进行排序
  2. 按照排序后的顺序依次调用supports(InvocableMethod invocableMethod)方法, 返回true则将其作为该参数的ReturnValueResolverFactory, 同时调用createResolver(InvocableMethod invocableMethod, List<? extends HttpResponseSerializer> serializers)方法创建出对应的ReturnValueResolver
  3. 未找到则启动报错。

由于初始化时通过createResolver(InvocableMethod invocableMethod, List<? extends HttpResponseSerializer> serializers)方法传入了InvocableMethod以及序列化器, 因此能满足上面的要求。

两种模式的定位

  • ReturnValueResolver: 适用于解析器不依赖方法元数据信息以及序列化的场景。例如: 如果Controller方法上上使用了@XXX注解则返回某个固定的值。
  • ReturnValueResolverFactory: 适用于解析器依赖方法元数据信息以及序列化的场景。例如: @ResponseBody, @ResponseStatus(reason = “error”)。

自定义返回值解析器

将自定义实现的ReturnValueResolverAdapter或者ReturnValueResolverFactory注入到Spring容器即可。

  • ReturnValueResolverAdapter案例

场景: 当Controller方法上有@AppId注解时, 返回固定的AppId

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AppId {
}
@Bean
public ReturnValueResolverAdapter resolver() {

    private static byte[] APP_ID = "your appid".getBytes(StandardCharsets.UTF_8);

    return new ReturnValueResolverAdapter() {
        @Override
        public boolean supports(InvocableMethod invoicableMethod) {
            // 当方法上有此注解时生效
            return invoicableMethod.hasMethodAnnotation(AppId.class);
        }
 
        @Override
        public byte[] resolve(Object returnValue,
                   AsyncRequest request,
                   AsyncReponse response) {
            return APP_ID;
        }
    };
}

controller使用


@GetMapping("/foo")
@AppId
public String foo() {
    return "";
}

上面的代码自定义实现了依据自定义注解获取固定appId的功能

  • ReturnValueResolverFactory

场景: 通过自定义注解对所有String类型的返回值加上一个指定前缀。

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Suffix {
    String value();
}
@Bean
    public ReturnValueResolverFactory resolver() {
        return new ReturnValueResolverFactory() {
            @Override
            public boolean supports(InvocableMethod invocableMethod) {
                // 当方法上有此注解时生效
                return String.class.equals(invocableMethod.getMethod().getReturnType()) && parameter.hasMethodAnnotation(CustomHeader.class);
            }
            
            public ReturnValueResolver createResolver(InvocableMethod invocableMethod,
                                                      List<? extends HttpResponseSerializer> serializers) {
                return new Resovler(invocableMethod);
            }
            
        };
    }

    /**
     * 实际ArgumentResolver实现
     */
    private static class Resolver implements ReturnValueResolver {

        private final String suffix;

        private Resolver(InvocableMethod invocableMethod) {
            // 获取前缀
            Suffix anno = invocableMethod.getMethodAnnotation(Suffix.class);
            this.suffix = anno.value();
        }

        @Override
        public byte[] resolve(Object returnValue,
                   AsyncRequest request,
                   AsyncReponse response) {
            // 拼接
            return suffix + String.valueOf(returnValue);
        }
    }

controller使用


@GetMapping("/foo")
@Suffix
public String foo() {
    return "foo";
}

8 - ReturnValueResolverAdvice

ReturnValueResolverAdvice允许用户在ReturnValueResolver参数解析器解析参数的前后添加业务逻辑以及修改解析后的参数。

接口定义

public interface ReturnValueResolverAdvice {
    /**
     * 使用此方法的返回值作为ReturnValueResolver.resolve()的参数调用
     */
    Object beforeResolve(Object returnValue, AsyncRequest request, AsyncResponse response);
}

自定义ReturnValueResolverAdvice

ArgumentResovler相同, ReturnValueResolverAdvice自定应时同样需要实现ReturnValueResolverAdviceAdapterReturnValueResolverAdviceFactory接口

方式1 实现ReturnValueResolverAdviceAdapter

接口定义

public interface ReturnValueResolverAdviceAdapter
        extends ReturnValueResolverPredicate, ReturnValueResolverAdvice, Ordered {

    @Override
    default Object beforeResolve(Object returnValue, AsyncRequest request, AsyncResponse response) {
        return returnValue;
    }

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

方式2:实现ReturnValueResolverAdviceFactory

接口定义

public interface ReturnValueResolverAdviceFactory extends ReturnValueResolverPredicate, Ordered {
    /**
     * 生成ReturnValueResolverAdvice
     */
    ReturnValueResolverAdvice createResolverAdvice(InvocableMethod method, ReturnValueResolver resolver);

    @Override
    default int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

ReturnValueResolverAdviceAdapter接口以及ReturnValueResolverAdviceFactory接口与ReturnValueResolver中的ReturnValueResolverAdapter接口以及ReturnValueResolverFactory接口的使用方式相同, 这里不过多赘述。

9 - 序列化

序列化支持

  • jackson(默认)
  • fastjson
  • Gson
  • ProtoBuf
  • 自定义支持

序列化切换

默认使用Jackson序列化,因此Restlight也默认引入了Jackson依赖,如果想要切换成别的序列化方式,则需要引入对应的Maven依赖并进行简单的配置。

Example:以切换Gson为例

  1. 引入Gson依赖:
<dependency>
     <groupId>com.google.code.gson</groupId>
     <artifactId>gson</artifactId>
     <version>2.8.5</version>
</dependency>
  1. 注入Gson序列化器
@Bean
public HttpBodySerializer bodySerializer() {
    return new GsonHttpBodySerializer();
}

完成切换。

序列化器

接口标准定义

  • HttpRequestSerializer: 用于请求的反序列化。
  • HttpResponseSerializer: 用于响应的序列化。
  • HttpBodySerializer: 继承自HttpRequestSerializer以及HttpResponseSerializer, 可同时提供请求与响应的序列化功能。

内置的序列化器

  • FastJsonHttpBodySerializer
  • JacksonHttpBodySerializer
  • GsonHttpBodySerializer
  • ProtoBufHttpBodySerializer

定制序列化器

可以选择通过继承或者自定义实现HttpRequestSerializer以及HttpResponseSerializer接口(或者HttpResponseSerializer同理)的方式实现定制序列化。

如,在上面的例子中我们切换序列化方式为jackson,此时我们想定制序列化的日期格式为yyyy-MM-dd

@Bean
public HttpBodySerializer bodySerializer() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
    return new HttpJsonBodySerializerAdapter(new JacksonSerializer(objectMapper)) {};
}

同样也可以定制更多的序列化选项。

多个序列化器并存

Restlight并不像Spring MVC一样要求一个应用中只能使用一种序列化方式,Restlight允许多种序列化方式并存。

实际的服务中我们可能有这样的需求:我们对接服务A时使用的是ProtoBuf序列化,而其他情况都是使用的fastjson序列化。

配置如下:

@Bean
public HttpBodySerializer fastjsonBodySerializer() {
    return new FastJsonHttpBodySerializer();
 
@Bean
public HttpBodySerializer protoBufBodySerializer() {
    return new ProtoBufHttpBodySerializer();
}

直接注入两个序列化器即可。

上面的配置当MediaType(请求对应ContentType, 响应对应Accept)为application/json时使用JSON序列化, 为application/x-protobuf时使用ProtoBuf序列化.

兼容Spring Boot标准

使用Spring Boot时,经常在配置文件里面添加Jackson和Gson的配置,如:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.gson.date-format=yyyy-MM-dd HH:mm:ss

Restlight同样支持该标准,其作用逻辑如下图所示: 序列化器兼容springboot逻辑图.png 如图所示,配置文件中Jackson和Gson相关内容要想生效,需要满足如下条件:

  1. 用户没有手动定制HttpJsonBodySerializerAdapter
  2. 用户没有手动注入Jackson或者Gson序列化器(FastJsonHttpBodySerializer和GsonJsonHttpBodySerializer)
  3. 用户没有手动注入ObjectMapper或者Gson对象
  4. 配置文件有对Jackson和Gson进行配置

满足以上四个条件,配置文件中关于Jackson和Gson的配置,才会在对应的Jackson和Gson序列化器中生效。

响应序列化内容协商(Json与ProtoBuf序列化)

当 json和ProtoBuf的序列化器同时存在时, 响应序列化方式可以通过url中的参数来指定。

  • 首先开启序列化协商功能:
# 开启请求序列化协商
restlight.server.serialize.request.negotiation=true
# 开启响应序列化协商
restlight.server.serialize.response.negotiation=true

指定使用json序列化

eg: /foo/{bar}?format=json

指定使用protobuf序列化

eg: /foo/{bar}?format=pb

  • 当参数format与应用自身的某些接口中的参数冲突时可以通过配置修改参数名称
restlight.server.serialize.request.negotiation-param=your-param-name
restlight.server.serialize.response.negotiation-param=your-param-name

使用@RequestSerializer指定请求序列化器

当配置了多个序列化器时, 可以使用@RequestSerializer中指定使用某个固定的序列化方式

下面的Controller指定使用Jackson反序列化请求数据

@PostMapping("/foo")
public void bar(@RequestBody @RequestSerializer(JacksonHttpBodySerializer.class) User user) {
    // ...
}

使用@ResponseSerializer指定响应序列化器

当配置了多个序列化器时, 可以使用@ResponseSerializer中指定使用某个固定的序列化方式

下面的Controller指定使用Jackson序列化响应结果

@PostMapping("/foo")
@ResponseSerializer(JacksonHttpBodySerializer.class)
public Foo bar() {
    return new Foo();
}

使用@Serializer指定请求 & 响应序列化器

当配置了多个序列化器时, 可以使用@Serializer中指定使用某个固定的序列化方式, 相当于同时指定RequestSerializer以及@ResponseSerializer

下面的Controller指定使用Jackson序列化

@PostMapping("/foo")
@Serializer(JacksonHttpBodySerializer.class)
public Foo bar(@RequestBody User user) {
    return new Foo();
}

多个序列化器框架如何选择使用哪个序列化器实现?

序列化器接口HttpRequestSerializerHttpResponseSerializer中均定义了suppot(xxx)方法, 用于在多个序列化器并存的情况下判断使用哪一个序列化器。以HttpRequestSerializer为例

public interface HttpRequestSerializer extends BaseHttpSerializer, RxSerializer {
    
    boolean supportsRead(MediaType mediaType, Type type);
    
}

HttpRequestSerializer中定义了supportsRead(MediaType mediaType, Type type)方法用于判断当前序列化器是否支持此次请求的序列化, 其中参数MediaType为请求的ContentTypeType为当前反序列化的目标类型。

同样HttpResponseSerializer

public interface HttpResponseSerializer extends BaseHttpSerializer, TxSerializer {

    boolean supportsWrite(MediaType mediaType, Type type);

    Object customResponse(AsyncRequest request, AsyncResponse response, Object returnValue);

}
  • supportsWrite(MediaType mediaType, Type type)方法用于判断当前序列化器是否支持此次请求的序列化, 其中参数MediaType为请求的Accept字段中的值, Type为当序列化的原始类型。
  • customResponse(xxx)将会在实际调用序列化之前被调用, 并使用返回值的Object作为此次序列化的原始对象(也就是说允许用户在响应的序列化之前更改响应的对象)

同时, 序列化器实现本身具有优先级由getOrder()表示(值越低优先级越高)

当最终无法找到匹配的序列化器时, 会通过默认的优先级进行序列化(暂时仅响应序列化为此行为)。

@Bean
public HttpBodySerializer fastjsonBodySerializer() {
    return new FastJsonHttpBodySerializer() {
       @Override
       public int getOrder() {
           return 0;
       }
   };
 
@Bean
public HttpBodySerializer protoBufBodySerializer() {
    return new ProtoBufHttpBodySerializer() {
        @Override
        public int getOrder() {
            return 1;
        }
    };
}
   

多种序列化并存时的优先级

  • 请求序列化
  1. 如果指定了@RequestSerializer或者@Serializer中指定的序列化方式则使用该序列化方式
  2. 使用序列化协商中的参数指定的序列化器
  3. 根据ContentType查找序列化器
  4. 未找到则报错
  • 响应序列化
  1. 如果指定了@ResponseSerializer或者@Serializer中指定的序列化方式则使用该序列化方式
  2. 使用序列化协商中的参数指定的序列化器
  3. 根据Accept查找序列化器
  4. 根据RequestMapping中produces指定的序列化器(未找到进入6)
  5. 未找到使用默认优先级最高的序列化器

ProtoBuf序列化支持

Restlight内置了ProtoBuf序列化器的实现,对应支持的MediaType为application/x-protobuf(需要请求的Content-Typeapplication/x-protobuf

同时使用ProtoBuf序列化器序列化后的响应结果的Content-Type也为application/x-protobuf)。

针对ProtoBuf序列化的特点,ProtoBuf序列化后还将增加Header, X-Protobuf-SchemaX-Protobuf-Message分别返回Message对象的getDescriptorForType().getFile().getName()getDescriptorForType().getFullName的结果。

特殊类型返回值的序列化

不管Controller上是否加有@ResponseBody注解, 在使用序列化器序列化之前都将遵守以下原则

  • 值为String类型直接返回该字符串结果。
  • 值为byte[]类型直接将结果写入响应。
  • 值为ByteBuf类型时直接将结果写入响应
  • 以上均不符合则使用序列化器进行序列化。
  • 如果Controlelr上未配置@ResponseBody,值为基本类型或基本类型包装类将返回该类型的字符串结果(调用String.valueOf())。
  • 以上均不符合则抛异常。

10 - 请求参数聚合

支持将请求的参数聚合到Bean中

eg.

@GetMapping(value = "/test")
public String foo(@RequestBean Pojo Pojo) {
    return "";
}

private static class Pojo {

    @QueryParam("id")
    private int id;

    @HeaderParam("message")
    private String message;

    private AsyncRequest request;
    private AsyncResponse response;

    public int getId() {
        return id;
    }

    //getter & setter
}

11 - URL参数聚合

支持将Url中的参数与Form表单中的参数(仅当Content-Typeapplication/x-www-form-urlencoded时有效)聚合到Bean中。

eg:

请求 /test?id=1&msg=hello 中的id和message的值将绑定到pojo参数中

@GetMapping(value = "/test")
public String handle(@QueryBean Pojo Pojo) {
    return "";
}

private static class Pojo {

    private int id;

    @QueryBean.Name("msg")
    private String message;

    public int getId() {
        return id;
    }

    //getter & setter
}

12 - Context Path

Restlight支持全局Path,使用时需要做如下配置:

restlight.server.context-path=/global-path/

原始Controller方法为:

@Controller
@RequestMapping("/restlight/employee/")
public class EmployeeController {

    @GetMapping("/list")
    @ResponseBody
    public List<Employee> listAll() {
        List<Employee> employeeList = new ArrayList<>(16);

        employeeList.add(new Employee("LiMing", 25, "1403063"));
        employeeList.add(new Employee("LiSi", 36, "1403064"));
        employeeList.add(new Employee("WangWu", 31, "1403065"));

        return employeeList;
    }
}

使用全局Path后的请求路径为:/global-path/restlight/employee/list

13 - Aware扩展

Spring场景,Restlight支持通过xxxAware接口获取一些内部对象。

其中包含

  • RestlightBizExecutorAware: 获取业务线程池
  • RestlightIoExecutorAware: 获取IO线程池
  • RestlightServerAware: 获取RestlightServer
  • RestlightDeployContextAware: 获取DeployContext

eg.

获取业务线程池

@Controller
public class HelloController implements RestlightBizExecutorAware {

    private Executor bizExecutor;

    @Override
    public void setRestlightBizExecutor(Executor bizExecutor) {
        this.bizExecutor = bizExecutor;
    }


    @GetMapping("/foo")
    public CompletableFuture<String> foo() {
        return CompletableFuture.supplyAsync(() -> "Hello Restlight!", bizExecutor);
    }

}

14 - 路由缓存

Spring MVC路由的痛点

传统的Spring MVC中, 当我们的@RequestMapping注解中包含了复杂任何的复杂匹配逻辑(这里的复杂逻辑可以理解为除了一个url对应一个controller实现,并且url中没有*, ? . {foo}等模式匹配的内容)时方能在路由阶段有相对较好的效果,反之如通常情况下一个请求的到来到路由到对应的controller实现这个过程将会是在当前应用中的所有Controller中遍历匹配,值得注意的是通常在微服务提倡RestFul设计的大环境下一个这种遍历几乎是无法避免的, 同时由于匹配的条件本身的复杂性(比如说正则本身为人诟病的就是性能),因此伴随而来的则是SpringMVC的路由的损耗非常的大。

Restlight路由缓存

设计原则

  • 二八原则(80%的业务由20%的接口处理)
  • 算法:类LFU(Least Frequently Used)算法

我们虽然不能改变路由条件匹配本身的损耗, 但是我们希望能做尽量少的匹配次数来达到优化的效果。因此采用常用的"缓存"来作为优化的手段。 当开启了路由缓存后,默认情况下将使用类LFU(Least Frequently Used)算法的方式缓存十分之的Controller,根据二八原则(80%的业务由20%的接口处理),大部分的请求都将在缓存中匹配成功并返回(这里框架默认的缓存十分之一,是相对比较保守的设置)

算法逻辑

当每次请求匹配成功时,会进行命中纪录的加1操作,并统计命中纪录最高的20%(可配)的Controller加入缓存, 每次请求的到来都将先从缓存中查找匹配的Controller(大部分的请求都将在此阶段返回), 失败则进入正常匹配的逻辑。

什么时候更新缓存? 我们不会在每次请求命中的情况下都去更新缓存,因为这涉及到一次排序(或者m次遍历, m为需要缓存的Controller的个数,相当于挑选出命中最高的m个controller)。 取而代之的是我们会以概率的方式去重新计算并更新缓存, 根据2-8原则通常情况下我们当前缓存的内存就是我们需要的内容, 所以没必要每次有请求命中都去重新计算并更新缓存, 因此我们会在请求命中的一定概率条件下采取做此操作(默认0.1%, 称之为计算概率), 减小了并发损耗(这段逻辑本身基于CopyOnWrite, 并且为纯无锁并发编程,本身性能损耗就很低),同时此概率可配置可以根据具体的应用实际情况调整配置达到最优的效果。

配置建议

缓存比例:请求集中化比较高则设置更小(比如集中在1%的Controller上则可以设置为缓存1%) 计算率: 理论上设置的越高实时性越强(缓存更新频率越高)但是并发损耗也会升高,因此建议设置的相对小一些以应对激增的非常用请求即可。

15 - 快速失败

Restlight 支持根据请求任务的排队时间快速失败。具体地,从接收到首字节(TTFB)或请求任务进入线程池开始排队时开始计时, 如果请求任务真正执行时的时间与起始时间的差值大于指定值(timeout),那么直接结束当前请求(返回500)。

使用时,需要配置timeout与起始时间(首字节时间或者开始排队时间,默认后者),示例如下:

restlight.server.scheduling.timeout.BIZ.type=QUEUED
restlight.server.scheduling.timeout.BIZ.time-millis=30

restlight.server.scheduling.timeout.IO.type=TTFB
restlight.server.scheduling.timeout.IO.time-millis=30

其中,BIZIO为Scheduler的名称,type为开始计时的方式,默认为QUEUED, 表示从请求任务进入线程池排队时开始计时,TTFB表示从接收到首字节时开始计时,time-millis 表示超时时间。

16 - Mock测试

同Spring MVC一样,Restlight也提供了单元测试的功能,用于构造请求并将请求映射到对应的Handler,得到Handler的执行结果并测试。如果您对Spring-Test不熟悉,请参考Spring-Testing。由于Restlight与Spring-web天生存在冲突,因此MockMvc的使用方式与Spring Mvc的略有差异,详情如下文所示。需要注意的是:

  • 使用MockMvc及MockMvcBuilders时请正确引入restlight包下的,而不是spring-web测试包下的。
  • 由于Restlight暂不支持RestTemplate,因此与该功能有关的测试同样暂不支持,如@AutoConfigureWebClient、@WebMvcTest等。

使用该功能需要额外引入如下依赖:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <version>${spring.boot.version}</version>
</dependency>

<dependency>
    <groupId>io.esastack</groupId>
    <artifactId>restlight-test-starter</artifactId>
    <scope>test</scope>
    <version>${restlight.version}</version>
</dependency>

1. 通过Context构造测试环境

该方式与Spring Mvc测试方式几乎无差异,需要注意的是:正确引入restlight包下的MockMvc及MockMvcBuilders。示例如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class BootstrapWithContextTest {

    @Autowired
    private ApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.contextSetup(context);
    }

    @Test
    public void testListAll() {
        mockMvc.perform(MockAsyncRequest.aMockRequest().withUri("/demo1/list").build())
                .addExpect(r -> assertTrue(((List) r.result()).isEmpty()))
                .addExpect(r -> assertEquals(200, r.response().status()));
    }

    @Test
    public void testListAll2() {
        mockMvc.perform(MockAsyncRequest.aMockRequest().withUri("/demo2/list").build())
                .addExpect(r -> assertTrue(((List) r.result()).isEmpty()))
                .addExpect(r -> assertEquals(200, r.response().status()));
    }
}

2. 通过Controller列表构造测试环境

该方式与Spring Mvc测试方式几乎无差异,需要注意的是:正确引入restlight包下的MockMvc及MockMvcBuilders。示例如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class BootstrapWithSingletonTest {

    @Autowired
    private DemoController1 demoController1;

    @Autowired
    private DemoController2 demoController2;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(demoController1, demoController2).build();
    }

    @Test
    public void testListAll() {
        mockMvc.perform(MockAsyncRequest.aMockRequest().withUri("/demo1/list").build())
                .addExpect(r -> assertTrue(((List) r.result()).isEmpty()))
                .addExpect(r -> assertEquals(200, r.response().status()));
    }

    @Test
    public void testListAll2() {
        mockMvc.perform(MockAsyncRequest.aMockRequest().withUri("/demo2/list").build())
                .addExpect(r -> assertTrue(((List) r.result()).isEmpty()))
                .addExpect(r -> assertEquals(200, r.response().status()));
    }
}

3. 自动注入MockMvc

该方式与Spring Mvc测试方式几乎无差异,需要注意的是:正确引入restlight包下的MockMvc。示例如下:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcAutowiredTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testListAll() {
        mockMvc.perform(MockAsyncRequest.aMockRequest().withUri("/demo1/list").build())
                .addExpect(r -> assertTrue(((List) r.result()).isEmpty()))
                .addExpect(r -> assertEquals(200, r.response().getStatus()));
    }

    @Test
    public void testListAll2() {
        mockMvc.perform(MockAsyncRequest.aMockRequest().withUri("/demo2/list").build())
                .addExpect(r -> assertTrue(((List) r.result()).isEmpty()))
                .addExpect(r -> assertEquals(200, r.response().getStatus()));
    }
}

4. 异步方法测试

与同步方法不同,如果原始Controller为异步方法,执行完perform()方法后直接对执行结果进行判断不会得到预期结果,因为原始Controller并未执行完,响应内容也尚未写入。因此如果原始Controller方法为异步,Restlight在执行完perform()方法后会阻塞等待异步方法执行完成,而后继续执行用户自定义的判断逻辑,使用时可以通过MockAsyncRequest的asynTimeout属性设置阻塞等待的时间(默认为-1)。

使用示例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RestlightDemoApplication.class)
@AutoConfigureMockMvc
public class AsyncDemoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testListAll() {
        mockMvc.perform(MockAsyncRequest.aMockRequest().withUri("/async/list").build())
                .addExpect(r -> assertTrue(((List) r.result()).isEmpty()))
                .addExpect(r -> assertEquals(200, r.response().status()));
    }
}

17 - 辅助配置

SpringBoot场景下大多数的配置可通过application.properties(或者yaml)配置文件即可完成配置,但是配置文件配置还是会有其缺陷

  • 无法动态配置(这里的动态指的是通过代码计算等方式决定配置)
  • 语法表达能力有限(比如ChannelOption无法通过配置文件表达)
  • 配置过多变得冗杂

等问题。

RestlightConfigure

用于支持SpringBoot场景显式配置

eg.

@Bean
public RestlightConfigure configure() {
    return restlight -> {
        restlight.address(8081)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .channelHandler(new LoggingHandler())
                .addFilter((request, response, chain) -> {
                    // biz logic
                    return chain.doFilter(request, response);
                })
                .deployments()
                .addHandlerInterceptor(new HandlerInterceptor() {
                    @Override
                    public boolean preHandle(AsyncRequest request,
                                             AsyncResponse response,
                                             Object handler) {
                        // biz logic
                        return true;
                    }
                });
        restlight.options().setCoreBizThreads(16);
        restlight.options().setMaxBizThreads(32);
        // more...
    };
}

18 - 配置一览

Server相关配置

所有配置均以restlight.server开头, 基于properties的配置(yml以此类推)

配置项 默认 说明
host 0.0.0.0 服务绑定的ip
port 8080 服务绑定的端口
unix-domain-socket-file 不为空则使用Unix Domain Socket绑定到此文件(优先级高于ip:port的方式)
use-native-transports Linux环境下为true其余为false 是否使用原生epoll支持, 否则使用NIO的selector
connector-threads 1 连接线程池大小
io-threads cpu*2(默认不超过64) IO线程池大小
biz-termination-timeout-seconds 60 优雅停机等待超时时间
http2-enable false 是否开启Http2
compress false 是否启用HTTP响应压缩
decompress false 是否启用HTTP请求解压
max-content-length 4 * 1024 * 1024 最大contentLength限制(b)
max-initial-line-length 4096 最大request line限制(b)
max-header-size 8192 最大header size限制(b)
route.use-cached-routing true 开启路由缓存
route.compute-rate 1 路由计算率,取值范围0-1000,固定的概率之下更新路由
warm-up.enable false 是否开启服务预热功能
warm-up.delay 0 服务延迟暴露时间(单位:毫秒)
keep-alive-enable true false服务器将强制只支持短链接
soBacklog 128 对应netty的ChannelOption.SO_BACKLOG
write-buffer-high-water-mark -1 netty中channel的高水位值
write-buffer-low-water-mark -1 netty中channel的低水位值
idle-time-seconds 60 连接超时时间
logging 设置LoggingHandler用于打印连接及读写信息

核心功能配置

所有配置均以restlight.server开头, 基于properties的配置(yml以此类推)

配置项 默认 说明
context-path 全局path前缀
biz-threads.core cpu*4(默认在64-128之间) 业务线程池核心线程数
biz-threads.max cpu*6(默认在128-256之间) 业务线程池最大线程数
biz-threads.blocking-queue-length 512 业务线程池阻塞队列大小
biz-threads.keep-alive-time-seconds 180 业务线程池keepAliveTime 单位:秒
serialize.request.negotiation false 请求序列化协商
serialize.request.negotiation-param format 请求序列化协商参数名称
serialize.response.negotiation false 响应序列化协商
serialize.response.negotiation-param format 响应序列化协商参数名称
print-banner true 是否启动打印logo

SSL配置

所有配置均以restlight.server.ssl开头, 基于properties的配置(yml以此类推)

配置项 默认 说明
enable false 是否使用https
ciphers 支持的加密套件,不设置表示使用默认
enable-protocols 支持的加密协议,不设置表示使用默认
cert-chain-path 证书路径,https-enable为true时必须
key-path 私钥路径,https-enable为true时必须
key-password 私钥文件密钥(如果需要的话)
trust-certs-path Trust Store
session-timeout session过期时间, 0表示使用默认
session-cache-size session缓存大小, 0表示使用默认
handshake-timeout-millis SSL握手超时时间
client-auth 客户端认证类型,不设置默认无