ElasticBand

Run non-java webapps on Elastic Beanstalk.

WARNING!

This is alpha quality software. This project, for the moment, should be considered nothing more than an exercise in yak shaving. Do not use this in a production environment. That said...fiddling around is good.

How it works

ElasticBand forks a process listening on a free HTTP port. All incoming requests are then proxied to that process. Depending on how your web.xml is set up, it will use a Runtime to determine how to set up that subprocess correctly given the potentially stripped down AWS image your application has started with.

For the moment, there is are python and virtualenv runtimes, but it wouldn't take a monster effort to implement new ones.

Python runtime

For an application that really only depends on Python, this runtime may be enough. An example is as follows:

First, the simplest python web server Googling could pull up.

main.py

import sys
import BaseHTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler


HandlerClass = SimpleHTTPRequestHandler
ServerClass  = BaseHTTPServer.HTTPServer
Protocol     = "HTTP/1.0"

port = int(sys.argv[1])
server_address = ('127.0.0.1', port)

HandlerClass.protocol_version = Protocol
httpd = ServerClass(server_address, HandlerClass)

sa = httpd.socket.getsockname()
print "Serving HTTP on", sa[0], "port", sa[1], "..."
httpd.serve_forever()

Next, the web.xml definition. Note how application.command is the name of the script followed by %d. This will be the given port number the application should start serving HTTP on (referenced above where we get the port from sys.argv[1]).

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">

	<context-param>
	  <param-name>application.command</param-name>
	  <param-value>main.py %d</param-value>
	</context-param>
	
	<context-param>
	  <param-name>application.runtime</param-name>
	  <param-value>python</param-value>
	</context-param>

  <listener>
    <listener-class>vistarmedia.elasticband.servlet.ContextListener</listener-class>
  </listener>

  <filter>
    <filter-name>guiceFilter</filter-name>
    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>guiceFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

Virtualenv runtime

Anything more involved, of course, is going to have external requirements. The odds that they'll be installed on a vanilla Elastic Beanstalk image are about zero. In fact, virtualenv itself isn't installed. If the runtime can't find it, a wrapper will be downloaded to bootstrap from there.

Each time the application server (ie: Tomcat) is started, the virtualenv will be set up from scratch. This is pretty darn slow, but seems most correct to me. One problem here is that if pypi is having problems that day, your boot could hang or fail waiting for packages to come down.

Check out the example directory for an example of a virtualenv setup.

Creating warfiles

When this project is compile, it'll generate an empty war file which won't do much. This isn't enough to upload to Elastic Beanstalk. The supplied package command in the root will help create it.

$ mvn clean package
$ cd example
$ ../package -o example.war -w ./web.xml ./site ../target/elasticband-0.1-SNAPSHOT.war

If that completes successfully, you'll have an example.war ready to be pushed up to Elastic Beanstalk.

Project Status

This project was hacked out in an evening to see if I could. It's not presently used in a production environment as far as I know. Some of the code is very round about. Patches are welcome.

TODOs

  • The Async HTTP Client seems a bit overkill since the servlet processing thread must block until completion, but the idea of using http-client just hurts me.
  • The target is Elastic Beanstalk, so perhaps there's a way to force Tomcat to handle async responses? It does't seem to want to run the servlet 3.0 API, but there is this, but I don't think EB is using APR or NIO HTTP connectors.
  • Static files served directly by the servlet container
  • There are some issues on AWS I can't recreate locally where the environment will try to spawn many runtimes. /me scratches head.
  • Of course, some non-python runtime
  • When the container starts up, it doesn't wait to see when the subprocess binds to the port, so the first N requests probably just bomb. Not a big deal w/ EB due to the LB health checks