I'm using netty channel pool for a http client and in the ChannelPoolHandler implementation channelAcquired is not getting called when the channelPool.acquire() invoked. I'm using netty 4.0.32.Final. Here's how I created the chanelpool. I just followed the simple example listed at netty.io. If someone can just explain what I've done wrong or if there is a bug that'll be very helpful. Thanks.
EventLoopGroup group = new NioEventLoopGroup();
final Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class);
AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool> poolMap = new AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool>() {
#Override
protected SimpleChannelPool newPool(InetSocketAddress key) {
return new SimpleChannelPool(b.remoteAddress(key), new HttpClientPoolHandler());
}
};
final SimpleChannelPool simpleChannelPool = poolMap.get(new InetSocketAddress(uri.getHost(), uri.getPort()));
final Future<Channel> acquire = simpleChannelPool.acquire();
acquire.addListener(new FutureListener<Channel>() {
public void operationComplete(Future<Channel> f) throws Exception {
if (f.isSuccess()) {
final Channel ch = f.getNow();
// Send the HTTP request.
ChannelFuture channelFuture = ch.writeAndFlush(request);
channelFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()) {
simpleChannelPool.release(ch);
} else {
}
}
});
} else {
System.out.println("ERROR : " + f.cause());
}
}
});
The channelAcquiredmethod will only be called if you "acquire" a previous created channel. In your case there is not channel yet in the pool so it will call channelCreated.
Related
I'm trying to construct a proxy service with Netty that will support streaming calls and HTTP/1 traffic.
I've managed to do this successfully but ran into problems when I tried adding an HttpObjectAggregator to the pipeline. I need the Aggregator in order to construct a FullHttpResponse for reporting.
My current setup uses 2 ChannelInitializers and 2 business logic handlers
To initiate the proxy service:
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(eventLoopGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new SourceTransportHandlerInitializer())
.childOption(ChannelOption.AUTO_READ, false);
Channel channel;
try {
channel = serverBootstrap.bind(localPort).sync().channel();
channel.closeFuture().sync();
}catch (InterruptedException e){
// oh no
}
SourceTransportHandlerInitializer.java
public class SourceTransportHandlerInitializer extends ChannelInitializer<SocketChannel>{
#Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeLine = socketChannel.pipeline();
pipeLine.addLast(new HttpServerCodec(102400,102400,102400));
pipeLine.addLast(new HttpObjectAggregator(1048576));
pipeLine.addLast(new SourceHandler());
pipeLine.addLast(new LoggingHandler(LogLevel.INFO));
}
}
SourceHandler.java
public class SourceHandler extends ChannelInboundHandlerAdapter {
private volatile Channel outboundChannel;
#Override
public void channelActive(ChannelHandlerContext context) {
final Channel inChannel = context.channel();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(inChannel.eventLoop())
.channel(context.channel().getClass())
.handler(new TargetTransportHandlerInitializer(inChannel))
.option(ChannelOption.AUTO_READ, false);
ChannelFuture channelFuture = bootstrap.connect(Constants.host, Constants.hostPort);
outboundChannel = channelFuture.channel();
channelFuture.addListener((ChannelFutureListener) channelFuture1 -> {
if (channelFuture1.isSuccess()) {
inChannel.read();
} else {
inChannel.close();
}
});
}
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
//record request
}
if (outboundChannel.isActive()) {
outboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) channelFuture -> {
if (channelFuture.isSuccess()) {
// flush and read data
ctx.channel().read();
} else {
channelFuture.channel().close();
}
});
} else {
LOG.debug("SourceHandler did not read. Outbound Channel not active");
}
}
...
}
TargetTransportHandlerInitializer.java
public class TargetTransportHandlerInitializer extends ChannelInitializer<SocketChannel>{
protected final Channel inChannel;
public TargetTransportHandlerInitializer (final Channel inChannel){
this.inChannel = inChannel;
}
#Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeLine = socketChannel.pipeline();
pipeLine.addLast("codec", new HttpClientCodec(102400, 102400, 102400));
//pipeLine.addLast(new HttpObjectAggregator(1048576));
pipeLine.addLast(new TargetHandler(inChannel));
}
}
TargetHandler.java
public class TargetHandler extends ChannelInboundHandlerAdapter {
private final Channel inChannel;
public TargetHandler(Channel inChannel) {
this.inChannel = inChannel;
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.read();
}
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpResponse ) {
//record response
} else if (msg instanceof DefaultHttpResponse) {
// convert to FullHttpResponse ?
}
inChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
ctx.channel().read();
} else {
future.channel().close();
}
});
}
...
}
The HttpObjectAggregator in the source initializer pipeline causes no problems, and allows me to record the FullHttpRequest. If I comment pipeLine.addLast(new HttpObjectAggregator(1048576)); in the TargetTransportHandlerInitializer class the proxy works perfect. If I remove that comment the streaming calls begin to fail. Without the aggregator the msg object is only a DefaultHttpResponse for HTTP/1 calls and I can't access the body and headers.
Do I need some kind of conditional pipeline where streaming avoids the aggregator? Or is there some alternative way to construct a FullHttpResponse from a DefaultHttpResponse?
I was really hoping I could just do this FullHttpResponse full = HttpObjectAggregator.aggregate(default) and call it a day.
Netty's HTTP codec works in two distinct ways:
Aggregated: Use the object aggregator as you mention and it produces FullHttpRequest/FullHttpResponse objects.
Non-aggregated (streaming): It produces distinct messages for headers, payload and trailers, which are DefaultHttpRequest/Response, HttpContent and LastHttpContent.
The idiomatic netty way of converting messages is to add a handler in the pipeline, so you would not have APIs like FullHttpResponse full = HttpObjectAggregator.aggregate(default) but instead you will insert a handler after the HttpObjectAggregator and receive the FullHttpResponse object.
The above is an either/or choice, so if you add an aggregator, handlers after the aggregator will only see aggregated messages whereas before the aggregator, they will see the distinct messages as I mention above.
The following example demonstrates of how to handle streaming messages:
https://github.com/netty/netty/blob/4.1/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientHandler.java#L30
I was able to accomplish this by making a slight modification to the PortUnification example in the Netty project.
I updated the TargetTransportHandlerInitializer with the following:
#Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeLine = socketChannel.pipeline();
pipeLine.addLast("codec", new HttpClientCodec(102400, 102400, 102400));
pipeLine.addLast("switch", new SwitchHandler(inChannel));
}
SwitchHandler is a ChannelInboundHandlerAdapter with the following:
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (recordable(msg))
createRecordingChannel(ctx);
else
createStreamingChannel(ctx);
ctx.fireChannelRead(msg);
}
private void createRecordingChannel(ChannelHandlerContext ctx) {
ChannelPipeline p = ctx.pipeline();
p.addLast("aggregator", new HttpObjectAggregator(1048576));
p.addLast("recordingHandler", new RecordingTargetHandler(inChannel));
p.remove(this);
}
private void createStreamingChannel(ChannelHandlerContext ctx) {
ChannelPipeline p = ctx.pipeline();
p.addLast("simpleHandler", new SimpleTargetHandler(inChannel));
p.remove(this);
}
recordable() contains some business logic to look at the msg and make a determination. In my case was was looking at Headers.
My two new handlers look like the original TargetHandler, with different channelRead methods.
SimpleTargetHandler extends ChannelInboundHandlerAdapter with
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
inChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
ctx.channel().read();
} else {
future.channel().close();
}
});
}
RecordingTargetHandler extends SimpleTargetHandler with:
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpResponse ) {
// record response
}
super.channelRead(ctx, msg);
}
I'm following the akka java websocket tutorial in attempt to create a websocket server. I want to implement 2 extra features:
Being able to display the number of connected clients, but the result
is always 0 or 1 , even when I know I have 100's concurrently
connected clients.
Websocket communication is biDirectional. Currently the server only respond with a message when client sends a message. How do I initiate sending a message from server to client?
Here's original akka java server example code with minimum modification of my client counting implementation:
public class websocketServer {
private static AtomicInteger connections = new AtomicInteger(0);//connected clients count.
public static class MyTimerTask extends TimerTask {
//called every second to display number of connected clients.
#Override
public void run() {
System.out.println("Conncurrent connections: " + connections);
}
}
//#websocket-handling
public static HttpResponse handleRequest(HttpRequest request) {
HttpResponse result;
connections.incrementAndGet();
if (request.getUri().path().equals("/greeter")) {
final Flow<Message, Message, NotUsed> greeterFlow = greeter();
result = WebSocket.handleWebSocketRequestWith(request, greeterFlow);
} else {
result = HttpResponse.create().withStatus(413);
}
connections.decrementAndGet();
return result;
}
public static void main(String[] args) throws Exception {
ActorSystem system = ActorSystem.create();
TimerTask timerTask = new MyTimerTask();
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(timerTask, 0, 1000);
try {
final Materializer materializer = ActorMaterializer.create(system);
final Function<HttpRequest, HttpResponse> handler = request -> handleRequest(request);
CompletionStage<ServerBinding> serverBindingFuture =
Http.get(system).bindAndHandleSync(
handler, ConnectHttp.toHost("****", 1183), materializer);
// will throw if binding fails
serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS);
System.out.println("Press ENTER to stop.");
new BufferedReader(new InputStreamReader(System.in)).readLine();
timer.cancel();
} catch (Exception e){
e.printStackTrace();
}
finally {
system.terminate();
}
}
//#websocket-handler
/**
* A handler that treats incoming messages as a name,
* and responds with a greeting to that name
*/
public static Flow<Message, Message, NotUsed> greeter() {
return
Flow.<Message>create()
.collect(new JavaPartialFunction<Message, Message>() {
#Override
public Message apply(Message msg, boolean isCheck) throws Exception {
if (isCheck) {
if (msg.isText()) {
return null;
} else {
throw noMatch();
}
} else {
return handleTextMessage(msg.asTextMessage());
}
}
});
}
public static TextMessage handleTextMessage(TextMessage msg) {
if (msg.isStrict()) // optimization that directly creates a simple response...
{
return TextMessage.create("Hello " + msg.getStrictText());
} else // ... this would suffice to handle all text messages in a streaming fashion
{
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
}
}
//#websocket-handler
}
Addressing your 2 bullet points below:
1 - you need to attach your metrics to the Message flow - and not to the HttpRequest flow - to effectively count the active connections. You can do this by using watchTermination. Code example for the handleRequest method below
public static HttpResponse handleRequest(HttpRequest request) {
HttpResponse result;
if (request.getUri().path().equals("/greeter")) {
final Flow<Message, Message, NotUsed> greeterFlow = greeter().watchTermination((nu, cd) -> {
connections.incrementAndGet();
cd.whenComplete((done, throwable) -> connections.decrementAndGet());
return nu;
});
result = WebSocket.handleWebSocketRequestWith(request, greeterFlow);
} else {
result = HttpResponse.create().withStatus(413);
}
return result;
}
2 - for the server to independently send messages you could create its Message Flow using Flow.fromSinkAndSource. Example below (this will only send one message):
public static Flow<Message, Message, NotUsed> greeter() {
return Flow.fromSinkAndSource(Sink.ignore(),
Source.single(new akka.http.scaladsl.model.ws.TextMessage.Strict("Hello!"))
);
}
In the handleRequest method you increment and then decrement the counter connections, so at the end the value is always 0.
public static HttpResponse handleRequest(HttpRequest request) {
...
connections.incrementAndGet();
...
connections.decrementAndGet();
return result;
}
I am using the Channle pool API code written here:
ChannelPool section http://netty.io/news/2015/05/07/4-0-28-Final.html
EventLoopGroup group = new NioEventLoopGroup();
final Bootstrap cb = new Bootstrap();
InetSocketAddress addr1 = new InetSocketAddress("10.0.0.10", 8888);
InetSocketAddress addr2 = new InetSocketAddress("10.0.0.11", 8888);
cb.group(group).channel(NioSocketChannel.class);
ChannelPoolMap<InetSocketAddress, SimpleChannelPool> poolMap = new AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool>() {
#Override
protected SimpleChannelPool newPool(InetSocketAddress key) {
return new SimpleChannelPool(cb.remoteAddress(key), new TestChannelPoolHandler());
}
};
// depending on when you use addr1 or addr2 you will get different pools.
final SimpleChannelPool pool = poolMap.get(addr1);
Future<Channel> f = pool.acquire();
f.addListener(new FutureListener<Channel>() {
#Override
public void operationComplete(Future<Channel> f) {
if (f.isSuccess()) {
Channel ch = f.getNow();
// Do somethings
// ...
// ...
// Release back to pool
pool.release(ch);
}
}
});
As i see the code we never called .connect method so my question is when netty trying to connecting my channel to server ?
As you use SimpleChannelPool it will do connect when you call acquire and there is nothing left in the ChannelPool.
I am using netty for developing my server.
I am also implementing the Idle state handling in netty.
I got it working but an issue I recently found out.
I can't access the channel context attributes inside the userEventTriggered method.
here is my code and can anybody tell me why it is not possible.
I am setting it like
public static final AttributeKey<Agent> CLIENT_MAPPING = AttributeKey.valueOf("clientMapping");
...
ctx.attr(CLIENT_MAPPING).set(agent);
and inside handler, I am getting the value like (this is working perfectly)
Agent agent = ctx.attr(CLIENT_MAPPING).get();
But inside userEventTriggered it is returning null. (I am sure that it is set before this function is being called.)
public class Server
{
...
public void run() throws Exception
{
...
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).
channel(NioServerSocketChannel.class).
childHandler(new SslServerInitializer());
...
}
}
class SslServerInitializer extends ChannelInitializer<SocketChannel>
{
#Override
public void initChannel(SocketChannel ch) throws Exception
{
ChannelPipeline pipeline = ch.pipeline();
....
pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, Integer.parseInt(Main.configurations.get("netty.idleTimeKeepAlive.ms"))));
pipeline.addLast("idleTimeHandler", new ShelloidIdleTimeHandler());
}
}
class ShelloidIdleTimeHandler extends ChannelDuplexHandler
{
#Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception
{
if (evt instanceof IdleStateEvent)
{
try
{
// This I am getting null, but I confirmed that I set the attribute from my handler and is accessible inside handler.
Agent agt = ctx.attr(WebSocketSslServerHandler.CLIENT_MAPPING).get();
ctx.channel().writeAndFlush(new TextWebSocketFrame("{\"type\":\"PING\", \"userId\": \"" + agt.getUserId() + "\"}"));
}
catch (Exception ex)
{
ctx.disconnect();
ex.printStackTrace();
}
}
}
}
Are you sure you set and get it in the same ChannelHandler? If you want to set and get it in different ChannelHandler you need to use Channel.attr(...)
I am using netty HexDumpProxy example(using Netty 5 lib),in that I want to send some messages to server for each 40 seconds. How to achieve this using Netty.
Help me to solve this.
Update:
here is my initChannel method,
protected void initChannel(SocketChannel sc) throws Exception {
int readerIdleTimeSeconds = 5;
int writerIdleTimeSeconds = 5;
int allIdleTimeSeconds = 0;
ChannelPipeline pipe = sc.pipeline();
// pipe.addLast("rtspdecoder", new RtspRequestDecoder());
// pipe.addLast("rtspencoder", new RtspResponseEncoder());
// pipe.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
// pipe.addLast("encoder", new StringEncoder());
// pipe.addLast("decoder", new StringDecoder());
// pipe.addLast("idleStateHandler", new IdleStateHandler(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds));
// pipe.addLast("idleStateEventHandler", new MyIdlestaeEvtHandler());
pipe.addLast("decoder", new MyRtspRequestDecoder());
pipe.addLast("encoder", new MyRtspResponseEncoder());
pipe.addLast("handler", new PServerRequestHandler(remoteHost, remotePort));
pipe.addLast("idleStateHandler", new IdleStateHandler(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds));
pipe.addLast("idleStateEventHandler", new MyIdlestaeEvtHandler());
}
here is MyIdlestaeEvtHandler class,
public class MyIdlestaeEvtHandler extends ChannelDuplexHandler {
#Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
if(e.state() == IdleState.WRITER_IDLE) {
String s = "Ping Pong!";
ctx.channel().writeAndFlush(Unpooled.copiedBuffer(s.getBytes()));
System.err.println("writing idle------------------------");
} else if(e.state() == IdleState.READER_IDLE) {
System.err.println("reading idle------------------------");
String s = "Pong Pong!";
ctx.channel().writeAndFlush(Unpooled.copiedBuffer(s.getBytes()));
}
}
}
}
I am able to see the writing idle------------------------ but, the same is not passed to server, because I am not able to see this message in server debug messages.
Anything wrong with the code?
ThankYou
Netty 4.x provides the capability to schedule an instance of Runnable to be executed at a fixed rate using the scheduleAtFixedRate() method on a channel's eventloop.
see javadoc for scheduleAtFixedRate() in EventExecutorGroup
Example:
channel.eventLoop().scheduleAtFixedRate(new Runnable(){
#Override
public void run()
{
System.out.println("print a line every 10 seconds with a 5 second initial delay");
}
}, 5, 10, TimeUnit.SECONDS);
Use IdleStateHandler. For more informations please check its javadocs.