|
PicnicTutorial
Step-by-step tutorial describing how to build a Picnic-enabled Camping app
IntroductionPicnic is a set of extensions for Camping. It is assumed that if you're using Picnic, you're building a Camping app. If you're not familliar with Camping, have a look here and here. Camping, on its own is a great little framework. It's especially useful for building small HTTP-based services, where Rails just doesn't make sense. A good example of just this sort of Camping app is RubyCAS-Server. RubyCAS-Server doesn't need Rails' extensive user interface niceties, since it's main purpose is to act as an HTTP service accessible behind the scenes by other services. Rails also has some serious problems that make it unusable for certain purposes. For example, Rails controllers are not concurrent, meaning that if a Rails controller is busy doing something, it can't respond to other requests. Generally for the kinds of applications Rails is good at this isn't an issue, but it can be a big problem for network services that rely on transactional conversations between each other. Camping does not have this limitation, making it perfect for building network service applications. Unfortunately, Camping does have its shortcomings. For one, deploying a Camping app in a production context as part of a bigger Linux infrastructure is problematic. Secondly, developing a Camping-based app and then distributing it as a stand-alone application via the normal Ruby mechanisms (i.e. RubyGems) is not easy. Enter Picnic. From the ground up, Picnic is designed to make it easy to:
The hope is that thanks to Picnic, Ruby developers can start easily building simple network applications, proliferating Ruby as the tool of choice for the service-oriented architecture approach. Picnic also works well with Reststop, making it possible to easily build RESTful services in Camping. If this sounds like something that might be useful to you, read on. PrerequisitesAs mentioned above, it is assumed that you already have some familiarity with Camping. If you're familiar with Camping, then you're also probably reasonably proficient with Ruby and know how to deal with RubyGems. This tutorial also assumes that you're on a Linux-based system with Ruby and RubyGems already installed. Most of these instructions will almost certainly work just fine on MacOS X and Windows, but you may have to do some translating. To start, you should install the Picnic and newgem gems: gem install picnic gem install newgem newgem is a collection of scripts for generating a skeleton Ruby application, easily packagable as a RubyGem. If you're familliar with Rails, newgem is similar to the rails script, but instead of generating a starter Rails app, it will generate a starter RubyGem, complete with a Rakefile and some starter code. Generating the SkeletonIn this tutorial we will take the canonical Camping Blog app, and Picnic-fy it. We want our Blog application to be distributable as a gem, and we want end users to be able to run it with minimal configuration as a stand-alone daemon. First, lets use the negem script to generate a skeleton directory structure: newgem blog Okay, now we've got a blog directory. Inside, it looks something like this: blog/CHANGELOG.txt blog/History.txt blog/README.txt blog/lib blog/lib/blog blog/lib/blog.rb blog/lib/blog/version.rb blog/bin blog/Rakefile blog/Manifest.txt blog/test blog/test/test_helper.rb blog/test/blog_test.rb blog/examples blog/setup.rb Open up the blog/Rakefile with a text editor, and modify the various values to your likings (i.e. change the DESCRIPTION, EMAIL, etc.) Towards the very bottom of the Rakefile, you'll see a line like this: #p.extra_deps We need to add Picnic as a dependency, so uncomment this line and change it to: p.extra_deps = ['picnic'] Adding Picnic CodeNow we're ready to Picnic-fy our application. Open blog/lib/blog.rb. You'll see some starter code in there. Delete it, and replace it with the following: $: << File.dirname(File.expand_path(__FILE__)) $APP_NAME ||= 'blog' $APP_ROOT ||= File.expand_path(File.dirname(__FILE__)+'/..') require 'rubygems' require 'picnic' require 'picnic/logger' Camping.goes :Blog Picnic::Logger.init_global_logger! # Our Camping application's implementation will go here... The actual implementation of the Camping application will go between the Blog.picnic! and Blog.start_picnic calls. Adding Camping CodeWe're going to use the code from http://code.whytheluckystiff.net/camping/browser/trunk/examples/blog.rb. However, one of the nice features Picnic gives us is the ability to read global settings from a user-defined configuration file (by default, this file is /etc/<app name>/config.yml). So, we're going to slightly modify the Blog code to load certain values from the configuration. For example, we're going to let the user specify the name of their blog using the configuration file. Note the call to Blog::Conf[:blog_title]. Also, we will load the database connection info from the configuration file and establish this connection in our app's create method: def Blog.create Blog::Models::Base.establish_connection Blog::Conf[:database] # ... end To see what the finished blog/lib/blog.rb file looks like, download, the complete example Picnic application from http://camping-picnic.googlecode.com/files/excample_picnic_app.tar.gz. THIS SAMPLE APP NEEDS TO BE UPDATED FOR PICNIC 0.8! Note that as with any other Camping application, it is possible to split up your Controllers, Views, and Models modules into separate files. Just put these under lib/blog/controllers.rb, lib/blog/views.rb, and lib/blog/models.rb, and require them in lib/blog.rb somewhere after your Camping.goes :Blog call. The bin/blog ScriptWe want to let end-users run their blog as a stand-alone application. Ideally the user should be able to install your gem and then just run blog from the command line. This is exactly what we're going to do. First, create a bin directory in your blog application. Inside this, create bin/blog and make it executable: cd blog mkdir bin touch bin/blog chmod +x bin/blog Now paste in the following code into your bin/blog file: #!/usr/bin/env ruby require 'rubygems' require 'picnic/cli' cli = Picnic::Cli.new( 'blog', :app_file => File.expand_path(File.dirname(File.expand_path(__FILE__))+"/../lib/blog.rb") ) cli.handle_cli_input Before we test our bin script, we need to create the database for our app. If you're using mysql, try something like this: mysqladmin -u root create blog You may need to add the -p flag to the above if your root account requires as password. Now try running bin/blog from your app's root directory. You should find that your application starts up. However, it will complain about a missing example configuration file. We'll address this in the next step. Creating a Default Configuration FileEvery Picnic app is configured via a YAML-based conf file. At the very least, this file will tell Picnic what web server to use (webrick or mongrel) and what port to run on. If your application uses a database, you'll also want to load your connection settings from here. The configuration file is essentially just a big Hash, so feel free to store application-specific settings here as well. Since we want our applications to run out-of-the-box, with minimal user configuration, every Picnic app should include an example config file. This should always be called config.example.yml and should sit in your application's root directory. The first time the user runs your app, Picnic will automatically copy the example config into /etc/<your app>/config.yml (although the end user can change this location using the --config option). Lets create an example config file for our blog application. It's a good idea to comment this file liberally so that the end user can figure out how to modify it for their needs. Here is our config.example.yml file: # This is a configuration file for the Blog daemon. # AN IMPORTANT NOTE ABOUT YAML CONFIGURATION FILES: # !!! Be sure to use spaces instead of tabs for indentation, as YAML is very # !!! sensitive to white-space inconsistencies! ##### HTTP SERVER ##################################################################### # Under what HTTP environment are you running the Fluxr server? The following methods # are currently supported: # # webrick -- simple stand-alone HTTP server; this is the default method # mongrel -- fast stand-alone HTTP server; much faster than webrick, but # you'll have to first install the mongrel gem # ### webrick example server: webrick port: 8106 ### webrick SSL example #server: webrick #port: 443 #ssl_cert: /path/to/your/ssl.pem # if the private key is separate from cert: #ssl_key: /path/to/your/private_key.pem ### mongrel example #server: mongrel #port: 8106 # It is possible to run mongrel over SSL, but you will need to use a reverse proxy # (try Pound or Apache). ##### DATABASE ######################################################################## # The Blog needs a database to store its records. # # By default, we use MySQL, since it is widely used and does not require any additional # ruby libraries besides ActiveRecord. # # With MySQL, your config would be something like the following: # (be sure to create the blog database in MySQL beforehand, # i.e. `mysqladmin -u root create blog`) database: adapter: mysql database: blog username: root password: host: localhost # Instead of MySQL you can use SQLite3, PostgreSQL, MSSQL, or anything else supported # by ActiveRecord. # # If you do not have a database server available, you can try using the SQLite3 # back-end. SQLite3 does not require it's own server. Instead all data is stored # in local files. For SQLite3, your configuration would look something like the # following (don't forget to install the 'sqlite3-ruby' gem first!): # #database: # adapter: sqlite3 # dbfile: /var/lib/blog.db ##### BLOG ############################################################################ # This sets the title that should appear at the top of our Blog pages. blog_title: My Blog ##### LOGGING ######################################################################### # This log is where you'll want to look in case of problems. # # By default, we will try to create a log file named 'blog.log' in the current # directory (the directory where you're running the blog from). A better place to put # the log is in /var/log, but you will have to run blog as root or otherwise give # it permissions. # # Set the level to DEBUG if you want more detailed logging. Other options are # INFO, WARN, and ERROR (DEBUG is most verbose, ERROR is least). log: file: blog.log level: DEBUG # file: /var/log/blog.log # level: INFO Okay, now if you try running bin/blog, you should see something like this: Adding Picnic functionality to Blog from /usr/lib/ruby/gems/1.8/gems/picnic-0.0.0.14/lib... Loading configuration for Blog from '/etc/blog/config.yml'... BLOG SERVER HAS NOT YET BEEN CONFIGURED!!! Attempting to copy sample configuration from '/home/URBACON/mzukowski/blog/bin/../config.example.yml' to '/etc/blog/config.yml'... It appears that you do not have permissions to create the '/etc/blog/config.yml' file. Try running this command using sudo (as root). The application complains about not being able to write the config file. By default, Picnic will try to put the configuration file in /etc/blog/config.yml, but we need root privileges to write to this directory. Either run the script as root (sudo bin/blog) or specify a different location for the configuration file (bin/blog -c ~/blog.yml). Now you should get a message about the sample configuration being created. Modify the generated config file if necessary, and run again. This time the server should launch, and your Blog application should be accessible at http://localhost:8106 (or whatever port you specified in your configuration file). init.d and the bin/blog-ctl ScriptThe bin/blog script we created above is great for launching your application from the command line, but if we want our app to function as part of a Linux server, we'll have to integrate it into Linux's init.d mechanism for managing services. To this end we'll create a bin/blog-ctl script. This takes care of launching your bin/blog as a background daemon process, and provides a command line interface similar to apachectl. Create a the file bin/blog-ctl and make sure it's executable (chmod +x bin/blog-ctl). Paste in the following code: #!/usr/bin/env ruby
require 'rubygems'
require 'picnic/service_control'
ctl = Picnic::ServiceControl.new('blog')
ctl.handle_cli_inputYou should now be able to use the blog-ctl script to start and stop the server from the command line using blog-ctl start and bloc-ctl stop. We can then make use of this inside an init.d script. We can make use of this for init.d like so:
#!/bin/sh
CTL=foo-ctl
. /etc/rc.status
rc_reset
case "$1" in
start)
$CTL start
rc_status -v
;;
stop)
$CTL stop
rc_status -v
;;
restart)
$0 stop
$0 start
rc_status
;;
status)
$CTL status
rc_status -v
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
exit 1
;;
esac
rc_exitYou should now be able to launch your application like any other init.d script (just make sure that foo-ctl is installed in your executable PATH -- in the next section we'll discuss how to do this automatically). Note that there is currently no way to have the init.d automatically installed for the end user. You will have to provide instructions for doing this in your end-user documentation. You can (and should) however include the app-ctl script with your app, since this makes launching your application as a daemon much easier for the end user. Packaging Your Picnic App as a RubyGemIn order to make our Picnic app easily distributable, we'll want to package it as a RubyGem. Because we used newgem to generate our application's skeleton, the process of packing our application into a gem is easy. The one thing left to do is make some final changes to our app's Rakefile and then update the Manifest.txt file to include all of the files we want package into the gem. Open up blog/Rakefile. Towards the bottom, you'll see the following line: #p.spec_extras - A hash of extra values to set in the gemspec. Uncomment this, and change it to: p.spec_extras = {:executables => ['blog', 'blog-ctl']}This will ensure that our two bin scripts are installed into the end user's /usr/bin directory, so that they are executable just like any other system-installed application. Now we need to update the Manifest.txt file. This file contains a list of directories and files that should be packed into the gem. Without changes, it will probably look something like this: Rakefile README.txt CHANGELOG.txt Manifest.txt setup.rb lib/blog/version.rb lib/blog.rb test/test_helper.rb test/blog_test.rb We should add to this our bin scripts and the config.example.yml file. This can be done automatically using the following command: rake check_manifest | patch Manifest.txt - Your updated Manifest.txt file should now look like this: CHANGELOG.txt History.txt Manifest.txt README.txt Rakefile bin/blog bin/blog-ctl config.example.yml lib/blog.rb lib/blog/version.rb setup.rb test/blog_test.rb test/test_helper.rb Now we're ready to package our app into a gem: rake package This will generate pkg/blog-0.0.1.gem. Go ahead and install this gem so that we can test it out: sudo gem install pkg/blog-0.0.1.gem You should now find that you can run the blog and blog-ctl commands directly from your bash shell. That's it. Our application is fully packaged and ready for distribution! Note that the version number attached to your gem is set in your lib/version.rb file. You should increment this anytime you release a new version of the gem. Publishing Your App to RubyForgeAs a final step, you may want to make your app distributable via RubyForge. This will let people install your application just by typing: gem install blog RubyForge maintains the default gem repository from which the gem command obtains its list of packages. To register your app with RubyForge you will have to:
Once your gem is uploaded, you'll have to wait approximately one hour for RubyForge to update its gem index. At that point your application should become installable as a remote gem. |
Sign in to add a comment
The service_control.rb file has some TODO's in it that I've completed in order to allow the rubycas-server rubycas-server-ctl script to work. Here is the updated service_control.rb file:
require 'optparse'
module Picnic
# # Changed 'state=' to 's=' so that this works on Solaris # # The following line modified by Shane Swartz # state = ps -p #{pid} -o state=.strip end