Wednesday, January 29, 2014

Iptables settings can interfere with apt-get update

I ran into a problem on my Ubuntu 12.04.3 box today. I issued the command sudo apt-get update. This usually works without a hitch, but today, it obtained a few of the updates successfully but gave 404 errors for many others. Here's an example:

Err http://security.ubuntu.com precise-security/main Sources
  404  Not Found [IP: 91.189.92.200 80]


After a little thought, I realized how this Ubuntu box differed from others I had worked on: I had used iptables to facilitate running Apache Tomcat on ports 80 and 443. (Because these port numbers are below 1024, Tomcat would have to run as a privileged user to access them, and iptables provides a well-known workaround.)

The particular iptables rule that caused the problem was this one:

sudo iptables -t nat -I OUTPUT -p tcp --dport 80 -j REDIRECT --to-ports 8080


By temporarily deleting that rule (which can be done by replacing -I with -D in the above command), I got sudo apt-get update to work.

Tuesday, January 28, 2014

Running Tomcat 7 on port 80 on Ubuntu 12.04.3

The goal:
  • Commission a new Amazon EC2 Instance running Ubuntu 12.04.3.
  • Install Apache Tomcat 7 and everything Tomcat requires to run.
  • Make Tomcat respond to requests on port 80.
How this differs from other tutorials you might have encountered:
  • It's for Ubuntu, not some other flavor of Linux where you can just set AUTHBIND=yes in an /etc/defaults/tomcat7 file.
  • It's for Tomcat 7, not some earlier version.
  • It doesn't just tell you to edit Tomcat's server.xml file, ignoring the fact that Ubuntu won't let a non-privileged user bind to ports below 1024.
  • It doesn't suggest running Tomcat as root, ignoring any resulting security concerns.
How to do it:
  • Create a new AWS EC2 Instance.
    • Select the AMI Ubuntu Server 12.04.3 LTS, 64-bit.
    • Use or create a security group that enables (at least) ports 22 and 80 for your IP address.
  • Use an SSH client to connect to the Instance. Most of the remaining steps will be performed in the SSH client.
  • Install Tomcat 7: 
    wget http://apache.mirrors.lucidnetworks.net/tomcat/tomcat-7/v7.0.50/bin/apache-tomcat-7.0.50.tar.gz
    tar -xzvf apache-tomcat-7.0.50.tar.gz
    rm apache-tomcat-7.0.50.tar.gz
    export CATALINA_HOME=/home/ubuntu/apache-tomcat-7.0.50
    export CATALINA_BASE=$CATALINA_HOME
  • Install JRE 7. Unfortunately, Oracle requires you to click to accept a license agreement, which you can't do from a headless server. So use another computer to visit http://www.oracle.com/technetwork/java/javase/downloads/server-jre7-downloads-1931105.html, download server-jre-7u51-linux-x64.tar.gz. Find a way to get this file to your home directory on the EC2 Instance. One possibility is to install an FTP or SFTP server on the Instance. Once the file is in your home directory:
cd ~ tar -xzvf /sftp/stevetest/incoming/server-jre-7u51-linux-x64.gz

export JAVA_HOME=/home/ubuntu/jdk1.7.0_51  
  • Use iptables to redirect requests on port 80 to port 8080
sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080

sudo iptables -t nat -I OUTPUT -p tcp --dport 80 -j REDIRECT --to-ports 8080
  • Start Tomcat:
cd ~/apache-tomcat-7.0.50/bin ./startup.sh

  • Now if you type the EC2 Instance's IP address into your browser's address bar, you should see the Tomcat welcome page. No need to specify port 8080!
A tip of the hat goes to Tomcat: The Definitive Guide by Jason Brittain and Ian F. Darwin for a clear explanation of how to use iptables.

In future installments, I hope to cover setting up the server to automatically export the environment variables and start Tomcat when booted, and to enable SSL on port 443.

Sunday, January 12, 2014

Modern technology frees desk space

At home, I use a low-cost computer as a basic file and print server. Family members store files such as photos and schoolwork in a shared folder, which is automatically backed up by Carbonite. And an all-in-one printer/scanner is attached via a USB cable for everyone's use. I realize nearly the same thing could be accomplished by using cloud storage and a wireless printer, but my old-fashioned solution is convenient and inexpensive.

For almost ten years, an obsolete desktop computer filled this role in my house. Its slow CPU and minimal RAM didn't have much of a negative effect, since copying small files and printing small documents aren't very taxing. But it sure took up a lot of desk space!



What finally motivated me to replace this old workhorse was when the hard disk, which had a capacity of only 80 GB, got full. After a few days of searching, I found a suitable used laptop for sale on eBay. It's an Acer Aspire V5 with a 11.6-inch screen, 1.5 GHz Celeron dual-core processor, 4 GB RAM, 500 GB hard drive and Windows 8.

With shipping, the cost was around $165.

I was worried that transferring all my files and software to the new computer would be an onerous chore, but it ended up going smoothly and only took a few hours. The steps were:
  • Connect to my home wireless network. Windows 8 automatically detected the network, prompted for the key, and connected without a hitch.
  • Create a homegroup and a shared folder with read/write access.
  • Download and install the printer driver.
  • Copy all the files from the old computer.
  • Log in to my Carbonite account and transfer the backup to the new computer.
So far, everything is working well. The screen and keyboard are tiny, but since I'll almost never use them, that's actually an advantage. Look at all the desk space I gained!

Monday, January 6, 2014

Java operator precedence can cause unexpected output

My Java program needed to determine whether a particular variable was null. I wasn't getting the expected results, so I added a System.out.println call to print some diagnostic output to the console. The diagnostic output wasn't what I expected, either. It turned out to have to do with operator precedence in Java. Here's some example code that illustrates the problem:

public class Example {
 public static void main(String[] args) {
  Integer n = 1;
  System.out.println("Is n null? " + n == null);
 }
}

I expected the output to be: Is n null? false. Instead, it was just the single word false. Here's why:

The additive operator + has higher precedence than the equality operator ==. So Java was evaluating the string concatenation

"Is n null? " + n

and then comparing the result to null. The result of that comparison was the boolean value false, which is what was displayed. This was easily fixed with the addition of a couple of parentheses:

System.out.println("Is n null? " + (n == null));

Interestingly, if I'd been comparing n to some integer rather than to null, the compiler would have caught the problem for me. This code

public class Example {
 public static void main(String[] args) {
  Integer n = 1;
  System.out.println("Is n one? " + n == 1);
 }
}

causes a compile-time error, Incompatible operand types String and int.

The moral of the story is, whatever language you're programming in, be aware of operator precedence!

Friday, December 27, 2013

Two Spring MVC tutorial projects - Spring 4, Eclipse, Tomcat, multiple models, MultipartFile

Today I present two "hello world"-type Java EE projects using Spring MVC. I've been getting up to speed with Spring for a while now. I've taken advantage of several helpful tutorials, but I found all of them lacking in one way or another.

First, although there are quite a few Spring MVC "hello world" tutorials, I couldn't find one that accommodated all of my requirements, so I created the springmvcshell project. It's based on mkyong's excellent Spring 3 MVC Hello World Example, which I modified to:
  • Use the Maven standard directory structure
  • Work with the Eclipse IDE
  • Work with Tomcat
  • Use Spring 4 rather than an earlier version
  • Use Java 1.7 rather than an earlier version
Second, I wanted to create an MVC view and corresponding controller method that include fields from two models plus a multipart file upload in a single form. Again, I found several relevant tutorials, but none that met all my needs. So I created the springmvcgoodness project, which provides all the features of springmvcshell, plus:
  • Uses a single form to read and write fields from two separate classes
  • Includes a file upload control using Spring's MultipartFile interface
The README files in the springmvcshell and springmvcgoodness repos explain how to install and run the two projects. You can of course see all the source code in those two repos. The remainder of this post discusses a few interesting points about the code and settings.

First of all, the Maven pom.xml files for the springmvcshell and springmvcgoodness projects are of critical importance. Both pom.xml's include the dependencies for Spring 4.0.0, as well as a reference to the com.springsource.repository.maven.snapshot repository that's required for Maven to find these dependencies. The springmvcgoodness pom.xml also adds dependencies for  the Java Server Pages Standard Tag Library (JSTL), the Java Persistence API, and Apache Commons FileUpload.

Next, the project settings are critical to making these projects work with Eclipse and Tomcat. If you clone my springmvcshell and springmvcgoodness repos, you'll automatically have the correct settings, which are stored in the .classpath and .project files. However, it's worth understanding some of the details.
  • I converted these projects to faceted form (right-click the project and select Configure | Convert to Faceted Form) and added the Dynamic Web Module facet version 3.0. This makes the projects usable as servlets.
  • The Java Build Path is key to avoiding compile-time errors due to missing dependencies. I had to add some libraries to the build path (right-click the project and select Build Path | Configure Build Path, then click the Add Library button). I had to add the EAR Libraries. To eliminate some compiler warnings, I also removed the old version 1.5 JRE System Library and replaced it with the JRE System Library [jre7].
  • The Deployment Assembly settings are key to successful deploying on Tomcat and are accessed by right-clicking the project, selecting Properties, and then clicking Deployment Assembly. It's critical that the Maven Dependencies be added here if not already present.
Finally, here are a few points about the springmvcgoodness project:
  • The approach I took to including fields from two models in one form was to create a wrapper class containing an instance of each model class.
  • Once you have such a wrapper class, this view shows how to set the commandName and path attributes to refer to fields of the two model classes.
  • Keys to making the file upload work include:
I hope you'll find these example Spring MVC projects helpful!

Tuesday, November 19, 2013

Installing Selenium WebDriver and Python on Amazon Linux

I'm configuring a headless server, hosted on AWS, for browser-based automated testing. I'll use Selenium WebDriver for browser automation, and I've chosen Python as the programming language.

I've seen instructions for setting up Selenium headless automated testing in Ubuntu. However, I decided to try Amazon Linux instead of Ubuntu, so those instructions were a good start, but not exactly what I needed.

I also borrowed heavily from this excellent article on installing Firefox on Amazon Linux.

And my Python code is straight from the example in the Selenium documentation.

Here are the steps that worked for me:

One-time initial setup:

Creating an AWS Instance:
  • Log in to AWS and create a new Instance from Amazon Linux AMI 2013.09.1 (64-bit).
  • I made my Instance a t1.micro, but you might want to choose something more powerful that will run faster.
  • I created a new Security Group. You can do the same, or use an existing one. At a minimum, you must give your IP address access on port 22 (SSH).
  • I associated my existing keypair with the Instance. If you don't already have a keypair, you can create a new one.
  • I named my Instance tester. You can of course name it anything you like, or even leave the name blank.
Connect to the Instance using your favorite SSH client. I used the default browser-based Java client. The username is ec2-user.

Install Selenium (which requires pip, which in turn requires setuptools):

wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
sudo python ez_setup.py
wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py
sudo python get-pip.py
sudo pip install selenium

Install Firefox (which requires the Gimp Tool Kit):
  • Use your favorite text editor (I used vim) to create a new file named gtf-firefox.sh, and insert these lines:

#!/bin/bash
# GTK+ and Firefox for Amazon Linux
# Written by Joseph Lawson 2012-06-03
# http://joekiller.com
# http://joekiller.com/2012/06/03/install-firefox-on-amazon-linux-x86_64-compiling-gtk/
 
# chmod 755 ./gtk-firefox.sh
# sudo ./gtk-firefox.sh
 
 
TARGET=/usr/local
 
function init()
{
export installroot=$TARGET/src
export workpath=$TARGET
 
yum --assumeyes install make libjpeg-devel libpng-devel \
libtiff-devel gcc libffi-devel gettext-devel libmpc-devel \
libstdc++46-devel xauth gcc-c++ libtool libX11-devel \
libXext-devel libXinerama-devel libXi-devel libxml2-devel \
libXrender-devel libXrandr-devel libXt dbus-glib
mkdir -p $workpath
mkdir -p $installroot
cd $installroot
PKG_CONFIG_PATH="$workpath/lib/pkgconfig"
PATH=$workpath/bin:$PATH
export PKG_CONFIG_PATH PATH
 
bash -c "
cat << EOF > /etc/ld.so.conf.d/firefox.conf
$workpath/lib
$workpath/firefox
EOF
ldconfig
"
}
 
function finish()
{
    cd $workpath
    wget -r --no-parent --reject "index.html*" -nH --cut-dirs=7 http://releases.mozilla.org/pub/mozilla.org/firefox/releases/latest/linux-x86_64/en-US/
    tar xvf firefox*
    cd bin
    ln -s ../firefox/firefox
    ldconfig
}
 
function install()
{
    wget $1
    FILE=`basename $1`
    if [ ${FILE: -3} == ".xz" ]
       then tar xvfJ $FILE
       else tar xvf $FILE
    fi
SHORT=${FILE:0:4}*
    cd $SHORT
    ./configure --prefix=$workpath
    make
    make install
    ldconfig
    cd ..
}
 
init
install ftp://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.xz
install http://download.savannah.gnu.org/releases/freetype/freetype-2.4.9.tar.gz
install http://www.freedesktop.org/software/fontconfig/release/fontconfig-2.9.0.tar.gz
install http://ftp.gnome.org/pub/gnome/sources/glib/2.32/glib-2.32.3.tar.xz
install http://cairographics.org/releases/pixman-0.26.0.tar.gz
install http://cairographics.org/releases/cairo-1.12.2.tar.xz
install http://ftp.gnome.org/pub/gnome/sources/pango/1.30/pango-1.30.0.tar.xz
install http://ftp.gnome.org/pub/gnome/sources/atk/2.4/atk-2.4.0.tar.xz
install http://ftp.gnome.org/pub/GNOME/sources/gdk-pixbuf/2.26/gdk-pixbuf-2.26.1.tar.xz
install http://ftp.gnome.org/pub/gnome/sources/gtk+/2.24/gtk+-2.24.10.tar.xz
finish
 
 
# adds the /usr/local/bin to your path by updating your .bashrc file.
cat << EOF >> ~/.bashrc
PATH=/usr/local/bin:\$PATH
export PATH
EOF

  • chmod 755 ./gtk-firefox.sh
  • sudo ./gtk-firefox.sh
    • On my t1.micro Instance, this step took over an hour to run!
Create a "hello world" Selenium automation program in Python:
  • Use a text editor to create a file named tester.py, and insert these lines:

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0
from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0

# Create a new instance of the Firefox driver
driver = webdriver.Firefox()

# go to the google home page
driver.get("http://www.google.com")

# find the element that's name attribute is q (the google search box)
inputElement = driver.find_element_by_name("q")

# type in the search
inputElement.send_keys("cheese!")

# submit the form (although google automatically searches now without submitting)
inputElement.submit()

# the page is ajaxy so the title is originally this:
print driver.title

try:
    # we have to wait for the page to refresh, the last thing that seems to be updated is the title
    WebDriverWait(driver, 10).until(EC.title_contains("cheese!"))

    # You should see "cheese! - Google Search"
    print driver.title

finally:
    driver.quit()

Set up the ability to run Firefox headless:
  • Append this line to .bashrc: export DISPLAY=:10
  • sudo yum install Xvfb

Step that must be performed after each time you reboot the Instance:

sudo Xvfb :10 -ac


Steps that must be performed each time you connect to the Instance after disconnecting:

  • Connect to the Instance using an SSH client.
  • Now open a second SSH session.
    • If you try to run Firefox in the first SSH session, you’ll get an error, Xlib:  extension "RANDR" missing on display ":10".)
  • firefox
  • python tester.py
If it's working, you'll see the output Google, followed by cheese! - Google Search.

Tuesday, November 12, 2013

A Python program to FTP a mix of text and binary files

When using FTP to download files from a server to your local computer, you must of course be sensitive to line endings. My server is a Linux box, so line endings are just LF. My laptop runs Windows 7, with CRLF line endings.

I recently wrote a Python program to automate the backup of files from the server to my laptop, and had to take this difference into account.

To make matters worse, some of the text files on the server have CRLF, even though it's a Linux box. The files came from a variety of sources -- uploaded by different developers, created in WordPress admin, etc -- and therefore don't have consistent line endings.

And there's another wrinkle: Some of the files are UTF-8 encoded and contain characters that can't be represented as ASCII.

At first, I thought I would simply use the Python ftplib module's retrbinary() method for binary files and retrlines() for text files. That didn't work out. The biggest obstacle was errors that resulted with the UTF-8 files when trying to read a line of text that contained a non-ASCII character and write it to a file. Two lesser hurdles were (1) figuring out when it was really necessary to add a CR, vs when the CR was already present, and (2) the possibility that the last line of the file doesn't end with a newline of any sort.

Eventually, I hit on a simple approach that almost (but not quite) 100% effective. It's good enough for my requirement, which is just to create a backup that's usable in case of disaster. My program uses retrbinary() for ALL files. If the file extension indicates a binary file, no additional action is taken. If the file extension indicates a text file, we replace every occurrence of the byte 0x0a (LF) with the two bytes 0x0d and 0x0a (CRLF) -- unless the 0x0a was already preceded by 0x0d, in which case we leave it alone.

Why is this only ALMOST 100% effective? Because it doesn't handle the case where a LF is the first byte in a buffer read by retrbinary(), and was preceded by a CR in the PREVIOUS buffer. In this case, my program inserts an extraneous CR. It wouldn't be terribly hard to fix, but not worth the trouble for my purpose.

Here's a simplified excerpt of the Python code:

from ftplib import FTP

# This is the callback function for retrbinary().
def processBytes(buffer):
 # Use the file that was opened in retrieve()
 global f

 previousByte = 0

 # Create an empty byte array.
 buffer2 = bytearray()

 # Loop over all the bytes that were read by retrbinary()
 for b in buffer:

  # Is the byte a LF? Is it NOT preceded by a CR?
  if b == 0x0a and previousByte != 0x0d:

   # Prepend a CR to the LF.
   buffer2.append(0x0d)
  buffer2.append(b)
  previousByte = b

 # Write the modified byte array to the local file.
 f.write(buffer2)

# Retrieve the specified file from the FTP server.
def retrieve(fname):
 global f
 global ftp

 print(fname)

 # Open the local file for writing in binary mode.
 f = open(fname, 'wb')

 # Determine whether the file is binary or text. In this simplified example, a file is
 # binary if and only if the file extension is GIF, JPG or PNG.
 name = fname.lower()
 if name.endswith('.gif') or name.endswith('.jpg') or name.endswith('.png'):

  # Binary file. Don't modify the bytes. Just write them to the local file.
  ftp.retrbinary('RETR ' + fname, f.write)
 else:

  # Text file. Insert CR's as needed before writing to the local file.
  ftp.retrbinary('RETR ' + fname, processBytes)

def main():
 global ftp

 print("STARTING...")

 # Connect to the FTP server. Replace the arguments with your URL, username and password.
 ftp = FTP('ftp.example.com', 'someuser', 'somepassword')

 # List all the files in the current directory, including the file type in the results.
 files = ftp.mlsd('', ['type'])
 for file in files:

  # If it's a file, not a directory, then download it.
  if file[1]['type'] == 'file':
   retrieve(file[0])
 ftp.quit()
 print("DONE!")

if __name__ == '__main__':
 main()