文章目录
- 思路
- Proxy
- Http
- sync
- 各种报错
- io.netty.util.concurrent.BlockingOperationException: DefaultChannelPromise
- java.net.SocketException: Permission denied
- java.lang.UnsupportedOperationException: unsupported message type: HttpObjectAggregator$AggregatedFullHttpRequest (expected: ByteBuf, FileRegion)
- 404
- TooLongFrameException
- java.nio.channels.ClosedChannelException
- shell调试
- 一个可用版本
思路
一个已经上线的项目,随着需求的增加和不断迭代,整个项目代码会慢慢腐败,那么重构就纳入了计划。根据《重构》提倡的原则,在重构前需要先确定测试,可是一遍遍手工测试太麻烦,写自动测试也是工程浩大。所以借助于项目已经上线(意味着已经被测试过,至少用户测试过),一个半自动的做法是,对于客户端发送的请求,既发向老的系统,同时也发向新的系统,如果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());
}
}
}