To get started quickly with Python on Stingray Traffic Manager, go straight to PyRunner.jar: Running Python code in Stingray Traffic Manager. This article dives deep into how that extension was developed.
As is well documented, 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.
Java isn't my first choice of languages, especially when it comes to web development. My language of choice in this sphere is Python; and thanks to the Jython project we can write our extensions in Python!
Jython comes with a servlet named PyServlet which will work with Stingray 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 Stingray Java extensions I recommend reviewing the Java Development Guide available in the Stingray Product Documentation and the Splash guide Writing Java Extensions - an introduction )
Grab Jython 2.5 (if there is now a more recent version I expect it will work fine as well)
Jython comes in the form of a .jar installer, on Linux I recommend using the CLI install variant:
java -jar jython_installer-2.5.0.jar -c
Install to a path of your choosing, I've used: /space/jython
Upload /space/jython/jython.jar to your Java Extensions Catalog
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 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 Stingray 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 Stingray 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 Stingray 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 is attached to this document.
To compile the Stingray 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 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 Stingray 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 Stingray software, and then you just have to set a configuration parameter for the servlet, in summary:
Install Jython on your Stingray machine, I've installed mine to /space/jython
In Catalogs > Java > ZeusPyServlet add some parameters:
Parameter: python_home, Value: /space/jython (or wherever you have installed Jython)
Parameter: debug, Value: none required (this is optional, it will turn on some potentially useful debug messages)
Back in Catalogs > Java you can now delete all the WEB-INF files, now that Jython knows where it is installed it doesn't need this
Go to System > Traffic Managers and click the 'Restart Java Runner ...' button, then confim the restart (this ensures no bad state is cached)
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 Stingray attributes are available
htmlOut += "<h2>VS: %s</h2>" % request.getAttribute("virtualserver")
htmlOut += "</body></html>"
toClient.println(htmlOut)
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.
... View more