Making ZXTM more RAD with Jython Extensions
Java isn't my first choice of languages, especially when it comes to web development. And while I think Scala is cool, I prefer not to have to compile my web code. My language of choice in this sphere is Python; and thanks to the Jython project we can write our extensions in Python! (Note: If you're particularly impatient, or already an expert, there's a checklist at the end of this article summarising the steps required to get Jython running on ZXTM.) Jython comes with a servlet named PyServlet which will work with ZXTM out-of-the-box, this is a good place to start. First let us quickly set up our Jython environment, working Java support is a prerequisite of course. (If you're not already familiar with ZXTM Java extensions I recommend reviewing the Java Development Guide available from the Diagnose > Technical Support section of the ZXTM UI, you can also find the ZXTM 5.1 documentation here. It is also worth reading over some of our other KnowledgeHub articles on the topic.)
In the Java Extensions Catalog you can now see a couple of extensions provided by jython.jar, including org.python.util.PyServlet. By default this servlet maps URLs ending in .py to local Python files which it will compile and cache. Set up a test HTTP Virtual Server (I've created a test one called "Jython" tied to the "discard" pool), and add the following request rule to it: if (string.endsWith(http.getPath(), ".py")) { java.run( "org.python.util.PyServlet" ); } The rule avoids errors by only invoking the extension for .py files. (Though if you invoke PyServlet for a .py file that doesn't exist you will get a nasty NullPointerException stack trace in your event log.) Next, create a file called Hello.py containing the following code: from javax.servlet.http import HttpServlet class Hello(HttpServlet): def doGet(self, request, response): toClient = response.getWriter() response.setContentType ("text/html") toClient.println("<html><head><title>Hello from Python</title>" + "<body><h1 style='color:green;'>Hello from Python!</h1></body></html>") Upload Hello.py to your ZXTM's Java Extensions Catalog. Now if you visit the file Hello.py at the URL of your VirtualServer you should see the message "Hello from Python!" Hey, we didn't have to compile anything! Your event log will have some messages about processing .jar files, this only happens on first invocation. You will also get a warning in your event log every time you visit Hello.py: WARN java/* servleterror Servlet org.python.util.PyServlet: Unknown attribute (pyservlet) This is just because ZXTM doesn't set up or use this particular attribute. We'll ignore the warning for now, and get rid of it when we tailor PyServlet to be more convenient for ZXTM use. The servlet will also have created some some extra files in your Java Libraries & Data Catalog under a top-level directory WEB-INF. It is possible to change the location used for these files, we'll get back to that in a moment. All quite neat so far, but the icing on the cake is yet to come. If you open up $ZEUSHOME/conf/jars/Hello.py in your favourite text editor and change the message to "Hello <em>again</em> from Python!" and refresh your browser you'll notice that the new message comes straight through. This is because the PyServlet code checks the .py file, if it is newer than the cached bytecode it will re-interpret and re-cache it. We now have somewhat-more-rapid application development for ZXTM extensions. Bringing together the excellence of Python and the extensiveness of Java libraries. However, what I really want to do is use TrafficScript at the top level to tell PyServlet which Python servlet to run. This will require a little tweaking of the code. We want to get rid of the code that resolves .py file from the request URI and replace it with code that uses an argument passed in by TrafficScript. While we're at it, we'll make it non-HTTP-specific and add some other small tweaks. The changes are documented in comments in the code, which you can download here: ZeusPyServlet.java You can download my compiled version if you want to avoid compiling the code yourself. Alternatively, to compile the ZXTM version of the servlet and pop the two classes into a single convenient .jar file execute the following commands. javac -cp $ZEUSHOME/zxtm/lib/servlet.jar:/space/jython/jython.jar ZeusPyServlet.java jar -cvf ZeusPyServlet.jar ZeusPyServlet.class ZeusPyServletCacheEntry.class (Adjusting paths to suit your environment if necessary.) Upload the ZeusPyServlet.jar file to your ZXTM's Java Extensions Catalog. You should now have a ZeusPyServlet extension available. Change your TrafficScript rule to load this new extension and provide an appropriate argument. if (string.endsWith(http.getPath(), ".py")) { java.run( "ZeusPyServlet", "Hello.py" ); } Now visiting Hello.py works just as before. In fact, visiting any URL that ends in .py will now generate the same result as visiting Hello.py. We have complete control over what Python code is executed from our TrafficScript rule, much more convenient. If you continue hacking from this point you'll soon find that we're missing core parts of python with the setup described so far. For example adding import md5 to your servlet code will break the servlet, you'd see this in your ZXTM Event Log:
WARN servlets/ZeusPyServlet Servlet threw exception javax.servlet.ServletException:
Exception during init of /opt/zeus/zws/zxtm/conf/jars/ServerStatus.py
WARN Java: Traceback (most recent call last):
WARN Java: File "/opt/zeus/zws/zxtm/conf/jars/ServerStatus.py", line 2, in <module>
WARN Java: import md5
WARN Java: ImportError: No module named md5
This is because the class files for the core Python libraries are not included in jython.jar. To get a fully functioning Jython we need to tell ZeusPyServlet where Jython is installed. To do this you must have Jython installed on the same machine as the ZXTM software, and then you just have to set a configuration parameter for the servlet, in summary:
Now your Jython should be fully functional, here's a script for you to try that uses MD5 functionality from both Python and Java. Just replace the content of Hello.py with the following code. from javax.servlet.http import HttpServlet from java.security import MessageDigest from md5 import md5 class Hello(HttpServlet): def doGet(self, request, response): toClient = response.getWriter() response.setContentType ("text/html") htmlOut = "<html><head><title>Hello from Python</title><body>" htmlOut += "<h1>Hello from Python!</h1>" # try a Python md5 htmlOut += "<h2>Python MD5 of 'foo': %s</h2>" % md5("foo").hexdigest() # try a Java md5 htmlOut += "<h2>Java MD5 of 'foo': " jmd5 = MessageDigest.getInstance("MD5") digest = jmd5.digest("foo") for byte in digest: htmlOut += "%02x" % (byte & 0xFF) htmlOut += "</h2>" # yes, the Zeus attributes are available htmlOut += "<h2>VS: %s</h2>" % request.getAttribute("virtualserver") htmlOut += "</body></html>" toClient.println(htmlOut) The expected output when you visit a .py URL is roughly (the styling will be a bit different): Hello from Python!
Python MD5 of 'foo': acbd18db4cc2f85cedef654fccc4a4d8
Java MD5 of 'foo': acbd18db4cc2f85cedef654fccc4a4d8
VS: Jython
An important point to realise about Jython is that beyond the usual core Python APIs you cannot expect all the 3rd party Python libraries out there to "just work". Non-core Python modules compiled from C (and any modules that depend on such modules) are the main issue here. For example the popular Numeric package will not work with Jython. Not to worry though, there are often pure-Python alternatives. Don't forget that you have all Java libraries available too; and even special Java libraries designed to extend Python-like APIs to Jyhon such as JNumeric, a Jython equivalent to Numeric. There's more information on the Jython wiki. I recommend reading through all the FAQs as a starting point. It is perhaps best to think of Jython as a language which gives you the neatness of Python syntax and the Python core with the utility of the massive collection of Java APIs out there. Lets do something more interesting to finish off. The ZXTM Java and TrafficScript APIs don't offer comprehensive control of the software, but we have a SOAP API which does. In theory one would expect Jython should give us several approaches to SOAP, some examples: Apache AXIS from Java, Python's SOAPpy, or Python's ZSI. In practice it doesn't seem to be practical to work with ZSI or SOAPpy, I had trouble getting either of these working. The most straightforward solution turns out to be AXIS. The following example will interface with ZXTM via SOAP from Jython using AXIS. I'm going to assume some pre-knowledge of Java to avoid delving into configuration details. My example ZXTM host system is Ubuntu Linux with the openjdk-6-jre package installed, and Jython installed in /space/jython. Start out by setting up AXIS support as described here: Accessing ZXTM's Control API from Java Once this is done you should have installed AXIS, JavaMail, and JAF .jar files in your ZXTM's Java Extensions Catalog. This full list of files in the catalog relating to this article is shown in the screenshot below.
I've ported the Java extension from the ZXTM ServerStatus: Java Extensions and the ZXTM Control API article to Python. I've written a fairly direct line-for-line translation of the original Java code so as to provide a good side-by-side comparison of the implementations. The output should be essentially identical. To use this Python extension:
(If you don't see server status information check the ZXTM Event Log to see if there are any errors.) Here's a sample of the code from our Jython implementation of the ServerStatus servlet: def doGet( self, req, res ): try: userPass = req.getRemoteUserAndPassword() if userPass == None: raise ServletException("No authentication details") # username is userPass[0], password is userPass[1] admin = "https://%s:%s@localhost:9090/soap" % \ (userPass[0], userPass[1]) smil = SystemMachineInfoLocator() smil.setSystemMachineInfoPortEndpointAddress( admin ) smip = smil.getSystemMachineInfoPort() # will throw an exception if admin username and password are incorrect smip.getProductVersion() result = self.generateReport( admin ) res.setContentType( "text/html" ) out = res.getWriter() out.println( "<h1>System Status</h1>" ) out.println( result ) except Exception, e: res.setHeader("WWW-Authenticate", "Basic realm=\"ZXTM Server Status\"") res.setHeader("Content-Type", "text/html") res.setStatus(401) message = \ "<html>" + \ "<head><title>Unauthorized</title></head>" + \ "<body>" + \ "<h2>Unauthorized - please log in</h2>" + \ "<p>Please log in with the Admin username and password</p>" + \ "<p>Error: " + e.toString() + "</p>" + \ "</body>" + \ "</html>" out = res.getWriter() out.println( message ) And here's the equivalent functionality from the original Java code. public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { try { ZXTMHttpServletRequest zreq = (ZXTMHttpServletRequest)req; String[] userPass = zreq.getRemoteUserAndPassword(); if( userPass == null ) throw new Exception( "No Authentication details" ); // Username is userPass[0], password is userPass[1] String admin = "https://"+userPass[0]+":"+userPass[1]+"@localhost:9090/soap"; SystemMachineInfoLocator smil = new SystemMachineInfoLocator(); smil.setSystemMachineInfoPortEndpointAddress( admin ); SystemMachineInfoPort smip = smil.getSystemMachineInfoPort(); // will throw an exception if admin username and passwd are incorrect smip.getProductVersion(); // No exceptions thrown... must have been successful ;-) String result = generateReport( admin ); res.setContentType( "text/html" ); PrintWriter out = res.getWriter(); out.println( "<h1>System Status</h1>" ); out.println( result ); } catch( Exception e ) { res.setHeader( "WWW-Authenticate", "Basic realm=\"ZXTM Server Status\"" ); res.setHeader( "Content-Type", "text/html" ); res.setStatus( 401 ); String message = "<html>" + "<head><title>Unauthorized</title></head>" + "<body>" + "<h2>Unauthorized - please log in</h2>" + "<p>Please log in with the Admin username and password</p>" + "<p>Error: " + e.toString() + "</p>" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println( message ); } } So that's it, the examples in this article should be enough to get you started writing Jython extensions for processing ZXTM traffic. You don't have to worry about compiling them. Making ZXTM even more RAD! Of course, the compile-and-cache model used by this ZeusPyServlet extension could be applied to other JVM languages too. Languages such as Scala, JRuby, and even Java itself. Jython on ZXTM ChecklistFor the really impatient you can try simply running through the checklist below to get Jython working on your Zeus traffic manager:
|
Recently...
Other Resources
|

As is well documented here on the KnowlegeHub, we support the use of Java
extensions to manipulate traffic. One of the great things about supporting
"Java" is that this really means supporting the JVM platform... which,
in turn, means we support any language that will run on the JVM and can access
the Java libraries. Declan's article on writing




