Friday, April 3, 2009

JRuby VoIP app on JBoss 5 reloaded - Look Ma ! My pure JRuby app can now receive phone calls !

Following on the previous blog (My JRuby-Rails app on JBoss and Mobicents can make Phone calls !), we took things a step further in the JRuby Telco integration with JBoss 5 and Mobicents Sip Servlets.

We thought this was a hassle to have to create a multi language jruby-java application for pure rubyists to be able to calls in their application, so we decided to remove the Java part altogether and allow the application to be a pure Ruby application handling VoIP to benefit from runtime modification (without having to redeploy anything) to cut development time drastically and in addition to that have all the benefits (Media support, Diameter support, STUN, advanced monitoring, clustering, failover, ...) of Mobicents Sip Servlets for free :-)

Note that this application will be bundled with our next 0.9 release that should be out very soon.

So let's go through a quick walk-through on how to do that in redoing the same application as in the previous blog post but this time pure Ruby :


The code source of the application is available here.
For the hackers that want to create it themselves here are the steps :

So let's create the application skeleton :
$ jruby -S rails pure-jruby-telco -d mysql

Go into the “pure-jruby-telco" directory, then modify the config/database.yml.
Adjust the adapter name, and instead of ‘mysql’ put ‘jdbcmysql’. You might also want to delete the lines starting with “socket:” or set it to tmp dir.

Here’s a simple example for the development environment:
development:
adapter: jdbcmysql
encoding: utf8
database: pure-jruby-telco_development
pool: 5
username: root
password:
socket: /tmp/mysqld.sock

Also edit the config/environment.rb to specify the gem dependency we have on the jdbcmysql adapter (this step is mandatory for freezing the dependencies in your app later on)


Rails::Initializer.run do |config|
...
config.gem "activerecord-jdbcmysql-adapter", :version => '0.9', :lib => 'active_record/connection_adapters/jdbcmysql_adapter'
...
end

Now, it’s time to create our database:


$ jruby -S rake db:create:all

The next step is to create some minimal scaffolding to create the complaint system

$ jruby script/generate scaffold Complaint customer_name:string company:string complaint:text sip_uri:string

$ jruby -S rake db:migrate

now, we will add the logic to make the phone call once a complaint has been created, to do that edit app/controllers/complaints_controller.rb and the create function should look like this :


def create
@complaint = Complaint.new(params[:complaint])

respond_to do |format|
if @complaint.save
# get the sip factory from the servlet context
@sip_factory = $servlet_context.get_attribute('javax.servlet.sip.SipFactory')
# create a new sip application session
@app_session = request.env['java.servlet_request'].get_session().get_application_session();
# create a new sip servlet request to start a call to the sip phone with from header equals to "sip:my_jruby_app_rocks@mobicents.org" and the to header equals to the sip_uri from the complaint
@sip_request = @sip_factory.create_request(@app_session, 'INVITE', 'sip:my_jruby_app_rocks@mobicents.org', @complaint.sip_uri);
# actually sending the request out to the sip phone
@sip_request.send();

flash[:notice] = 'Complaint was successfully created.'
format.html { redirect_to(@complaint) }
format.xml { render :xml => @complaint, :status => :created, :location => @complaint }
else
format.html { render :action => "new" }
format.xml { render :xml => @complaint.errors, :status => :unprocessable_entity }
end
end


Ok we are done with the web part that make phone calls, let's make the app able to handle phone calls, create a sip directory :
mkdir app/sip 


This directory will contain our sip controllers to handle SIP messages, so let's create such a controller, by adding a sip_handler.rb file, to the app/sip directory, containing the following code :


# Note that the class extend a JBoss provided sip controller called JBoss::Sip::SipBaseHandler
# that mimic the Java Sip Servlet class the ruby way
class SipHandler < JBoss::Sip::SipBaseHandler
# Handle INVITE request to setup a call by answering 200 OK
def do_invite(request)
request.create_response(200).send
end
# Handle BYE request to tear down a call by answering 200 OK
def do_bye(request)
request.create_response(200).send
end
# Handle REGISTER request so that a SIP Phone can register with the application by answering 200 OK
def do_register(request)
request.create_response(200).send
end
# Handle a successful response to an application initiated INVITE to set up a call (when a new complaint is filed throught the web part) by send an acknowledgment
def do_success_response(response)
response.create_ack.send
end
end


Please read the comments in the above code, they should be insightful on what's going on.
Ok that's it the app can now receive phone calls and handle the signaling part :-)

Let's prepare the application for deployment to the Mobicents Sip Servlets on top of JBoss 5 app server.

Now let's freeze the rails version and the associated gems dependencies we are using into our application so that if rails or a dependency is upgraded in the system, our application will always use the version we freezed and not the newly upgraded version of rails from the system. Note: this is highly recommended for production env and really is a best practice (google freeze rails for more information)
Note that freezing is mandatory if you wish to deploy your application to JBoss 5 with the jboss-rails plugin.

It is a 2 steps process, first freeze rails then the dependencies (in our case the jdbcmysql adapter)
Here is the command to freeze your rails application :
$ jruby -S rake rails:freeze:gems
Here is the command
$ jruby -S rake gems:unpack:dependencies
You can verify that it worked by issuing this command
$ jruby -S rake gems
That will produce the following output :

- [F] activerecord-jdbcmysql-adapter = 0.9
- [F] activerecord-jdbc-adapter = 0.9
- [F] jdbc-mysql = 5.0.4

I = Installed
F = Frozen
R = Framework (loaded before rails starts)


Let's add jboss-rails-support to the application so that it can be deployed and run on JBoss 5 and even start JBoss 5 from the commandline, grab the following zip and extract it to the vendor/plugins directory of our application.

Now grab Mobicents Sip Servlets latest binary snapshot and extract it to any location that suits you and set JBOSS_HOME env variable to it.

Then in JBOSS_HOME/server/default/deploy create a file called pure-jruby-telco.yml containing :

---
application:
RAILS_ENV: development
RAILS_ROOT: /home/deruelle/workspaces/mobicents-sip-servlets/sip-servlets-examples/pure-jruby-telco
web:
context: /jruby-telco
sip:
appname: PureJRubyTelcoApplication
rubycontroller: SipHandler


change the RAILS_ROOT in it to the location of your application.

Then let's roll and fire up the server, from the root directory of the application do
$ rake jboss:as:run


When the server has started go to the Mobicents Sip Servlets management console and for INVITE and REGISTER select PureJRubyTelcoApplication in the select box then click 'Save'. This will instruct the Mobicents Sip Servlets container to route INVITE and REGISTER requests to our JRuby application.

You're ready to test the application. Starts your favorite Sip Phone (wengo phone, linphone, ekiga, sip communicator, ...) and configure it to register to 127.0.0.1:5080 then go to http://localhost:8080/pure-jruby-telco/complaints

Create a new complaint and make sure that in the sip uri field you put the address of the sip phone as shown here


Now enjoy your first JRuby Rails Sip-Servlets application making a call to your sip phone.
Note that with some more coding and a VoIP provider such as http://www.callwithus.com, it could call real land-line phones or cell phones.

This application doesn't play any media yet so you hangup the phone whenever you like.
You can also call the application in dialing sip:pure-jruby-telco@127.0.0.1:5080 :-)