#1771 Question about accessing java libraries (Netty) from Fantom

booleanman Sun 12 Feb 2012

I've been evaluating Fantom vs. Scala for writing a simple server using the Netty library as a server socket framework.

I was having problems getting the Fantom version to work, so I tried stripping it down to a simple echo server. You fire up the server, telnet to port 8080, send a string, and it gets echoed back. I made Java, Scala, and Fantom implementations:

Java

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class EchoHandler extends SimpleChannelHandler {

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
		Channel channel = e.getChannel();
		ChannelBuffer buffer = (ChannelBuffer)e.getMessage();
		StringBuilder strBuffer = new StringBuilder();

		while(buffer.readable()) {
			strBuffer.append( (char)buffer.readByte() );
		}

		String terms = strBuffer.toString();		
		channel.write(terms + "\n");
		channel.close();		
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
		e.getCause().printStackTrace();
		e.getChannel().close();
	}

}

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringEncoder;
import org.jboss.netty.util.CharsetUtil;

public class EchoServer {

	public static void main(String[] args) {
		NioServerSocketChannelFactory socketFactory = new NioServerSocketChannelFactory(
			Executors.newCachedThreadPool(),
			Executors.newCachedThreadPool()
		);

		ServerBootstrap bootstrap = new ServerBootstrap(socketFactory);

		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			public ChannelPipeline getPipeline() {
				ChannelPipeline pipeline = Channels.pipeline(new EchoHandler());
				pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
				return pipeline;
			}
		});

		bootstrap.setOption("child.tcpNoDelay", true);
		bootstrap.setOption("child.keepAlive", true);
		bootstrap.bind(new InetSocketAddress(8080));

	}

}

Scala:

import org.jboss.netty.channel.SimpleChannelHandler
import org.jboss.netty.channel.ChannelHandlerContext
import org.jboss.netty.channel.MessageEvent
import org.jboss.netty.buffer.ChannelBuffer
import org.jboss.netty.channel.ExceptionEvent

class EchoHandler extends SimpleChannelHandler {

	override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) {
		val channel = e.getChannel
		val buffer = e.getMessage.asInstanceOf[ChannelBuffer]
		val strBuffer = new StringBuilder

		while(buffer.readable()) {
			strBuffer.append( buffer.readByte.asInstanceOf[Char] )
		}

		val terms = strBuffer.toString		
		channel.write(terms + "\n")
		channel.close		
	}

	override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) {
		e.getCause.printStackTrace
		e.getChannel.close
	}

}

import java.net.InetSocketAddress
import java.util.concurrent.Executors
import org.jboss.netty.bootstrap.ServerBootstrap
import org.jboss.netty.channel.ChannelPipeline
import org.jboss.netty.channel.ChannelPipelineFactory
import org.jboss.netty.channel.Channels
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory
import org.jboss.netty.handler.codec.string.StringEncoder
import org.jboss.netty.util.CharsetUtil

object EchoServer {

  def main(args: Array[String]): Unit = {
    val socketFactory = new NioServerSocketChannelFactory(
			Executors.newCachedThreadPool,
			Executors.newCachedThreadPool
		)

    val bootstrap = new ServerBootstrap(socketFactory)

		bootstrap.setPipelineFactory( new ChannelPipelineFactory {
			def getPipeline = {
				val pipeline = Channels.pipeline(new EchoHandler)
				pipeline.addLast( "stringEncoder", new StringEncoder(CharsetUtil.UTF_8) )
				pipeline
			}
		})

		bootstrap.setOption("child.tcpNoDelay", true)
		bootstrap.setOption("child.keepAlive", true)
		bootstrap.bind( new InetSocketAddress(8080) )        
  }

}

Fantom:

using [java] org.jboss.netty.buffer
using [java] org.jboss.netty.channel

class ScrapeHandler : SimpleChannelHandler {
  override Void messageReceived(ChannelHandlerContext? ctx, MessageEvent? e) { 
    buffer := (ChannelBuffer)e.getMessage();
    terms := "";

    while(buffer.readable) { terms += buffer.readByte }
    echo(terms)
  }

  override Void exceptionCaught(ChannelHandlerContext? ctx, ExceptionEvent? e) { 
    e.getCause.printStackTrace
    e.getChannel.close
  }
}

using [java] org.jboss.netty.channel
using [java] org.jboss.netty.handler.codec.string
using [java] org.jboss.netty.util
using NettyTest::ScrapeHandler

class ScrapeChannelPipelineFactory : ChannelPipelineFactory {

  override ChannelPipeline? getPipeline() {
    handler := ScrapeHandler()

    pipeline := Channels.pipeline()
    pipeline.addFirst( "Scrape Handler", handler )
    pipeline.addLast( "String Encoder", StringEncoder(CharsetUtil.UTF_8) )

    return pipeline
  }

}

using [java] java.net::InetSocketAddress
using [java] java.util.concurrent
using [java] org.jboss.netty.bootstrap
using [java] org.jboss.netty.channel
using [java] org.jboss.netty.channel.socket.nio
using [java] org.jboss.netty.handler.codec.string
using NettyTest::ScrapeChannelPipelineFactory

class Main {
	static Void main() {
    port := 8080

    factory := NioServerSocketChannelFactory(
      Executors.newCachedThreadPool,
      Executors.newCachedThreadPool
    )

    bootstrap := ServerBootstrap(factory)   
    bootstrap.setPipelineFactory( ScrapeChannelPipelineFactory() )    
    bootstrap.setOption("child.tcpNoDelay", true)
    bootstrap.setOption("child.keepAlive", true)

    echo("Starting server on port $port")
    bootstrap.bind( InetSocketAddress(port) )    
	}
}

The first question: Is there a way to write anonymous inner classes with Fantom? In the Fantom version I had to use three classes to accomplish this. I'm assuming that an idiomatic Fantom library would use a closure rather than an inner class, but are anonymous classes possible?

The second question is: Why doesn't the Fantom implementation work? I'm using Fantom 1.0.61 on OS X:

Fantom Launcher
Copyright (c) 2006-2011, Brian Frank and Andy Frank
Licensed under the Academic Free License version 3.0

Java Runtime:
  java.version:    1.6.0_29
  java.vm.name:    Java HotSpot(TM) 64-Bit Server VM
  java.vm.vendor:  Apple Inc.
  java.vm.version: 20.4-b02-402
  java.home:       /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
  fan.platform:    macosx-x86_64
  fan.version:     1.0.61
  fan.env:         sys::BootEnv
  fan.home:        /Users/bman/Applications/fantom-1.0.61

When I run the Java or Scala implementations, a server socket is bound on port 8080 and the program listens for requests. In the Fantom version, the program exits. I can't tell if the socket is never opened or if it's opened then immediately closed. In any case, I can't get the Netty library to work with Fantom. I've tried implementing the socket call using a native class and a native peer, neither of which works. My last attempt was to package the working Java implementation as a Jar and just call the server in one line from a Fantom class. That also doesn't work.

Is there something I'm missing or doing wrong? Is there some inherent incompatibility at play here? Any insight would be appreciated. I really like what I've seen of Fantom so far.

SlimerDude Sun 12 Feb 2012

Hiya,

From what I can gather, no, Fantom does not support inner classes, anonymous or otherwise. I believe it's just top level classes, much as everything in a Pod is lumped together in the one package.

If you're implementing a Java interface, and it has just one method then, yes, you can substitute a closure. i.e. java.lang.Runnable - See here: JavaFFI : Functions as Interfaces

Like yourself I'm new to Fantom, am still exploring, and therefore possibly wrong!

Steve.

brian Sun 12 Feb 2012

When I run the Java or Scala implementations, a server socket is bound on port 8080 and the program listens for requests. In the Fantom version, the program exits. I can't tell if the socket is never opened or if it's opened then immediately closed.

I don't have Netty installed so can't test easily. In Fantom when you main exits it actually does a real force exit. So I suspect you just need to add an infinite sleep to the end of your program:

Actor.sleep(Duration.maxVal)

Fantom doesn't support inner classes largely for two reasons: 1) you often use closures and b) to keep type namespace simple and flat (to avoid overloads of the dot operator)

booleanman Sun 12 Feb 2012

Calling Actor.sleep(Duration.maxVal) did the trick. Also adding while (true) {} seems to work too. Thanks for the info!

brian Sun 12 Feb 2012

By the way, you don't have to put all your classes into different source files like Java (you can put them all into one source file).

SlimerDude Sun 12 Feb 2012

Too true. I've recently found my self packaging similar / meaningful classes up into the same source file. Much as I would have made them inner classes in Java.

And this is fine, I can't ever think of a situation in Java where I've named 2 classes the same name and needed class resolution to distinguish between them.

dobesv Tue 28 Feb 2012

Actually I think the reason they match the class name to the file name in Java is that you can easily find the file containing a certain class you're curious about. It's about easier code navigation.

However, IDEs do a really good job of indexing your code base these days so it doesn't really seem like that decision pays off. Plus programmers get good at searching around anyway.

This netty thing looks cool, I think it would be nice if Fantom used NIO to watch client connections instead of a thread per connection like it does now (for Weblets).

Using Netty with weblets might be one way to do that.

Login or Signup to reply.