The lightweight REST framework for java called Restlet (http://www.restlet.org/) is able to handle JSON. Cool no need for the heavyweight called XML.
So here is an example of how you make Ruby on Rails call a webservice created with Restlet.
First have a look at the HTTP library for ruby. Do not forget to install the JSON library for Ruby.
And here is the ruby code, I am connecting with my Restlet example.
require 'net/http'
require 'json'
....
def show_user(id=nil)
return nil if id == nil
url = URI.parse("http://localhost:8100/users/#{id}")
req = Net::HTTP::Get.new(url.path)
# We want JSON returned
req['Accept'] = 'application/json'
resp = Net::HTTP.new(url.host, url.port).start {|http| http.request(req)}
if !resp.kind_of?(Net::HTTPSuccess) then
case resp
when Net::HTTPBadRequest
raise ArgumentError
when Net::HTTPInternalServerError
raise Error, "Server error"
when Net::HTTPClientError
raise Error, "Client error"
end
end
data = resp.body
return nil if data.nil?
results = JSON.parse(data)
return results
end
Labels: Restlet, Ruby on Rails
Restlet is a great, lightweight REST framework for Java developers. The only problem, there are not that many examples on how to use this framework available.
Some examples are included after installing the package under the src/org.restlet.example directory. But these examples are simple starting points and do not fully describe how to create a full REST web service with create, update and delete functionality.
I am using the 1.1 M4 version of restlet. The API documentation is available here.
The example code works but does not execute any database lookup. To make this example more useful, it is recommended to replace the :TODO lines with your database lookup.
First, create a simple User class. Which contains a variable id and name.
import java.nio.Buffer;
import org.json.JSONObject;
public class User {
private String id = null;
private String name = null;
/**
* @return Returns the id.
*/
public String getId() {
return id;
}
/**
* @param id
* The id to set.
*/
public void setId(String id) {
this.id = id;
}
/**
* @return Returns the name.
*/
public String getName() {
return name;
}
/**
* @param name
* The name to set.
*/
public void setName(String name) {
this.name = name;
}
/**
* Convert this object to a JSON object for representation
*/
public JSONObject toJSON() {
try{
JSONObject jsonobj = new JSONObject();
jsonobj.put("id", this.id);
jsonobj.put("name", this.name);
return jsonobj;
}catch(Exception e){
return null;
}
}
/**
* Convert this object to a string for representation
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("id:");
sb.append(this.id);
sb.append(",name:");
sb.append(this.name);
return sb.toString();
}
}
We also need some kind of error object to represent an error message. This is a very simple example
import org.json.JSONObject;
public class ErrorMessage {
public JSONObject toJSON() {
JSONObject jsonobj = new JSONObject();
try {
jsonobj.put("error", "An error occured");
return jsonobj;
} catch (Exception e) {
return null;
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("error:");
sb.append("An error occured");
return sb.toString();
}
}
Now, let's create our UserResource.
import org.restlet.Context;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.ext.json.JsonRepresentation;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.StringRepresentation;
import org.restlet.resource.Variant;
public class UserResource extends Resource {
private User user = null;
public UserResource(Context context, Request request, Response response) {
super(context, request, response);
String userid = null;
userid = (String) getRequest().getAttributes().get("id");
this.user = findUser(userid);
getVariants().add(new Variant(MediaType.TEXT_PLAIN));
getVariants().add(new Variant(MediaType.APPLICATION_JSON));
}
/**
* Allow a PUT http request
*
* @return
*/
public boolean allowPut() {
return true;
}
/**
* Allow a POST http request
*
* @return
*/
public boolean allowPost() {
return true;
}
/**
* Allow a DELETE http request
*
* @return
*/
public boolean allowDelete() {
return true;
}
/**
* Allow the resource to be modified
*
* @return
*/
public boolean setModifiable() {
return true;
}
/**
* Allow the resource to be read
*
* @return
*/
public boolean setReadable() {
return true;
}
/**
* Find the requested user object
*
* @param userid
* @return
*/
private User findUser(String userid) {
try {
if (null == userid)
return null;
// :TODO {do some database lookup here }
// user = result of lookup
// This part should be replaced by a lookup
User u = new User();
u.setId("1");
u.setName("name");
// end replace
return u;
} catch (Exception e) {
return null;
}
}
/**
* Represent the user object in the requested format.
*
* @param variant
* @return
* @throws ResourceException
*/
public Representation represent(Variant variant) throws ResourceException {
Representation result = null;
if (null == this.user) {
ErrorMessage em = new ErrorMessage();
return representError(variant, em);
} else {
if (variant.getMediaType().equals(MediaType.APPLICATION_JSON)) {
result = new JsonRepresentation(this.user.toJSON());
} else {
result = new StringRepresentation(this.user.toString());
}
}
return result;
}
/**
* Handle a POST Http request. Create a new user
*
* @param entity
* @throws ResourceException
*/
public void acceptRepresentation(Representation entity)
throws ResourceException {
// We handle only a form request in this example. Other types could be
// JSON or XML.
try {
if (entity.getMediaType().equals(MediaType.APPLICATION_WWW_FORM,
true)) {
Form form = new Form(entity);
User u = new User();
u.setName(form.getFirstValue("user[name]"));
// :TODO {save the new user to the database}
getResponse().setStatus(Status.SUCCESS_OK);
// We are setting the representation in the example always to
// JSON.
// You could support multiple representation by using a
// parameter
// in the request like "?response_format=xml"
Representation rep = new JsonRepresentation(u.toJSON());
getResponse().setEntity(rep);
} else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
}
} catch (Exception e) {
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
}
/**
* Handle a PUT Http request. Update an existing user
*
* @param entity
* @throws ResourceException
*/
public void storeRepresentation(Representation entity)
throws ResourceException {
try {
if (null == this.user) {
ErrorMessage em = new ErrorMessage();
Representation rep = representError(entity.getMediaType(), em);
getResponse().setEntity(rep);
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return;
}
if (entity.getMediaType().equals(MediaType.APPLICATION_WWW_FORM,
true)) {
Form form = new Form(entity);
this.user.setName(form.getFirstValue("user[name]"));
// :TODO {update the new user in the database}
getResponse().setStatus(Status.SUCCESS_OK);
// We are setting the representation in this example always to
// JSON.
// You could support multiple representation by using a
// parameter
// in the request like "?response_format=xml"
Representation rep = new JsonRepresentation(this.user.toJSON());
getResponse().setEntity(rep);
} else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
}
} catch (Exception e) {
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
}
/**
* Handle a DELETE Http Request. Delete an existing user
*
* @param entity
* @throws ResourceException
*/
public void removeRepresentations()
throws ResourceException {
try {
if (null == this.user) {
ErrorMessage em = new ErrorMessage();
Representation rep = representError(MediaType.APPLICATION_JSON, em);
getResponse().setEntity(rep);
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return;
}
// :TODO {delete the user from the database}
getResponse().setStatus(Status.SUCCESS_OK);
} catch (Exception e) {
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
}
}
/**
* Represent an error message in the requested format.
*
* @param variant
* @param em
* @return
* @throws ResourceException
*/
private Representation representError(Variant variant, ErrorMessage em)
throws ResourceException {
Representation result = null;
if (variant.getMediaType().equals(MediaType.APPLICATION_JSON)) {
result = new JsonRepresentation(em.toJSON());
} else {
result = new StringRepresentation(em.toString());
}
return result;
}
protected Representation representError(MediaType type, ErrorMessage em)
throws ResourceException {
Representation result = null;
if (type.equals(MediaType.APPLICATION_JSON)) {
result = new JsonRepresentation(em.toJSON());
} else {
result = new StringRepresentation(em.toString());
}
return result;
}
}
And implement a restlet server, listening on port 8100.
import org.restlet.Application;
import org.restlet.Component;
import org.restlet.Context;
import org.restlet.Restlet;
import org.restlet.Router;
import org.restlet.data.MediaType;
import org.restlet.data.Protocol;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.StringRepresentation;
public class WebServiceApplication extends Application {
public static void main(String[] args) throws Exception {
// Create a component
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8100);
WebServiceApplication application = new WebServiceApplication(
component.getContext());
// Attach the application to the component and start it
component.getDefaultHost().attach(application);
component.start();
}
public WebServiceApplication() {
super();
}
public WebServiceApplication(Context context) {
super(context);
}
@Override
public Restlet createRoot() {
Router router = new Router(getContext());
router.attach("/users", UserResource.class);
router.attach("/users/{id}", UserResource.class);
Restlet mainpage = new Restlet() {
@Override
public void handle(Request request, Response response) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<html>");
stringBuilder
.append("<head><title>Sample Application Servlet Page</title></head>");
stringBuilder.append("<body bgcolor=white>");
stringBuilder.append("<table border=\"0\">");
stringBuilder.append("<tr>");
stringBuilder.append("<td>");
stringBuilder.append("<h1>2048Bits.com example - REST</h1>");
stringBuilder.append("</td>");
stringBuilder.append("</tr>");
stringBuilder.append("</table>");
stringBuilder.append("</body>");
stringBuilder.append("</html>");
response.setEntity(new StringRepresentation(stringBuilder
.toString(), MediaType.TEXT_HTML));
}
};
router.attach("", mainpage);
return router;
}
}You can test the restlet web service with any internet browser except for the PUT and DELETE requests. I prefer curl to test a Restful web service.
GET REQUEST - Show the information of a user:
curl http://localhost:8100/users/1POST REQUEST - Create a new user:
curl -d "user[name]=John" http://localhost:8100/usersPUT REQUEST - Update an existing user:
curl -X PUT -d "user[name]=Jane" http://localhost:8100/users/1DELETE REQUEST - Delete an existing user:
curl -X DELETE http://localhost:8100/users/1
Installing Ruby on Rails on CentOS 4.5 provided by GoDaddy.com is definitely not for the weak of heart.
First of all, Apache HTTP Server. Sure there are alternatives available which are faster, easier, and maybe better, but none are as popular as Apache. The version I have installed is 2.2.8.
Next, Ruby on Rails, version 2.0. One of my new favorites. Not ideal for all your programming projects but a lot better than most web frameworks out there.
And third, MySQL. Installed on a separate machine. You should never install your database on your web server machine.
Last, some packages necessary for some cool RoR gems and plug-ins. This is up to you, but is not really necessary for standard RoR except maybe the mysql gem.
1. Mysql gem
2. RMagick, this one was a pain in the derrière.
3. ar_mailer
A. Starting with Apache HTTP Server 2.0.x
Uninstall any default installed versions: yum remove httpd
Download the latest version of Apache HTTP Server and compile with the following settings:
install apache 2 with : ./configure --enable-deflate --enable-proxy --enable-proxy-html --enable-proxy-balancer --enable-rewrite --enable-cache --enable-mem-cache --enable-ssl --enable-headers
Create a user apache and group apache. Add
User apacheto the httpd.conf file. The default installation directory is /usr/local/apache2. The httpd.conf file can be found under the conf subdirectory.
Group apache
Create a new conf.d subdirectory under /usr/local/apache2, or in the install directory on your machine if you changed the default install path.
Add the following in your httpd.conf file:
Include conf.d/*.confUnder this new directory create the following files.
I like to create first a .common file which contains all the common settings for your web applications.
e.g. mywebserver.common
RewriteEngine On
# Uncomment for rewrite debugging
#RewriteLog logs/myapp_rewrite_log
#RewriteLogLevel 9
# Check for maintenance file and redirect all requests
# ( this is for use with Capistrano's disable_web task )
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]
# Rewrite index to check for static
RewriteRule ^/$ /index.html [QSA]
# Rewrite to check for Rails cached page
RewriteRule ^([^.]+)$ $1.html [QSA]
# Redirect all non-static requests to cluster
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L]
# Deflate
AddOutputFilterByType DEFLATE text/html text/plain text/css
# ... text/xml application/xml application/xhtml+xml text/javascript
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# Uncomment for deflate debugging
#DeflateFilterNote Input input_info
#DeflateFilterNote Output output_info
#DeflateFilterNote Ratio ratio_info
#LogFormat '"%r" %{output_info}n/%{input_info}n (%{ratio_info}n%%)' deflate
#CustomLog logs/myapp_deflate_log deflate
# this not only blocks access to .svn directories, but makes it appear
# as though they aren't even there, not just that they are forbidden
<DirectoryMatch "^/.*/\.svn/">
ErrorDocument 403 /404.html
Order allow,deny
Deny from all
Satisfy All
<DirectoryMatch>
And next create a .conf file for your app. This file also contains the settings for the Mongrel cluster.
e.g. myapp.conf
As a bonus, this is my start and stop script for Apache HTTP server. If you did not use the default install directory, it will need some tweaking.><VirtualHost www.myapp.com:80>
ServerName www.myapp.com
DocumentRoot /var/www/myapp/current/public
<Directory "/var/www/myapp/current/public">
Options FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</DirectoryInclude /usr/local/apache2/conf.d/mywebserver.common
ErrorLog logs/myapp_errors_log
CustomLog logs/myapp_log combined
# Proxy balancer
<Proxy balancer://mongrel_cluster>
BalancerMember http://127.0.0.1:8000
BalancerMember http://127.0.0.1:8001
BalancerMember http://127.0.0.1:8002
</Proxy>
</VirtualHost>
#!/bin/bash
#
# httpd Startup script for the Apache HTTP Server
#
# chkconfig: - 85 15
# description: Apache is a World Wide Web server. It is used to serve \
# HTML files and CGI.
# processname: httpd
# config: /usr/local/apache2/conf/httpd.conf
# config: /usr/local/apache2/httpd
# pidfile: /var/run/httpd.pid
# Source function library.
. /etc/rc.d/init.d/functions
if [ -f /usr/local/apache2/ ]; then
. /usr/local/apache2/
fi
# Start httpd in the C locale by default.
HTTPD_LANG=${HTTPD_LANG-"C"}
# This will prevent initlog from swallowing up a pass-phrase prompt if
# mod_ssl needs a pass-phrase from the user.
INITLOG_ARGS=""
# Set HTTPD=/usr/sbin/httpd.worker in /etc/sysconfig/httpd to use a server
# with the thread-based "worker" MPM; BE WARNED that some modules may not
# work correctly with a thread-based MPM; notably PHP will refuse to start.
# Path to the apachectl script, server binary, and short-form for messages.
apachectl=/usr/local/apache2/bin/apachectl
httpd=${HTTPD-/usr/local/apache2/bin/httpd}
prog=httpd
pidfile=${PIDFILE-/var/run/httpd.pid}
lockfile=${LOCKFILE-/var/lock/subsys/httpd}
RETVAL=0
# check for 1.3 configuration
check13 () {
CONFFILE=/usr/local/apache2/conf/httpd.conf
GONE="(ServerType|BindAddress|Port|AddModule|ClearModuleList|"
GONE="${GONE}AgentLog|RefererLog|RefererIgnore|FancyIndexing|"
GONE="${GONE}AccessConfig|ResourceConfig)"
if LANG=C grep -Eiq "^[[:space:]]*($GONE)" $CONFFILE; then
echo
echo 1>&2 " Apache 1.3 configuration directives found"
echo 1>&2 " please read /usr/share/doc/httpd-2.0.52/migration.html"
failure "Apache 1.3 config directives test"
echo
exit 1
fi
}
# The semantics of these two functions differ from the way apachectl does
# things -- attempting to start while running is a failure, and shutdown
# when not running is also a failure. So we just do it the way init scripts
# are expected to behave here.
start() {
echo -n $"Starting $prog: "
check13 || exit 1
LANG=$HTTPD_LANG daemon $httpd $OPTIONS
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch ${lockfile}
return $RETVAL
}
# When stopping httpd a delay of >10 second is required before SIGKILLing the
# httpd parent; this gives enough time for the httpd parent to SIGKILL any
# errant children.
stop() {
echo -n $"Stopping $prog: "
killproc -d 10 $httpd
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
}
reload() {
echo -n $"Reloading $prog: "
if ! LANG=$HTTPD_LANG $httpd $OPTIONS -t >&/dev/null; then
RETVAL=$?
echo $"not reloading due to configuration syntax error"
failure $"not reloading $httpd due to configuration syntax error"
else
killproc $httpd -HUP
RETVAL=$?
fi
echo
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status $httpd
RETVAL=$?
;;
restart)
stop
start
;;
condrestart)
if [ -f ${pidfile} ] ; then
stop
start
fi
;;
reload)
reload
;;
graceful|help|configtest|fullstatus)
$apachectl $@
RETVAL=$?
;;
*)
echo $"Usage: $prog {start|stop|restart|condrestart|reload|status|fullstatus|graceful|help|configtest}"
exit 1
esac
exit $RETVAL
B. Installing Ruby on Rails
First of all I assume you know where to find all the packages and how to compile a program in linux.
Download and install readline from source, this package a prerequisite for RoR. Install default in
/usr/localDownload Ruby's source. Again remove any pre-installed version:
yum remove rubyConfigure and compile:
./configure --enable-pthread --with-readline-dir=/usr/localDownload and install ruby gems from source:
ruby setup.rb
And
sudo gem install rails
C. MySQL
Because your database server should not be installed on the same machine as your web server.
You should only install the client and development packages from MySQL on this machine. I used yum for this.
yum install mysql
yum install mysql-devel
Installed Packages
mysql.i386 5.0.48-1.el4.centos installed
Installed Packages
mysql-devel.i386 5.0.48-1.el4.centos installed
D. And last, all those nice gems.
1. Mysql gem
gem install mysql -- --with-mysql-config=/usr/bin/mysql_config2. RMagick
First, we need all the ImageMagick dependencies
yum install ImageMagickyum remove ImageMagickThere is probably a better way to get the dependencies.
Second, download and install ghostscript fonts:
ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/ghostscript-fonts-std-8.11.tar.gz
Install:
sudo tar xvf ghostscript-fonts-std-8.11.tar.gz -c /usr/local/shareNext. download and compile ghostcript:
http://pages.cs.wisc.edu/~ghost/
And, download ImageMagick and compile with following settings:
./configure --disable-static --with-modules --without-perl --without-magick-plus-plus --with-quantum-depth=8 --with-gs-font-dir=/usr/local/share/fonts
Last, install the gem:
gem install rmagick3. ar_mailer:
You will need this if you want to send bulk emails through must SMTP servers.
gem install ar_mailer
create a cronjob, this one runs every 3 minutes:
*/3 * * * * /usr/local/bin/ar_sendmail -o -e production -c /var/www/myapp/current/ > /home/user/ar_mailsender.log 2>&1
E. Final note.
I assumed your Ruby on Rails app will be installed under /var/www/myapp.
This post did not describe anything about the mongrel_cluster, a good link for information on how to install the mongrel cluster, the article is about Mac OS X, but will also work for CentOS 4.5: http://blog.gwikzone.org/articles/2006/10/02/easy-setup-a-mongrel-cluster-with-apache-2-2-on-mac-os-x
Labels: CentOS 4.5, ImageMagick, Ruby on Rails