Netty URL路由方案探讨

栏目: Java · 发布时间: 7年前

内容简介:最近在用Netty做开发,需要提供一个http web server,供调用方调用。采用Netty本身提供的最开始是通过对Http method及uri 采用多层if else 嵌套判断的方法路由到真正的controller类:在只需提供

最近在用Netty做开发,需要提供一个http web server,供调用方调用。采用Netty本身提供的 HttpServerCodec handler进行Http协议的解析,但是需要自己提供路由。

最开始是通过对Http method及uri 采用多层if else 嵌套判断的方法路由到真正的controller类:

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")) {

    }
}
复制代码

在只需提供 loginlogout API时,代码可以完成功能,可是随着API的数量越来越多,需要支持的方法及uri越来越多, else if 越来越多,代码越来越复杂。

Netty URL路由方案探讨
是时候考虑重构了

在阿里开发手册中也提到过:

Netty URL路由方案探讨
重构多层else if

因此首先考虑采用状态设计模式及策略 设计模式 重构。

状态模式

状态模式的角色:

  • state状态 表示状态,定义了根据不同状态进行不同处理的接口,该接口是那些处理内容依赖于状态的方法集合,对应实例的state类
  • 具体的状态 实现了state接口,对应daystate和nightstate
  • context context持有当前状态的具体状态的实例,此外,他还定义了供外部调用者使用的状态模式的接口。

首先我们知道每个http请求都是由method及uri来唯一标识的,所谓路由就是通过这个唯一标识定位到controller类的中的某个方法。

因此把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时只需要网map里面put一个值就行了。

类似SpringMVC路由功能

后来看了 JAVA反射+运行时注解实现URL路由 发现反射+注解的方式很优雅,代码也不复杂。

下面介绍Netty使用反射实现URL路由。

路由注解:

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

    /**
     * 路由的方法
     *
     * @return
     */
    String method();
}
复制代码

扫描classpath下带有 @RequestMapping 注解的方法,将这个方法放进一个路由Map: Map<HttpLabel, Action<GeneralResponse>> httpRouterAction ,key为上面提到过的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);
}
复制代码

测试结果如下:

Netty URL路由方案探讨
测试结果

完整代码在 github.com/morethink/N…


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Think About Algorithms

How to Think About Algorithms

Jeff Edmonds / Cambridge University Press / 2008-05-19 / USD 38.99

HOW TO THINK ABOUT ALGORITHMS There are many algorithm texts that provide lots of well-polished code and proofs of correctness. Instead, this one presents insights, notations, and analogies t......一起来看看 《How to Think About Algorithms》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具