Tuesday, November 17, 2009

Simple Web Tool Building With Maven (The Woot-Off Tracker Revisited)

One of my favorite tools of the past year was the Woot-Off tracker I developed just over a year ago. In my mind, one of the problems this tool had was that I needed to start up a proxy server to host make calls on behalf of the AJAX Javascript. I needed to find some way to make this easier. Additionally, I had run into an issue when I actually tried to run the tool behind a firewall proxy.



Since discovering the power of Maven, I thought I'd look into seeing what was available for the Winstone servlet engine I used on the first iteration of this tool. Conviently, there was the winstone-maven-plugin, a mojo which, among other things, built an executable jar for winstone with your war already deployed.



This was perfect for my needs. So (along with converting to apache's http-client) I set out on a new maven adventure. My pom looked like this:





<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.twofourone</groupId>
<artifactId>woot.tracker.proxy</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>woot.tracker.proxy</name>
<url>http://twofourone.blogspot.coom</url>
<dependencies>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.6</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.0</version>
</dependency>

</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>net.sf.alchim</groupId>
<artifactId>winstone-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>embed</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>net.sf.alchim</groupId>
<artifactId>winstone-maven-plugin</artifactId>
<version>1.2</version>
<configuration>
<cmdLineOptions>
<property>
<name>httpPort</name>
<value>8480</value>
</property>
<property>
<name>ajp13Port</name>
<value>-1</value>
</property>
<property>
<name>controlPort</name>
<value>-1</value>
</property>
<property>
<name>directoryListings</name>
<value>false</value>
</property>
<property>
<name>useInvoker</name>
<value>false</value>
</property>
</cmdLineOptions>
</configuration>
<executions>
<execution>
<goals>
<goal>embed</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
<finalName>woot.tracker.proxy</finalName>
</build>
</project>


We have two setups for winstone. The first goal, embed, tells Maven to stick winstone into the jar during the package phase. By default, this step will create a jar with the same name as your project and throw -standalone on the end. The second setup tells winstone what port it should run on, etc. These could potentially be in a single setup, just haven't tried it yet.



In order to run the proxy, you simply execute the jar from a command prompt and you'll have a war deployed to port 8480.



How has our proxy changed? In order to support a firewall call, I decided to use Apache's HttpClient. Adding the dependency for HttpClient to the pom automagically downloads all dependencies of HttpClient for use by the war. Our new code looks like this:




package com.twofourone.woot.tracker.proxy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;

/**
* Proxy Servlet to redirect requests to the Woot site.
* @author mcornell
*/
public class WootProxy extends HttpServlet {

static final long serialVersionUID = 1L;

private HttpClient httpClient = new DefaultHttpClient();
private HttpGet wootGet;

/**
* Sets up the HttpClient with proxy info if provided. Sets up the URL for Woot.
*/
@Override
public void init() throws ServletException {
String proxyHost = System.getProperty("proxyHost", "");

String proxyPort = System.getProperty("proxyPort", "");

if (proxyHost.length() > 0 && proxyPort.length() > 0) {
HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort));
httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
}

String contentObj = "http://www.woot.com/salerss.aspx";
wootGet = new HttpGet(contentObj);
}


/**
* Processes requests for both HTTP GET and POST methods.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

HttpResponse wootResponse = httpClient.execute(wootGet);

HttpEntity wootEntity = wootResponse.getEntity();

response.setContentType(wootEntity.getContentType().toString());
// Get and read the input stream.
StringBuffer buffer = new StringBuffer();

BufferedReader din =
new BufferedReader(new InputStreamReader(wootEntity.getContent()));

String s;
while ((s = din.readLine()) != null) {
buffer.append(s);
}
din.close();

// Now write the bytes out to the client.
byte[] contentBytes = buffer.toString().getBytes();
OutputStream out = response.getOutputStream();
out.write(contentBytes, 0, contentBytes.length);
out.flush();
out.close();
}

/**
* Handles the HTTP GET method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/**
* Handles the HTTP POST method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/**
* Returns a short description of the servlet.
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Woot Tracker Proxy";
}
}


The code has changed to setup the client once and initialize it once. Additionally, there is the code provided by the HttpClient project that adds a proxy to the client. One additional change needs to be the ability to track a Kids.Woot-Off URL in case it's a different kind of woot-off.



The HTML/Javascript remains the same.

ShareThis