Обсуждение схемы маршрутизации Netty URL

сервер GitHub Шаблоны проектирования Netty

В последнее время я работаю с Netty, и мне нужно предоставить HTTP-веб-сервер для вызова вызывающего абонента. Принять Netty для обеспеченияHttpServerCodecОбработчик анализирует протокол Http, но ему необходимо обеспечить собственную маршрутизацию.

Сначала он направляется в реальный класс контроллера с использованием нескольких вложенных суждений if else для метода Http и uri:

String uri = request.uri();
HttpMethod method = request.method();
if (method == HttpMethod.POST) {
    if (uri.startsWith("/login")) {
        //url参数解析,调用controller的方法
    } else if (uri.startsWith("/logout")) {
        //同上
    }
} else if (method == HttpMethod.GET) {
    if (uri.startsWith("/")) {

    } else if (uri.startsWith("/status")) {

    }
}

просто предоставляяloginа такжеlogoutКогда используется API, код может выполнять функцию, но по мере увеличения количества API необходимо поддерживать все больше и больше методов и URI.else ifВсе больше и больше код становится все более и более сложным.

time-for-change
Время подумать о рефакторинге

Это также упоминается в руководстве по разработке Ali:

Многоуровневый рефакторинг else if

Поэтому сначала рассмотрите возможность использования шаблона проектирования состояния и рефакторинга шаблона проектирования стратегии.

режим состояния

Роль государственного режима:

  • состояние Представляет состояние, определяет интерфейс для различной обработки в соответствии с различными состояниями, интерфейс представляет собой набор методов, содержание обработки которых зависит от состояния и соответствует классу состояния экземпляра.
  • конкретное состояние Реализован интерфейс состояния, соответствующий дневному и ночному состояниям.
  • контекст Контекст содержит экземпляр конкретного состояния текущего состояния и, кроме того, определяет интерфейс шаблона состояния для использования внешними вызывающими объектами.

Прежде всего, мы знаем, что каждый http-запрос однозначно идентифицируется методом и uri, так называемая маршрутизация заключается в поиске метода в классе контроллера с помощью этой уникальной идентификации.

Так что поставьте HttpLabel как состояние

@Data
@AllArgsConstructor
public class HttpLabel {
    private String uri;
    private HttpMethod method;
}

Государственный интерфейс:

public interface Route {
    /**
     * 路由
     *
     * @param request
     * @return
     */
    GeneralResponse call(FullHttpRequest request);
}

Добавьте реализацию состояния для каждого состояния:

public void route() {
    //单例controller类
    final DemoController demoController = DemoController.getInstance();
    Map<HttpLabel, Route> map = new HashMap<>();
    map.put(new HttpLabel("/login", HttpMethod.POST), demoController::login);
    map.put(new HttpLabel("/logout", HttpMethod.POST), demoController::login);
}

Получите запрос, оцените статус и вызовите разные интерфейсы:

public class ServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
        String uri = request.uri();
        GeneralResponse generalResponse;
        if (uri.contains("?")) {
            uri = uri.substring(0, uri.indexOf("?"));
        }
        Route route = map.get(new HttpLabel(uri, request.method()));
        if (route != null) {
            ResponseUtil.response(ctx, request, route.call(request));
        } else {
            generalResponse = new GeneralResponse(HttpResponseStatus.BAD_REQUEST, "请检查你的请求方法及url", null);
            ResponseUtil.response(ctx, request, generalResponse);
        }
    }
}

Используйте шаблон проектирования состояния для рефакторинга кода. При добавлении URL-адреса вам нужно только указать значение в карте сети.

Подобно функции маршрутизации SpringMVC

Я смотрел это позжеОтражение JAVA + аннотация времени выполнения для реализации маршрутизации URL-адресовСпособ поиска отражения + аннотации очень элегантен, а код не сложен.

Далее описывается использование Netty отражения для реализации маршрутизации URL-адресов.

Аннотации маршрутизации:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    /**
     * 路由的uri
     *
     * @return
     */
    String uri();

    /**
     * 路由的方法
     *
     * @return
     */
    String method();
}

Сканируйте путь к классам с помощью@RequestMappingАннотированный метод, поместите этот метод в карту маршрутизации:Map<HttpLabel, Action<GeneralResponse>> httpRouterAction, ключ — это уникальный идентификатор HTTP, упомянутый выше.HttpLabel, Value — метод отражения вызовов:

@Slf4j
public class HttpRouter extends ClassLoader {

    private Map<HttpLabel, Action<GeneralResponse>> httpRouterAction = new HashMap<>();

    private String classpath = this.getClass().getResource("").getPath();

    private Map<String, Object> controllerBeans = new HashMap<>();

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = classpath + name.replaceAll("\\.", "/");
        byte[] bytes;
        try (InputStream ins = new FileInputStream(path)) {
            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[1024 * 5];
                int b = 0;
                while ((b = ins.read(buffer)) != -1) {
                    out.write(buffer, 0, b);
                }
                bytes = out.toByteArray();
            }
        } catch (Exception e) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, bytes, 0, bytes.length);
    }

    public void addRouter(String controllerClass) {
        try {
            Class<?> cls = loadClass(controllerClass);
            Method[] methods = cls.getDeclaredMethods();
            for (Method invokeMethod : methods) {
                Annotation[] annotations = invokeMethod.getAnnotations();
                for (Annotation annotation : annotations) {
                    if (annotation.annotationType() == RequestMapping.class) {
                        RequestMapping requestMapping = (RequestMapping) annotation;
                        String uri = requestMapping.uri();
                        String httpMethod = requestMapping.method().toUpperCase();
                        // 保存Bean单例
                        if (!controllerBeans.containsKey(cls.getName())) {
                            controllerBeans.put(cls.getName(), cls.newInstance());
                        }
                        Action action = new Action(controllerBeans.get(cls.getName()), invokeMethod);
                        //如果需要FullHttpRequest,就注入FullHttpRequest对象
                        Class[] params = invokeMethod.getParameterTypes();
                        if (params.length == 1 && params[0] == FullHttpRequest.class) {
                            action.setInjectionFullhttprequest(true);
                        }
                        // 保存映射关系
                        httpRouterAction.put(new HttpLabel(uri, new HttpMethod(httpMethod)), action);
                    }
                }
            }
        } catch (Exception e) {
            log.warn("{}", e);
        }
    }

    public Action getRoute(HttpLabel httpLabel) {
        return httpRouterAction.get(httpLabel);
    }
}

Вызывается отражениемcontrollerМетоды в классе:

@Data
@RequiredArgsConstructor
@Slf4j
public class Action<T> {
    @NonNull
    private Object object;
    @NonNull
    private Method method;

    private boolean injectionFullhttprequest;

    public T call(Object... args) {
        try {
            return (T) method.invoke(object, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.warn("{}", e);
        }
        return null;
    }

ServerHandler.javaОбработка заключается в следующем:

 //根据不同的请求API做不同的处理(路由分发)
Action<GeneralResponse> action = httpRouter.getRoute(new HttpLabel(uri, request.method()));
if (action != null) {
    if (action.isInjectionFullhttprequest()) {
        ResponseUtil.response(ctx, request, action.call(request));
    } else {
        ResponseUtil.response(ctx, request, action.call());
    }
} else {
    //错误处理
    generalResponse = new GeneralResponse(HttpResponseStatus.BAD_REQUEST, "请检查你的请求方法及url", null);
    ResponseUtil.response(ctx, request, generalResponse);
}

DemoControllerКонфигурация метода:

@RequestMapping(uri = "/login", method = "POST")
public GeneralResponse login(FullHttpRequest request) {
    User user = JsonUtil.fromJson(request, User.class);
    log.info("/login called,user: {}", user);
    return new GeneralResponse(null);
}

Результаты теста следующие:

Результаты теста

Полный код находится наGithub.com/more думаю / n ...