Follow me
RSS feed
My sources
My Viadeo

Déployer une application Capcode dans un serveur d'application Java

Greg | 27 Jan 2010

Projets Depuis la naissance de Capcode, je l'ai pas mal utilisé dans le cadre de mon travail. Je pense que ce petit framework arrive à maturité, ce qui devrait être (j'espère) prouvé avec la sortie de la version 1.0. Jusque-là, outre les inévitables corrections de bugs, je ne prévois pas d'ajouter de nouvelle fonctionnalité majeure pour me concentrer sur les problématiques de déploiement. Ce travail a déjà été initié en validant l'utilisation de Capcode avec Passenger. Je vous propose aujourd'hui de compléter ce chapitre en regardant comment déployer une application Capcode dans un serveur d'application Java.

JRuby-Rack et Warbler

JRuby-Rack est un adaptateur vous permettant d'exécuter une application basée sur Rack dans un container d'application Java. Tout cela sans rien changer au code de votre application. Warbler quant à lui est un gem qui vous permet de créer un fichier war pour votre application Ruby. Le premier est une dépendance du second, et nous nous concentrerons donc sur ce dernier.

Préparer l'application

Afin de vous montrer que la solution Warbler n'impose pratiquement aucun changement dans l'application, je vais partir de l'exemple REST présent dans les sources de Capcode.

Cet exemple est composé de deux fichiers : rest.rb et rest.ru. Le premier est le coeur de l'application. Le second est un fichier de configuration pour rackup. Cela tombe bien, car ce sont justement les deux éléments nécessaires pour utiliser JRuby-Rack.

Dans les exemples qui accompagnent les sources de JRuby-Rack, il semble y avoir une habitude pour l'organisation des sources. Le fichier de configuration de rackup (nommé config.ru) étant placé à la racine, le fichier de l'application est dans un répertoire lib et il existe un répertoire config dans lequel se trouve un fichier warble.rb. Si nous suivons ce modèle, nous devons donc faire trois petits changements :

  1. Réorganiser les fichiers de l'application de façon à obtenir ceci :
    rest
    |-- README_FIRST
    |-- config
    |-- rest.ru
    `-- lib
        `-- rest.rb
    
  2. Ayant placé le fichier rest.rb dans le répertoire lib, nous devons légèrement modifier le fichier rest.ru de façon à ce qu'il charge non plus rest mais lib/rest. Nous passons donc de ceci :
    require 'rest'
    
    run Capcode.application()
    
    A cela :
    require './lib/rest'
    
    run Capcode.application()
    
  3. Enfin, nous renommons le fichier rest.ru en config.ru.

Il nous manque maintenant le fameux fichier warble.rb. Ce fichier peut être généré automatiquement par Warbler. Ce dernier est, en effet, une surcouche de rake :

$ warble -T
rake config             # Generate a configuration file to customize your war assembly
rake pluginize          # Unpack warbler as a plugin in your Rails application
rake version            # Display version of warbler
rake war                # Create rest.war
rake war:app            # Copy all application files into the .war
rake war:clean          # Clean up the .war file and the staging area
rake war:exploded       # Create an exploded war in the app's public directory
rake war:gems           # Unpack all gems into WEB-INF/gems
rake war:jar            # Run the jar command to create the .war
rake war:java_classes   # Copy java classes into the .war
rake war:java_libs      # Copy all java libraries into the .war
rake war:public         # Copy all public HTML files to the root of the .war
rake war:war:java_libs  # Copy all java libraries into the .war
rake war:webxml         # Generate a web.xml file for the webapp
$

Il suffit donc de lancer la commande warble config pour obtenir le fichier config/warble.rb. Cette commande se contente de copier le fichier warble.rb situé dans le répertoire generators/warble/templates/ du gem Warbler dans le répertoire config de notre projet. Regardons donc ce qu'il contient :

 1 # Disable automatic framework detection by uncommenting/setting to false
 2 # Warbler.framework_detection = false
 3 
 4 # Warbler web application assembly configuration file
 5 Warbler::Config.new do |config|
 6   # Temporary directory where the application is staged
 7   # config.staging_dir = "tmp/war"
 8 
 9   # Application directories to be included in the webapp.
10   config.dirs = %w(app config lib log vendor tmp)
11 
12   # Additional files/directories to include, above those in config.dirs
13   # config.includes = FileList["db"]
14 
15   # Additional files/directories to exclude
16   # config.excludes = FileList["lib/tasks/*"]
17 
18   # Additional Java .jar files to include.  Note that if .jar files are placed
19   # in lib (and not otherwise excluded) then they need not be mentioned here.
20   # JRuby and JRuby-Rack are pre-loaded in this list.  Be sure to include your
21   # own versions if you directly set the value
22   # config.java_libs += FileList["lib/java/*.jar"]
23 
24   # Loose Java classes and miscellaneous files to be placed in WEB-INF/classes.
25   # config.java_classes = FileList["target/classes/**.*"]
26 
27   # One or more pathmaps defining how the java classes should be copied into
28   # WEB-INF/classes. The example pathmap below accompanies the java_classes
29   # configuration above. See http://rake.rubyforge.org/classes/String.html#M000017
30   # for details of how to specify a pathmap.
31   # config.pathmaps.java_classes << "%{target/classes/,}p"
32 
33   # Gems to be included. You need to tell Warbler which gems your application needs
34   # so that they can be packaged in the war file.
35   # The Rails gems are included by default unless the vendor/rails directory is present.
36   # config.gems += ["activerecord-jdbcmysql-adapter", "jruby-openssl"]
37   # config.gems << "tzinfo"
38 
39   # Uncomment this if you don't want to package rails gem.
40   # config.gems -= ["rails"]
41 
42   # The most recent versions of gems are used.
43   # You can specify versions of gems by using a hash assignment:
44   # config.gems["rails"] = "2.0.2"
45 
46   # You can also use regexps or Gem::Dependency objects for flexibility or
47   # fine-grained control.
48   # config.gems << /^merb-/
49   # config.gems << Gem::Dependency.new("merb-core", "= 0.9.3")
50 
51   # Include gem dependencies not mentioned specifically
52   config.gem_dependencies = true
53 
54   # Files to be included in the root of the webapp.  Note that files in public
55   # will have the leading 'public/' part of the path stripped during staging.
56   # config.public_html = FileList["public/**/*", "doc/**/*"]
57 
58   # Pathmaps for controlling how public HTML files are copied into the .war
59   # config.pathmaps.public_html = ["%{public/,}p"]
60 
61   # Name of the war file (without the .war) -- defaults to the basename
62   # of RAILS_ROOT
63   # config.war_name = "mywar"
64 
65   # Name of the MANIFEST.MF template for the war file. Defaults to the
66   # MANIFEST.MF normally generated by `jar cf`.
67   # config.manifest_file = "config/MANIFEST.MF"
68 
69   # Value of RAILS_ENV for the webapp -- default as shown below
70   # config.webxml.rails.env = ENV['RAILS_ENV'] || 'production'
71 
72   # Application booter to use, one of :rack, :rails, or :merb. (Default :rails)
73   # config.webxml.booter = :rails
74 
75   # When using the :rack booter, "Rackup" script to use.
76   # The script is evaluated in a Rack::Builder to load the application.
77   # Examples:
78   # config.webxml.rackup = %{require './lib/demo'; run Rack::Adapter::Camping.new(Demo)}
79   # config.webxml.rackup = require 'cgi' && CGI::escapeHTML(File.read("config.ru"))
80 
81   # Control the pool of Rails runtimes. Leaving unspecified means
82   # the pool will grow as needed to service requests. It is recommended
83   # that you fix these values when running a production server!
84   # config.webxml.jruby.min.runtimes = 2
85   # config.webxml.jruby.max.runtimes = 4
86 
87   # JNDI data source name
88   # config.webxml.jndi = 'jdbc/rails'
89 end

Vous me permettrez de faire l'impasse sur le détail complet des paramètres de ce fichier pour me concentrer uniquement sur ceux nécessaires dans le cas d'un déploiement d'application Capcode.

Ce fichier de configuration est prévu pour servir de base dans le cas d'une application Rails. Ainsi à la ligne 10, nous retrouvons la liste des répertoires d'une telle application. Dans notre cas, le seul répertoire utile est lib. Nous remplacerons donc cette ligne par ceci :

config.dirs = %w(lib)

Juste en dessous, nous avons le paramètre include permettant de lister les fichiers devant être ajoutés dans la webapp. Dans notre cas, seul le fichier config.ru est à prendre en compte. Nous ajoutons donc la ligne suivante :

config.includes = FileList["config.ru"]

Les lignes 33 à 49 vous expliquent comment préciser la dépendance de gem pour faire fonctionner notre application. Dans notre exemple, nous en avons besoin de trois : Capcode, capcode-render-markaby et mime-types. La dépendance avec mime-types étant directement héritée de Capcode. Nous ajoutons donc la ligne suivante dans le fichier :

config.gems = ['Capcode', 'capcode-render-markaby', 'mime-types']

Si vous continuez la lecture, vous verrez que le paramètre gem_dependencies est par défaut positionné à true. Ce qui indique que les dépendances de gem non spécifiées seront également prises en compte.

Les lignes 72-73 nous montrent comment indiquer le type de laucher à utiliser. Ce choix se faut entre rack, rails ou merb. Capcode étant basé sur rack, nous positionnerons ce paramètre de la façon suivante :

config.gems = ['Capcode', 'capcode-render-markaby', 'mime-types']

Et voilà. Nous sommes prêts à créer la webapp. Pour cela rien de plus simple :

$ warble
mkdir -p tmp/war/WEB-INF/gems/specifications
cp /Library/Ruby/Gems/1.8/specifications/Capcode-0.9.2.gemspec tmp/war/WEB-INF/gems/specifications/Capcode-0.9.2.gemspec
mkdir -p tmp/war/WEB-INF/gems/gems
cp /Library/Ruby/Gems/1.8/specifications/rack-1.1.0.gemspec tmp/war/WEB-INF/gems/specifications/rack-1.1.0.gemspec
cp /Library/Ruby/Gems/1.8/specifications/mime-types-1.16.gemspec tmp/war/WEB-INF/gems/specifications/mime-types-1.16.gemspec
cp /Library/Ruby/Gems/1.8/specifications/capcode-render-markaby-0.2.0.gemspec tmp/war/WEB-INF/gems/specifications/capcode-render-markaby-0.2.0.gemspec
cp /Library/Ruby/Gems/1.8/specifications/Markaby-0.6.5.gemspec tmp/war/WEB-INF/gems/specifications/Markaby-0.6.5.gemspec
cp /Library/Ruby/Gems/1.8/specifications/builder-2.1.2.gemspec tmp/war/WEB-INF/gems/specifications/builder-2.1.2.gemspec
mkdir -p tmp/war/WEB-INF/lib
cp lib/rest.rb tmp/war/WEB-INF/lib/rest.rb
cp config.ru tmp/war/WEB-INF/config.ru
cp /Library/Ruby/Gems/1.8/gems/warbler-0.9.14/lib/jruby-rack-0.9.5.jar tmp/war/WEB-INF/lib/jruby-rack-0.9.5.jar
cp /Library/Ruby/Gems/1.8/gems/warbler-0.9.14/lib/jruby-rack-0.9.5.jar tmp/war/WEB-INF/lib/jruby-rack-0.9.5.jar
cp /Library/Ruby/Gems/1.8/gems/jruby-jars-1.4.0/lib/jruby-core-1.4.0.jar tmp/war/WEB-INF/lib/jruby-core-1.4.0.jar
cp /Library/Ruby/Gems/1.8/gems/jruby-jars-1.4.0/lib/jruby-core-1.4.0.jar tmp/war/WEB-INF/lib/jruby-core-1.4.0.jar
cp /Library/Ruby/Gems/1.8/gems/jruby-jars-1.4.0/lib/jruby-stdlib-1.4.0.jar tmp/war/WEB-INF/lib/jruby-stdlib-1.4.0.jar
cp /Library/Ruby/Gems/1.8/gems/jruby-jars-1.4.0/lib/jruby-stdlib-1.4.0.jar tmp/war/WEB-INF/lib/jruby-stdlib-1.4.0.jar
mkdir -p tmp/war/WEB-INF
jar cf rest.war  -C tmp/war .
$

Comme vous pouvez le voir, Warbler se charge de créer notre webapp en y plaçant les fichiers de notre application, les gems dont elle dépend, mais également JRuby. Nous avons donc un fichier rest.war totalement portable et donc extrêmement facile à déployer.

Installer et déployer l'application

Dans cet exemple, j'utilise la version 6.0.20 de Tomcat. Je n'ai pas pris le temps de tester avec d'autre serveur Java, mais il n'y a pas de raison pour que cela ne fonctionne pas.

Pour installer l'application, il suffit simplement de copier le fichier rest.war dans le répertoire webapps de Tomcat. Le déploiement se fera de lui-même lors du lancement sur serveur. Pour cela, exécutez le script startup situé dans le répertoire bin de Tomcat. Si vous suivez en parallèle les logs inscrits dans logs/catalina.out, vous devriez, à un moment, voir apparaitre l'information suivante :

INFO: Déploiement de l'archive rest.war de l'application web
JRuby limited openssl loaded. gem install jruby-openssl for full support.
http://jruby.kenai.com/pages/JRuby_Builtin_OpenSSL
...
INFO: Server startup in 32519 ms

C'est parfait... Nous pouvons maintenant utiliser notre application. Pour cela, rendez-vous à l'adresse http://localhost:8080/rest

Si vous poussez le test, vous verrez rapidement que quelque chose ne fonctionne pas :

Capcode Warbler

Rassurez-vous, ceci s'explique très bien. En effet, dans l'application, au niveau des vues, nous utilisons la méthode URL pour générer les valeurs des champs action des formulaires. C'est une bonne pratique, mais qui, dans le cas présent renvoi sur l'url /action alors que, étant dans une webapp, cette route devrait en fait être /rest/action.

Heureusement, tout a été prévu. En effet, la méthode URL est prévue pour fonctionner avec un RACK_BASE_URI spécifique. Dans notre cas, il s'agit de /rest. Il suffit donc de positionner cette valeur avant de démarrer le serveur :

export RACK_BASE_URI=/rest
./bin/startup.sh
open http://localhost:8080/rest

Et cela fonctionne correctement !

Attention !

Lors de la rédaction de ce post, j'ai corrigé quelques bugs dans Capcode. Donc, à moins que la version 0.9.2 (ou supérieure) soit sortie, il faudra récupérer les sources pour tester cela.

Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.