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());
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章