Netty搭建半自动测试工具(一)

思路

一个已经上线的项目,随着需求的增加和不断迭代,整个项目代码会慢慢腐败,那么重构就纳入了计划。根据《重构》提倡的原则,在重构前需要先确定测试,可是一遍遍手工测试太麻烦,写自动测试也是工程浩大。所以借助于项目已经上线(意味着已经被测试过,至少用户测试过),一个半自动的做法是,对于客户端发送的请求,既发向老的系统,同时也发向新的系统,如果response一致,那么就确定新系统的功能是正确。

Proxy

那么负责接受客户端请求并转发至后面新旧两个系统的Proxy最好是NIO的,那么Netty就成为了最佳选择。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.qbit</groupId>
  <artifactId>response-comparator</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>response-comparator</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->
    <dependency>
      <groupId>commons-cli</groupId>
      <artifactId>commons-cli</artifactId>
      <version>1.4</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.46.Final</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
      <plugins>
        <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <archive>
            <manifest>
              <mainClass>com.qbit.responsecomparator.App</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-shade-plugin</artifactId>
          <version>3.2.2</version>
          <executions>
            <execution>
              <phase>package</phase>
              <goals>
                <goal>shade</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
  </build>
</project>

App.java

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.net.InetSocketAddress;

/**
 * @author Qbit
 */
public class App 
{
    private final Configuration configuration;

    public App(Configuration configuration) {
        this.configuration=configuration;
    }

    public static void main(String[] args )
    {
        Configuration configuration=Configuration.fromCLI(args);
        new App(configuration).start();
    }

    private void start() {
        NioEventLoopGroup group=new NioEventLoopGroup();
        ServerBootstrap bootstrap=new ServerBootstrap()
            .group(group)
                .channel(NioServerSocketChannel.class)
                .childHandler(new SimpleChannelInboundHandler<ByteBuf>(){

                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
                        System.out.println(msg);
                    }

                    @Override
                    public boolean isSharable() {
                        return true;
                    }

                    @Override
                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                        cause.printStackTrace();
                        ctx.close();
                    }
                });
        bootstrap.bind(new InetSocketAddress(configuration.getPort()))
            .addListener(new ChannelFutureListener(){
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if(future.isSuccess()){
                    System.out.println("Server start success");
                }else{
                    System.err.println("Server start failed!");
                    future.cause().printStackTrace();
                }
            }
        });
    }
}

实验

这是一个开始,还未实现上面说的功能,但是这已经是一个可以启动的服务了,启动后监听80端口,用浏览器访问后会打印一行

PooledUnsafeDirectByteBuf(ridx: 0, widx: 328, cap: 1024)

这个时候浏览器还一直等待,因为服务端没有回复信息。

Http

App.java


/**
 * @author Qbit
 */
public class App
{
    private final Configuration configuration;

    public App(Configuration configuration) {
        this.configuration=configuration;
    }

    public static void main(String[] args )
    {
        Configuration configuration=Configuration.fromCLI(args);
        new App(configuration).start();
    }

    private void start() {
        NioEventLoopGroup group=new NioEventLoopGroup();
        ServerBootstrap bootstrap=new ServerBootstrap()
            .group(group)
                .channel(NioServerSocketChannel.class)
                .childHandler(new HttpServerInitializer());
        bootstrap.bind(new InetSocketAddress(configuration.getPort()))
            .addListener(new ChannelFutureListener(){
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if(future.isSuccess()){
                    System.out.println("Server start success");
                }else{
                    System.err.println("Server start failed!");
                    future.cause().printStackTrace();
                }
            }
        });
    }
}
class Comparator extends SimpleChannelInboundHandler<FullHttpRequest>{

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        System.out.println(msg.method());
        close(ctx,msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public boolean isSharable() {
        return true;
    }

    private void close(ChannelHandlerContext ctx, FullHttpRequest request) {
        HttpResponse response=new DefaultHttpResponse(
                request.protocolVersion(), HttpResponseStatus.OK
        );
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=UTF-8");
        boolean keepAlive=HttpUtil.isKeepAlive(request);
        ctx.write(response);
        ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
    }

}
class HttpServerInitializer extends ChannelInitializer<Channel>{

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ch.pipeline().addLast(new HttpServerCodec(),
                new HttpObjectAggregator(65536),
                new Comparator());
    }
}

要点就是一个HttpServerInitializer,然后作为一个ChannelInboundHandler交给childHandler方法。
另一个要点就是close这样就会返回一个合法的HttpResponse

实验

在浏览器访问http://localhost,会接收到200的状态码
后台会打印GET

sync

App.java

package com.onlyedu.responsecomparator;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import lombok.extern.log4j.Log4j;

import java.net.InetSocketAddress;
import java.util.Date;

/**
 * @author Qbit
 */
@Log4j
public class App {
    private final Configuration configuration;

    public App(Configuration configuration) {
        this.configuration = configuration;
    }

    public static void main(String[] args) {
        Configuration configuration = Configuration.fromCLI(args);
        new App(configuration).start();
    }

    private void start() {
        log.info(new Date().toString());
        NioEventLoopGroup group = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(group)
                .channel(NioServerSocketChannel.class)
                .childHandler(new HttpServerInitializer());
        bootstrap.bind(new InetSocketAddress(configuration.getPort()))
                .addListener(new SimpleChannelFutureListener("Server"));
    }

    class Comparator extends SimpleChannelInboundHandler<FullHttpRequest> {

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
            log.info(new Date().toString());
            System.out.println(msg.method()+" "+new Date());
            if(!controlServiceFuture.isDone()){
                System.err.println(Configuration.CONTROL_SERVICE+" is not done");
            }
            controlServiceFuture.sync();
            if(!controlServiceFuture.isDone()){
                log.error(Configuration.CONTROL_SERVICE+" is not done");
            }
            if(!experimentalServiceFuture.isDone()){
                System.err.println(Configuration.EXPERIMENTAL_SERVICE+" is not done");
            }
            experimentalServiceFuture.sync();
            if(!experimentalServiceFuture.isDone()){
                log.error(Configuration.EXPERIMENTAL_SERVICE+" is not done");
            }
            SessionHandler.newSession(ctx);
            controlServiceFuture.channel().writeAndFlush(msg)
                .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
            experimentalServiceFuture.channel().writeAndFlush(msg)
                    .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
            if(1==1){
                return;
            }
            close(ctx, msg);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            log.error(new Date().toString(),cause);
            ctx.close();
        }

        @Override
        public boolean isSharable() {
            log.info(new Date().toString());
            return true;
        }

        private void close(ChannelHandlerContext ctx, FullHttpRequest request) {
            log.info(new Date().toString());
            HttpResponse response = new DefaultHttpResponse(
                    request.protocolVersion(), HttpResponseStatus.OK
            );
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
            ctx.write(response);
            ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        }

        private ChannelFuture controlServiceFuture;
        private ChannelFuture experimentalServiceFuture;

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            log.info(new Date().toString());
            controlServiceFuture=new Bootstrap()
                .channel(NioSocketChannel.class)
                    .group(ctx.channel().eventLoop())
                    .handler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            log.info(new Date().toString());
                            ch.pipeline().addLast(new HttpServerCodec(),
                                    new HttpObjectAggregator(65536),
                                    new ControlServiceResponseHandler());
                        }

                        @Override
                        public boolean isSharable() {
                            log.info(new Date().toString());
                            return true;
                        }
                    })
            .connect(configuration.getControlService(),configuration.getPort())
                    .addListener(new SimpleChannelFutureListener("Control"))
                ;

            experimentalServiceFuture=new Bootstrap()
                    .channel(NioSocketChannel.class)
                    .group(ctx.channel().eventLoop())
                    .handler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            log.info(new Date().toString());
                            ch.pipeline().addLast(new HttpServerCodec(),
                                    new HttpObjectAggregator(65536),
                                    new ExperimentalResponseHandler());
                        }

                        @Override
                        public boolean isSharable() {
                            log.info(new Date().toString());
                            return true;
                        }
                    })
                    .connect(configuration.getExperimentalService(),configuration.getPort())
                    .addListener(new SimpleChannelFutureListener("Experimental"))
            ;
        }
    }

    class HttpServerInitializer extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel ch) throws Exception {
            log.info(new Date().toString());
            ch.pipeline().addLast(new HttpServerCodec(),
                    new HttpObjectAggregator(65536),
                    new Comparator());
        }
    }
}
@Log4j
class ControlServiceResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
        log.info(new Date().toString());
        System.out.println(msg.status());
        ByteBuf content=msg.content();
        System.out.println(new String(content.array(),"UTF-8"));
        SessionHandler.currentSession().controlResponse(msg);
    }

    @Override
    public boolean isSharable() {
        log.info(new Date().toString());
        return true;
    }
}
@Log4j
class ExperimentalResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse>{
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
        log.info(new Date().toString());
        System.out.println(msg.status());
        ByteBuf content=msg.content();
        System.out.println(new String(content.array(),"UTF-8"));
        SessionHandler.currentSession().experimental(msg);
    }

    @Override
    public boolean isSharable() {
        log.info(new Date().toString());
        return true;
    }
}
@Log4j
class SimpleChannelFutureListener implements ChannelFutureListener{
    private final String name;

    SimpleChannelFutureListener(String name) {
        this.name = name;
    }

    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        log.info(new Date().toString());
        if (future.isSuccess()) {
            System.out.println(name+" start success");
        } else {
            log.error(name+" start failed!",future.cause());
        }
    }
}
class SessionHandler{
    private static SessionHandler[] holder=new SessionHandler[1];
    private FullHttpResponse controlResponse;
    private FullHttpResponse experimentResponse;
    private Channel requestChannel;
    public static void newSession(ChannelHandlerContext context){
        holder[0]=new SessionHandler();
        holder[0].requestChannel=context.channel();
    }
    public void controlResponse(FullHttpResponse response){
        this.controlResponse=response;
        if(null!=experimentResponse){
            compare();
        }
    }
    public static SessionHandler currentSession(){
        return holder[0];
    }
    private void compare() {
        //todo implememt logic here
        requestChannel.writeAndFlush(controlResponse);
        requestChannel.close();
    }

    public void experimental(FullHttpResponse msg) {
        this.experimentResponse=msg;
        if(null!=controlResponse){
            compare();
        }
    }
}

上面代码启动会成功,但是使用http调用时会报错

各种报错

io.netty.util.concurrent.BlockingOperationException: DefaultChannelPromise

io.netty.util.concurrent.BlockingOperationException: DefaultChannelPromise@46fc6d32(uncancellable)
        at io.netty.util.concurrent.DefaultPromise.checkDeadLock(DefaultPromise.java:461)
        at io.netty.channel.DefaultChannelPromise.checkDeadLock(DefaultChannelPromise.java:159)
        at io.netty.util.concurrent.DefaultPromise.await(DefaultPromise.java:246)
        at io.netty.channel.DefaultChannelPromise.await(DefaultChannelPromise.java:131)
        at io.netty.channel.DefaultChannelPromise.await(DefaultChannelPromise.java:30)
        at io.netty.util.concurrent.DefaultPromise.sync(DefaultPromise.java:403)
        at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:119)
        at io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:30)
        at com.onlyedu.responsecomparator.App$Comparator.channelRead0(App.java:52)

报错还算比较详细,就是在一个应该非阻塞的channelRead0里调用了阻塞方法sync

java.net.SocketException: Permission denied

java.net.SocketException: Permission denied
        at java.base/sun.nio.ch.Net.bind0(Native Method)
        at java.base/sun.nio.ch.Net.bind(Net.java:455)
        at java.base/sun.nio.ch.Net.bind(Net.java:447)
        at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:227)
        at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:134)
        at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:550)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1334)
        at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:504)
        at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:489)
        at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:973)
        at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:248)
        at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:356)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:834)

这个是因为打开80端口失败造成的,因为linux默认情况下需要root才能打开1024以下端口

java.lang.UnsupportedOperationException: unsupported message type: HttpObjectAggregator$AggregatedFullHttpRequest (expected: ByteBuf, FileRegion)

这个是由于使用了错误的codec引起的,注意作为客户端的channel需要使用HttpClientCodec而不是HttpServerCodec

404

在浏览器访问后端服务时,根据http协议会设置Host这个head值,如果浏览器直接访问Netty,那么这个的Host就是netty的地址,然后netty原封不动的转发给后面的Nginx时由于Host不对,Nginx就会返回404。可用下面方法修改FullHttpRequest

private FullHttpRequest changeHostHeader(FullHttpRequest msg, String host) {
            FullHttpRequest controlRequest = msg.copy();
            controlRequest.headers().remove("Host");
            controlRequest.headers().set("Host",host);
            return controlRequest;
        }

TooLongFrameException

如果要传输图片等大文件会出现这个问题,解决方案是调大maxContentLength大小

new HttpObjectAggregator(Integer.MAX_VALUE)

java.nio.channels.ClosedChannelException

method:io.netty.channel.DefaultChannelPipeline.onUnhandledInboundException(DefaultChannelPipeline.java:1152)An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
java.nio.channels.ClosedChannelException
	at io.netty.channel.AbstractChannel$AbstractUnsafe.newClosedChannelException(AbstractChannel.java:957)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.write(AbstractChannel.java:865)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.write(DefaultChannelPipeline.java:1367)
	at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:715)
	at io.netty.channel.AbstractChannelHandlerContext.invokeWriteAndFlush(AbstractChannelHandlerContext.java:762)
	at io.netty.channel.AbstractChannelHandlerContext$WriteTask.run(AbstractChannelHandlerContext.java:1089)
	at io.netty.channel.ThreadPerChannelEventLoop.run(ThreadPerChannelEventLoop.java:69)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at java.base/java.lang.Thread.run(Thread.java:834)

shell调试

tcpdump

假设Nginx运行在80端口,可以用下面命令来查看Http报文(包括浏览器发送的和Netty发送的),来比较二者的差别

tcpdump -i ens33 -vvv 'tcp port 80'

wget

由于wget默认失败了会重试,为了避免后端打印一堆异常,使用t

wget -t 1 http://localhost:8080

一个可用版本

App.java


import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j;

import java.net.InetSocketAddress;
import java.util.Date;

/**
 * @author Qbit
 */
@Log4j
public class App {
    private static final Factory factory=new OioFactory();
    private final Configuration configuration;

    private ChannelFuture experimentalServiceFuture;
    private ChannelFuture controlServiceFuture;

    public App(Configuration configuration) {
        this.configuration = configuration;
    }

    public static void main(String[] args) {
        Configuration configuration = Configuration.fromCLI(args);
        new App(configuration).start();
    }
    @SneakyThrows
    private void start() {
        log.info(new Date().toString());
        EventLoopGroup group = factory.eventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(group)
                .channel(factory.serverChannel())
                .childHandler(new HttpServerInitializer())
//                .option(ChannelOption.SO_BACKLOG,2048)
//                .option(ChannelOption.SO_RCVBUF,128*1024)
                ;
        ChannelFuture future = bootstrap.bind(new InetSocketAddress(configuration.getPort()))
                .addListener(new SimpleChannelFutureListener("Server"));

        controlServiceFuture = new Bootstrap()
                .channel(factory.channel())
                .group(group)
                .handler(new ChannelInitializer<>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        log.info(new Date().toString());
                        ch.pipeline().addLast(new HttpClientCodec(),
                                new HttpObjectAggregator(Integer.MAX_VALUE),
                                new ControlServiceResponseHandler());
                    }

                    @Override
                    public boolean isSharable() {
                        log.info(new Date().toString());
                        return true;
                    }
                })
                .connect(configuration.getControlService(), configuration.getControlServicePort())
                .addListener(new SimpleChannelFutureListener("Control"));

        experimentalServiceFuture = new Bootstrap()
                .channel(factory.channel())
                .group(group)
                .handler(new ChannelInitializer<>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        log.info(new Date().toString());
                        ch.pipeline().addLast(new HttpClientCodec(),
                                new HttpObjectAggregator(Integer.MAX_VALUE),
                                new ExperimentalResponseHandler());
                    }

                    @Override
                    public boolean isSharable() {
                        log.info(new Date().toString());
                        return true;
                    }
                })
                .connect(configuration.getExperimentalService(), configuration.getExperimentalServicePort())
                .addListener(new SimpleChannelFutureListener("Experimental"));
        future.channel().closeFuture().sync();
        log.info("the server channel has closed");
        experimentalServiceFuture.channel().closeFuture().sync();
        log.info("the experimental channel has closed");
        controlServiceFuture.channel().closeFuture().sync();
        log.info("the control channel has closed");
        group.shutdownGracefully().sync();
        log.info("the group has shutdown");
    }

    class ServerInboundHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg)  {
            log.info(msg.method());
            log.info(new String(msg.content().array()));
            if(controlServiceFuture.isSuccess()){
                log.info(Configuration.CONTROL_SERVICE+" is success");
                controlServiceFuture.channel().pipeline()
                        .forEach(entity->log.info(entity.getValue()));
            }else{
                log.error(Configuration.CONTROL_SERVICE+" is not success");
            }
            if(experimentalServiceFuture.isSuccess()){
                log.info(Configuration.EXPERIMENTAL_SERVICE+" is success");
                experimentalServiceFuture.channel().pipeline()
                        .forEach(entity->log.info(entity.getValue()));
            }else{
                log.error(Configuration.EXPERIMENTAL_SERVICE+" is not success");
            }
            SessionHandler.newSession(ctx);
            FullHttpRequest controlRequest = changeHostHeader(msg,configuration.getControlService());
            controlServiceFuture.channel().writeAndFlush(controlRequest)
                .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
            log.info("write to control service success");
            FullHttpRequest experimentalRequest= changeHostHeader(msg,configuration.getExperimentalService());
            experimentalServiceFuture.channel().writeAndFlush(experimentalRequest)
                    .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
            log.info("write to experimental service success");
        }

        private FullHttpRequest changeHostHeader(FullHttpRequest msg, String host) {
            FullHttpRequest controlRequest = msg.copy();
            controlRequest.headers().remove("Host");
            controlRequest.headers().set("Host",host);
            return controlRequest;
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)  {
            log.error(new Date().toString(),cause);
            ctx.close();
        }

        @Override
        public boolean isSharable() {
            log.info(new Date().toString());
            return true;
        }
    }

    class HttpServerInitializer extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel ch) {
            log.info(new Date().toString());
            ch.pipeline().addLast(new HttpServerCodec(),
                    new HttpObjectAggregator(Integer.MAX_VALUE),
                    new ServerInboundHandler());
        }
    }
}

Configuration.java


import lombok.Getter;
import org.apache.commons.cli.*;

/**
 * @author qbit
 */
@Getter
public class Configuration {

    public static final String CONTROL_SERVICE = "controlService";
    public static final String EXPERIMENTAL_SERVICE = "experimentalService";
    public static final String POLICY = "policy";
    public static final String PORT = "port";
    private final int port;
    private final String controlService;
    private final String experimentalService;
    private final int controlServicePort;
    private final int experimentalServicePort;

    private Configuration(int port,String controlService,String experimentalService){
        this.port=port;
        int index=controlService.indexOf(':');
        if(-1==index){
            this.controlService=controlService;
            this.controlServicePort=80;
        }else{
            this.controlService=controlService.substring(0,index);
            this.controlServicePort=Integer.valueOf(controlService.substring(index+1));
        }
        index=experimentalService.indexOf(':');
        if(-1==index){
            this.experimentalService=experimentalService;
            this.experimentalServicePort=80;
        }else {
            this.experimentalService=experimentalService.substring(0,index);
            this.experimentalServicePort=Integer.valueOf(experimentalService.substring(index+1));
        }
    }
    static Configuration fromCLI(String[] args){
        CommandLineParser parser=new DefaultParser();
        Options options=new Options()
                .addOption(CONTROL_SERVICE,true,"作为参照的服务")
                .addOption(EXPERIMENTAL_SERVICE,true,"待验证的服务")
                .addOption(POLICY,true,"策略")
                .addOption(PORT,true,"port")
                ;
        CommandLine commandLine=null;
        try{
            commandLine=parser.parse(options,args);
        }catch (ParseException e){
            System.err.println("Parsing failed.Reason: "+e.getMessage());
            System.exit(-1);
        }
        for(String requiredArg:new String[]{
                CONTROL_SERVICE,EXPERIMENTAL_SERVICE}){
            if(!commandLine.hasOption(requiredArg)){
                System.err.println(requiredArg+" is required!");
                System.exit(-1);
            }
        }
        int port=8080;
        if(commandLine.hasOption(PORT)){
            try{
                port=Integer.valueOf(commandLine.getOptionValue(PORT));
            }catch (Exception e){
                System.err.println(PORT+" is illegal");
            }
        }
        System.out.println("configuration is validated");
        return new Configuration(
                port,
                commandLine.getOptionValue(CONTROL_SERVICE),
                commandLine.getOptionValue(EXPERIMENTAL_SERVICE)
        );
    }

    public static void main(String[] args) {
        fromCLI(args);
    }

}

ControlServiceResponseHandler.java


@Log4j
class ControlServiceResponseHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.info("control client receive");
        SessionHandler.currentSession().controlResponse(msg);
    }

    @Override
    public boolean isSharable() {
        log.info(new Date().toString());
        return true;
    }
}

ExperimentalResponseHandler.java


import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.log4j.Log4j;

import java.util.Date;

@Log4j
class ExperimentalResponseHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("experimental client receive");
        SessionHandler.currentSession().experimental(msg);
    }

    @Override
    public boolean isSharable() {
        log.info(new Date().toString());
        return true;
    }
}

Factory.java


import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;

/**
 * @author qbit
 */
public interface Factory {
    EventLoopGroup eventLoopGroup();

    Class<? extends Channel> channel();

    Class<? extends ServerChannel> serverChannel();
}

NioFactory.java


import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @author qbit
 */
public class NioFactory implements Factory{
    @Override
    public EventLoopGroup eventLoopGroup() {
        return new NioEventLoopGroup();
    }

    @Override
    public Class<? extends Channel> channel() {
        return NioSocketChannel.class;
    }

    @Override
    public Class<? extends ServerChannel> serverChannel() {
        return NioServerSocketChannel.class;
    }
}

OioFactory


import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.oio.OioServerSocketChannel;
import io.netty.channel.socket.oio.OioSocketChannel;

/**
 * @author qbit
 */
public class OioFactory implements Factory {
    @Override
    public EventLoopGroup eventLoopGroup() {
        return new OioEventLoopGroup();
    }

    @Override
    public Class<? extends Channel> channel() {
        return OioSocketChannel.class;
    }

    @Override
    public Class<? extends ServerChannel> serverChannel() {
        return OioServerSocketChannel.class;
    }
}

SessionHandler.java


import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.log4j.Log4j;

@Log4j
class SessionHandler{
    private static SessionHandler[] holder=new SessionHandler[1];
    private Object controlResponse;
    private Object experimentResponse;
    private Channel requestChannel;
    private ChannelHandlerContext ctx;

    public static void newSession(ChannelHandlerContext context){
        holder[0]=new SessionHandler();
        holder[0].ctx=context;
        holder[0].requestChannel=context.channel();
    }
    public void controlResponse(Object response){
        this.controlResponse=response;
        if(null!=experimentResponse){
            compare();
        }
    }
    public static SessionHandler currentSession(){
        return holder[0];
    }
    private void compare() {
        //todo implememt logic here
        ctx.writeAndFlush(controlResponse);
    }

    public void experimental(Object msg) {
        log.info(msg.getClass());
        this.experimentResponse= msg;
        if(null!=controlResponse){
            compare();
        }
    }
}

SimpleChannelFutureListener.java


import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import lombok.extern.log4j.Log4j;

@Log4j
class SimpleChannelFutureListener implements ChannelFutureListener {
    private final String name;

    SimpleChannelFutureListener(String name) {
        this.name = name;
    }

    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        log.info(future.isSuccess());
        if (future.isSuccess()) {
            log.info(name+" isSuccess");
        } else {
            log.error(name+" start failed!",future.cause());
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值