Showing posts with label Tomcat. Show all posts
Showing posts with label Tomcat. Show all posts

Thursday, October 25, 2007

JRuby and Rails in the Enterprise

At work, management is finally getting the message about dynamic languages like Ruby and starting to recognize that frameworks like Rails may have productivity benefits.  We're actually going to look at doing a pilot project.  I like to think that I've had some influence in that but you never know...

Anyway, I would love to make this work but this is a Java shop through and through.  We can only deploy to a Java web container, specifically Weblogic 8.1 (the transition to Weblogic 10 is happening but it's months away).  I've used JRuby (works great BTW) and the JRuby extras to help me deploy an app to Tomcat, but I can't manage to get JRuby and Rails working in Weblogic 8.1.  Any ideas?

Secondly, Groovy and Grails is the other contender.  I like the idea behind Groovy and Grails but I can't help feel that it's a pale imitation of Ruby and Rails.  Anybody have enough experience to compare and share?

Finally, I'm not naive enough to think that this will cure all the ills of working in the "big enterprise".  When I think about all the things that bug me, the technology is probably the smallest thing that causes me pain.  But on the other hand, this would be a wonderful distraction. :-)

Thursday, May 03, 2007

Aptana RADRails & JRuby

I thought I'd go and take a look at what's been happening on the RADRails project lately. As it turns out, quite a bit! Aptana has taken over the project and are making quite a bit a progress integrating it with their Eclipse based IDE. The most impressive thing I saw was refactoring support! Fantastic! It's great to see RADRails getting some attention again. Go download the beta.

Then I went over to the JRuby project to see what they've been up to. JRuby 0.9.9 is out and looks very good. The Java integration is quite good. But I've been mostly interested in seeing support for deploying a Rails app in a JEE Web Container. Well nestled in beside the JRuby TAR file is a file called sample-rails-warfile.tar.gz. I downloaded it and pretty quickly had a rails application running in Tomcat. The app doesn't do much of anything but it's still pretty impressive to see it. My hope is that this kind of integration will help to get Ruby & Rails adopted into some of Java-only environment I tend to work in. The next thing I did was build my own Rails app and deploy it in Tomcat. I also tried deploying it in WebLogic but didn't have as much luck. Obviously there's still some work to do. Check out the JRuby WIKI for details.

Saturday, January 06, 2007

Java & Ruby HTTP Clients

In a mythical IT universe designed around a service oriented architecture (SOA) you could assemble several loosely coupled, autonomous services into a business solution. Each service would communicate in a platform and technology agnostic manner and XML would be the lingua franca. For example, if you needed to integrate data from several business partners you could call various SOAP or RESTful services, get structured XML, and then transform the results into something you could use.

But unfortunately that's not what happens in the real world. Instead of nice composable services you usually get web sites targeted at people not machines. That means you need to automate what would typically be browser conversations with various websites to get the data you need. And then you need to deal with the format of the resulting data. If you're lucky, the data may be structured in a comma separated value (CSV) text file. But undoubtedly you'll have to parse unstructured text representations of a report or get the data out of an excel spreadsheet or even a PDF. It's not pretty.

But lets forget that unpleasantness for the moment and deal with the first problem you'll encounter in trying to automate a browser conversation, getting by the various authentication mechanisms. Let's look at BASIC authentication first. It's pretty common and well supported in the Java-based Jakarta Commons HttpClient library and the Ruby Net::HTTP Standard Library.

BASIC Authentication

To start things off I created a simple Java-based "Dynamic Web Project" using the Eclipse Web Tools Project (WTP) plugins. To keep things simple I created a servlet that returns a string. Then I configured the application to protect the url for that servlet with basic authentication in web.xml. Then in Tomcat's server.xml I modified the context element for my web app to include a reference to the default Tomcat in-memory user database:
<Context docBase="MyWebApp" path="/MyWebApp" [...]
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
debug="0" resourceName="UserDatabase"/>
</Context>
With that in place, and the server running we're able to write a couple of methods to access the servlet. In Java:
public static void basicAuthDemo()
throws HttpException, IOException{
HttpClient client = new HttpClient();
List<String> authPrefs = new ArrayList<String>();
authPrefs.add(AuthPolicy.BASIC);
client.getParams().setParameter(
AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);

client.getState().setCredentials(
new AuthScope("localhost", 8080, "localhost:8080"),
new UsernamePasswordCredentials("tomcat", "tomcat")
);

GetMethod get = new GetMethod(
"http://localhost:8080/MyWebApp/myservlet");
get.setDoAuthentication(true);
client.executeMethod(get);
System.out.println(get.getResponseBodyAsString());
get.releaseConnection();
}
In this example I limited HttpClient's default authentication mechanism to BASIC. I know what my target system uses so why complicate matters with DIGEST or NTLM? Then it was a simple matter of defining the credentials and telling HttpClient to automatically use them and then executing the Http GET method. It looks surprisingly similar in Ruby:
def basic_auth_demo
url = URI.parse('http://localhost:8080/MyWebApp/myservlet')
get = Net::HTTP::Get.new(url.path)
get.basic_auth('tomcat','tomcat')
response = Net::HTTP.new(url.host, url.port).start do |http|
http.request(get)
end
puts response.body
end
The difference between the two implementations is that the Java HttpClient is doing some housekeeping for you. You define a scope for your authentication and as long as you GetMethod is configured to do authentication it will automatically pick up any necessary credentials from the HttpClient instance. In Ruby you need to set the credentials on the GetMethod explicitly.

FORM based authentication

The next most common method is form-based authentication. When you make a request for a web resource, the response contains a cookie that identifies your session on the server. If that session indicates that you haven't been authenticated yet, then you're redirected to a form to enter your id and password. You fill in the values and then submit the form. Now assuming you entered the right credentials your session on the server will indicate that you're authenticated and every subsequent request (which includes the cookie to identify your now authenticated session) will execute normally. There are variations on this theme that may add more than one cookie so just be sure to capture the cookies and continue to submit them on every request in your conversation.

In order to test this I modified the web.xml file for my Java web app:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login-error.jsp</form-error-page>
</form-login-config>
</login-config>
and added the requisite JSP pages. The login.jsp contains a form that looks like this:
<form method="POST" action="j_security_check">
Username:<input type="text" name="j_username"><br/>
Password:<input type="password" name="j_password"><br/>
<input type=submit value="Login">
</form>
So the Java code to access the servlet using form based authentication looks like this:
public static void formAuthDemo()
throws IOException, HttpException {
HttpClient client = new HttpClient();

// make the initial get to get the JSESSION cookie
GetMethod get = new GetMethod(
"http://localhost:8080/MyWebApp/myservlet");
client.executeMethod(get);
get.releaseConnection();

// authorize
PostMethod post = new PostMethod(
"http://localhost:8080/MyWebApp/j_security_check");
NameValuePair[] data = {
new NameValuePair("j_username", "tomcat"),
new NameValuePair("j_password", "tomcat")
};
post.setRequestBody(data);
client.executeMethod(post);
post.releaseConnection();

//resubmit the original request
client.executeMethod(get);
String response = get.getResponseBodyAsString();
get.releaseConnection();
System.out.println(response);
}
The Ruby code looks like this:
def form_auth_demo
res = Net::HTTP.new('localhost', 8080).start do |http|
#make the initial get to get the JSESSION cookie
get = Net::HTTP::Get.new('/MyWebApp/myservlet')
response = http.request(get)
cookie = response.response['set-cookie'].split(';')[0]

#authorize
post = Net::HTTP::Post.new('/MyWebApp/j_security_check')
post.set_form_data({'j_username'=>'tomcat', 'j_password'=>'tomcat'})
post['Cookie'] = cookie
http.request(post)

#resubmit the original request
get['Cookie'] = cookie
response = http.request(get)
puts response.body
end
end
Again, the two implementations are remarkably similar. The biggest difference is that the Java HttpClient library is again doing the housekeeping, by tracking and automatically resubmitting the cookies for you. In the Ruby code you have to fetch the cookie yourself from the response header and set the HTTP header for all future requests.

So there you go, you're past the website authentication and are ready to make whatever requests you need to get the data you require.

Digg!

Wednesday, May 25, 2005

Tomcat and IIS

I've been busy lately deploying my latest Java web application to production. It's been a bit of an adventure since I don't usually get to play the deployer role. Fortunately I already had my Ant script building the WAR (Web ARchive) file so I was only working on deploying to Tomcat 5.5.9. The twist is that I was deploying to four instances of Tomcat on two physical Windows 2003 servers fronted by the latest version of IIS.

The only reason IIS is involved at all is so that I could use the JK 1.2 Tomcat Connector to do the load balancing. This connector works as an ISAPI Filter and intercepts requests to my appliction (based on the URI) and forwards them on to an instance of Tomcat running my application. The connector does a weighted round robin load balancing and ensures that a request that is tied to a particular session on a particular Tomcat instance (from a previous request) is directed back to that same Tomcat instance.

But perhaps I'm a bit ahead of myself. Let's back up to the Tomcat installations. The first thing I wanted to accomplish was the sharing of the Tomcat JAR files among the various instances of Tomcat. By doing that, I know all Tomcat instances are running the same version and upgradable in one shot. The steps were:
  1. Unzip jakarta-tomcat-5.5.9.zip
  2. Rename jakarta-tomcat-5.5.9 to tomcat for simpler upgrading in the future (if only Windows had symbolic links)
  3. Create a directory called tomcat-cluster-node1 that looks something like this (the files in the subdirectories were copied from the standard Tomcat installation):


/tomcat (renamed from jakarta-tomcat-5.5.9)
/tomcat-cluster-node1 (The subirectories' files are copied from the tomcat
installation.)
/bin (contains service.bat)
/conf (contains catalina.policy, catalina.properties, context.xml,
server.xml, tomcat-users.xml, web.xml)
/Catalina
/localhost (contains manager.xml, host-manager.xml, myapp.xml)
/logs (empty)
/temp (empty)
/webapps (empty)
/work (empty)

Because this installation is on a Windows server I decided to follow the adage "When in Rome, do as the Romans do" and make my Tomcat instances run as Windows Services. Fortunately Tomcat is ready to go with a Tomcat5.exe and a batch script to create the registry entries call Service.bat. The only changes I had to make to the batch file were a few lines at the top to set some environment variables:

set CATALINA_BASE=E:\tomcat-cluster-node1
set CATALINA_HOME=E:\tomcat
set JAVA_HOME=E:\jdk1.5.0_03

then I ran "Service.bat install Node1" and I now have a service that I can easily start and stop. You may want to change the service to "Automatic" to ensure that it gets restarted in the event of a server restart.

Now back to the Tomcat connector installation. I like keeping the redirector stuff separate from the tomcat installation so I created a directory called /tomcat-iis-connector that contains the following files:

isapi_redirect.dll (downloaded from the jakarta site)
isapi_redirect.reg (see below)
uriworkermap.properties (start with a copy from the tomcat/conf directory)
workers.properties (ditto)

The reg file contains some registry entries that the DLL uses at runtime to find your configuration information. Mine looks like this:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Jakarta Isapi Redirector\1.0]
"extension_uri"="/jakarta/isapi_redirect.dll"
"log_file"="e:\\tomcat-cluster-node1\\logs\\isapi.log"
"log_level"="error"
"worker_file"="e:\\tomcat-iis-connector\\workers.properties"
"worker_mount_file"="e:\\tomcat-iis-connector\\uriworkermap.properties"

Now you have to actually tell IIS about the redirector DLL (notice the /jakarta/ prefix in the extension_uri entry in the reg file. This becomes important in a moment)
  1. Open IIS Manager and create a new Web Site.
  2. Create a virtual directory called "jakarta". This name must be the same as the prefix from the extension_uri value in the registry, and it must be executable.
  3. Go to the "ISAPI Filters" tab of your website's properties dialog and add a filter that points to the isapi_redirect.dll
  4. In the "Web Sites" properties dialog's Service tab select the "Run WWW service in IIS5.0 isolation mode" checkbox.
  5. Disable session state for your website (Properties dialog->Home Directory tab-> Configuration...->Options tab). Just let Tomcat handle your session timeouts.

Now all you have to do is configure your workers.properties file. Mine looks like this:

workers.tomcat_home=E:\tomcat
workers.java_home=E:\jdk1.5.0_03

ps=
worker.node1.port=8009
worker.node1.host=localhost
worker.node1.type=ajp13
worker.node1.cachesize=10
worker.node1.cache_timeout=600
worker.node1.socket_keepalive=1
worker.node1.recycle_timeout=300

worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=node1
worker.loadbalancer.sticky_session=1

worker.list=loadbalancer

And then in the cluster-tomcat-node1/conf/server.xml make sure you identify the server with the same name you used when defining the worker in workers.properties, in this case "node1". Without this the "sticky sessions" won't work.

<engine name="Catalina" defaulthost="localhost" jvmroute="node1">

At this point I've only described the installation of a single Tomcat instance, but in reality I have four tomcat instances. So you can imagine more tomcat-cluster-node directories and a few more entries in workers.properties and the addition of those worker names to worker.loadbalancer.balanced_workers:

worker.loadbalancer.balanced_workers=node1, node2, node3, node4

Next you must define the URIs that should get redirected from IIS to Tomcat in the uriworkermap.properties file. Mine looks like this (of course you'll need to change the value of "mycontext" to the context of your app:

/mycontext/*.jsp=loadbalancer
/mycontext/*.do=loadbalancer
/mycontext/*.exe=loadbalancer
/mycontext/*.jpg=loadbalancer
/mycontext/*.gif=loadbalancer
/mycontext/*.png=loadbalancer
/mycontext/*.css=loadbalancer
/mycontext/*.js=loadbalancer
/mycontext/*.jpeg=loadbalancer
/mycontext/*=loadbalancer

Normally, I would like IIS to serve all my static content but I use struts tags to specify the location of images and the JSTL c:url tag to dynamically generate URLs. These tags rewrite the URL and not only include the name of the context (which is configurable at deployment time) but also include the jsessionid in the URL when not using cookies. Unfortunately IIS can't figure out what to do with the jsessionid so your pages come back without CSS or images. Not only do they look pretty awful but if you use js files some functionality may not even work. So I just map everything to Tomcat. On the plus side, deployment is simpler with the single WAR file.

Finally restart IIS and hope everything works! :-)