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()

Monday, November 11, 2013

A Python program to back up all repos in a GitHub account

The goal: Automatically find all repos in a specified GitHub account and back them up to folders on the local hard disk.

I've seen instructions for backing up GitHub using Ruby (such as this example) or a shell script (like this one), but I wanted to use Python, which was already installed on my computer.

My environment:
  • Windows 7
  • Python 3.3.0
Here's the Python code, gitback.py:

import json
import os
import sys
import urllib.request
import zipfile

def main():
 # Change this to your own GitHub username.
 user = 'xxxxx'

 # The repos will be backed up to the directory you
 # specify as a command-line argument.Example: gitback.py c:\gitbkup
 target = sys.argv[1]

 # Delete the target directory if it exists. Then create it.
 if (target != None and target != '' and os.path.exists(target)):
  os.system('rd /S /Q ' + target)
 os.makedirs(target)

 # Get up to 100 repos. If your account has more than 100 repos,
 # increase the per_page value.
 req = urllib.request.Request('https://api.github.com/users/' + user + '/repos?per_page=100')
 resp = urllib.request.urlopen(req)

 # The result is in byte format, so we need to specify
 # the encoding and decode it.
 content = resp.read().decode('utf-8')

 # The result is in JSON format. Parse the JSON.
 data = json.loads(content)

 # Loop over all the repos, cloning each one.
 for repo in data:
  name = repo['name']
  os.system('git clone git@github.com:' + user + '/' + name + ' ' + target + "\\" + name)

if __name__ == '__main__':
 main()


Friday, October 11, 2013

Workaround for problem with fieldset border in IE 10

I discovered a problem with the visual appearance of a page using a fieldset tag in Internet Explorer 10.

Here's a small HTML page that illustrates the problem:

<html>
<body style="background: blue;">
<fieldset style="width: 500px;">
<form>
 <legend>This is my form</legend>
 <p>Enter some text: <input type="text"></p>
 <p><input type="submit" /></p>
</form>
</body>
</html>
Here's how the page renders in IE 10. Note the wide, misaligned right border.

The border looks normal, however, in the other browsers I tested: Chrome 30.0.1599.69 m and Firefox 24.0.

I found a simple workaround, which is to explicitly set the border style for the fieldset tag:

<fieldset style="width: 500px; border: 1px solid white;">

This cleans up the border like so:


And it continues to look fine in Chrome and Firefox.

Of course, you could do the same thing in a CSS file if you prefer:

fieldset {
 border: 1px solid white; 
}

Sunday, September 29, 2013

Mod_security causes sporadic "No data received' error on website

I recently received a report from our headquarters office that our corporate website was down. I tried accessing it from my home office and had no problem, but a few minutes later, I too saw the error. In Chrome, it manifested as a "No data received" message. Clicking the "More" button revealed the further description "Unable to load the webpage because the server sent no data" and the error code "ERR_EMPTY_RESPONSE".

I soon discovered the problem was sporadic. Sometimes I was able to view the site's homepage, sometimes not. Sometimes I was able to log in to the site's Wordpress admin page, sometimes not. Sometimes I was able to save changes to a post in Wordpress admin, sometimes not. There didn't seem to be any pattern to which pages produced the error or when.

The site uses WordPress and is hosted on a GoDaddy shared server. Several people have complained about similar problems with WordPress recently (such as here and here), but didn't offer any real solutions.

I contacted GoDaddy support. Their first move was to enable error logging for my hosting account. That led nowhere: although I replicated the problem several times while logging was enabled, and although we waited several minutes for log data to be recorded, nothing showed up in the logs. The support tech concluded that no errors were being logged because the requests were being blocked before reaching the layer where logging would occur.

His next move was to run some tests on GoDaddy's firewall and the servers behind it. These tests showed that everything was operating normally.

After some thought, he concluded that mod_security was to blame. He mentioned that WordPress has recently been the target of a lot of hacking, and that GoDaddy had therefore stepped up security measures, deploying mod_security. Mod_security was mistakenly identifying some, but not all, attempts to access the site, from both headquarters and my home office, as hostile. While we assumed that users in other locations weren't affected, we had no way of knowing.

The tech suggested some workarounds, including clearing cache and cookies, and using a private browsing sessions. Neither of these helped any of the affected users. The one suggestion he offered that did work was to wait.

He indicated that once mod_security identifies a request as hostile, it blocks the sender's IP address for 90 seconds. This explains the sporadic, now-it's-up, now-it's-down nature of the problem. He also indicated that, if several requests from the same IP are flagged as hostile, the duration of the blockage becomes longer. He wasn't able to tell me a formula for how long we needed to wait. I ended up waiting several hours before trying again. At that point, the problem had resolved itself, and it hasn't resurfaced, three days later.

We really have little control over the situation. Because it's a shared hosting account, we don't have access to mod_security settings, or to network and server logs that might help with troubleshooting. The tech indicated that upgrading to a dedicated or virtual dedicated server would give us greater control. So far, we've decided not to upgrade, since it would increase cost, cause some downtime, and take several hours of effort on our part to migrate the site to a new server.

We're keeping our fingers crossed that mod_security decides that we -- and our customers -- aren't evil after all.

Tuesday, August 20, 2013

Eliminating JavaScript validation errors due to jQuery in an Eclipse dynamic web project

If your Eclipse project includes jQuery files such as jquery-1.8.3.min.js, Eclipse's JavaScript validation may report syntax errors that, although harmless, makes it look as  if your project contains bugs.



Much has been written online about how to resolve this problem, but not all of it is correct. In particular, suggestions involving Properties | Validation | Client-side JavaScript are off the mark, as these settings affect JavaScript within  HTML pages, not standalone .js files.

This comment -- https://bugs.eclipse.org/bugs/show_bug.cgi?id=349020#c15 -- has it right but doesn't give much detail. Here are the exact steps I followed to correct the problem:

1. Right-click the project name in Project Explorer and select Properties.

2. Expand JavaScript and select Include Path.


3. Expand the folder that contains the offending JavaScript files (which will vary depending on how you've set up your project), and click Excluded.


4. Click Edit. Click the Add button next to the Exclusion patterns textarea.


5. Now type in the pattern for files to be excluded, being careful to include the correct path, which will vary depending on how you've set up your project. In my project, the pattern is resources\lib\jquery*.*.


6. Click OK. Click Finish. Click OK. The errors should now disappear.


Behind the scenes, this added the following line to my project's .settings\.jsdtscope file:
<classpathentry excluding="resources/lib/jquery*.*" kind="src" path="src/main/webapp"/>

Resolving the error "Java compiler level does not match the version of the installed Java project" in Eclipse

I inherited a Java project created by another developer. When I opened it in Eclipse, I was able to build and run it, but Eclipse's Problems window showed an error, "Java compiler level does not match the version of the installed Java project."


Here's how I fixed it:

1. Right-click the project name in Project Explorer and select Properties. Then click Project Facets.
 
 
2. Click the dropdown arrow to the right of Java. Select the value 1.6, rather than 1.5.
 
 
3. Click OK. The error should disappear from the Problems window.
 

Tuesday, June 25, 2013

Making VNC server run on boot on Ubuntu

The goal: Having installed vncserver, make it run automatically on boot, without anyone needing to log in.

Environment: Amazon Web Services EC2 instance running Ubuntu 12.04.1 LTS 64-bit. Vncserver was installed as per these instructions.

To start vncserver on boot:
  • Open an SSH connection to the server.
  • cd /etc/init.d
  • sudo vim vncserver-start, then insert this text:
### BEGIN INIT INFO
# Provides:          scriptname
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start daemon at boot time
# Description:       Enable service provided by daemon.
### END INIT INFO
#! /bin/sh
sudo -u ubuntu vncserver -geometry 1200x650

  • sudo chmod +x tomcat-start
  • sudo update-rc.d tomcat-start defaults

One of the more interesting parts is the need to NOT run vncserver as root. When vncserver-start executes on boot, it executes as root. Launching vncserver as root results in an error. The solution is to prepend sudo -u ubuntu on the last line of vncserver-start.

Friday, June 7, 2013

Enabling remote access to a PostgreSQL database hosted on AWS

The goal: To enable access via pgAdmin and psql from our local machine to a PostgreSQL database server hosted on an Amazon Web Services EC2 instance.

The environment:
Server:
  • AWS EC2 Instance running Ubuntu Server 12.04.1 LTS, 64-bit
  • PostgreSQL 9.1
  • Default, out-of-the-box PostgreSQL configuration. That includes listening on port 5432, a database named postgres, and a role named postgres.
Client:
  • Windows 7
  • pgAdmin III version 1.16.1
  • psql version 9.2.3
Until the proper security settings are applied, the client can't connect to PostgreSQL on the server. If we try to connect using pgAdmin, a Server not listening error occurs:

If we try to connect using psql, a Connection timed out error occurs:

To enable connections, we must edit the AWS Security Group and two PostgreSQL configuration files.

Editing the AWS Security Group:
  • Log in to AWS.
  • On the EC2 Dashboard, select your Instance and note which Security Group it's using:
  • Select that Security Group, click the Inbound tab, and add a rule. The port should be 5432, and the source should be the IP address or our local machine (not the server), followed by /32. Don't forget to click the Apply Rule Changes button.
 
Editing the PostgreSQL pg_hba.conf file:
  • Use an SSH client to connect to the EC2 Instance.
  • cd to the directory that contains the PostgreSQL configuration files. This may vary, but mine are in /etc/postgresql/9.1/main.
  • Use a text editor to modify pg_hba.conf.
  • Locate the line host    all             all             127.0.0.1/0               md5.
  • Immediately below it, add this new line: host    all             all             0.0.0.0/0               md5
  • Save the file.
Editing the PostgreSQL postgresql.conf file:
  • Use a text editor to modify postgresql.conf.
  • Locate the line that starts with #listen_addresses = 'localhost'.
  • Uncomment the line by deleting the #, and change localhost to *.
  • The line should now look like this: listen_addresses = '*'                  # what IP address(es) to listen on;.
  • Save the file.
  • Restart PostgreSQL. The command may vary. In my case, it was: sudo /etc/init.d/postgresql restart.
Now you should be able to connect to the PostgreSQL server from your local machine, using your choice of pgAdmin3 or psql!
 
You'll be prompted for the postgres role's password when you connect, unless you've stored the password in %APPDATA%\postgresql\pgpass.conf on your local machine. (If your local machine is running Linux, the analogous file is .pgpass, usually found in your home directory.)
 
Caveats: The above instructions aren't optimized for security. In a production environment, you'll likely want tighter control over access to PostgreSQL. For example, in pg_hba.conf, we granted access to all IP addresses, which might be overkill. And logging in as the default role, postgres, might not be the best idea.
 

Wednesday, May 29, 2013

A database gotcha - null doesn't equal anything

I recently spent a long time debugging a problem where a database query was returning fewer rows than I expected. The cause was a well-known fact, but one that's easy to forget about: a "not equals" comparison to NULL is always false. An example makes this clear...

Create a table with a couple of columns:
CREATE TABLE test
(
  id integer NOT NULL,
  name character varying(10),
  CONSTRAINT test_pkey PRIMARY KEY (id)
)


Insert a few rows into the table; one of the rows includes a NULL value:
INSERT INTO test VALUES (1, 'abc');
INSERT INTO test VALUES (2, 'def');
INSERT INTO test VALUES (3, null);


Suppose we'd like to find all the rows where the name isn't 'abc'. It seems like this query would do it:
SELECT * FROM test WHERE name <> 'abc'

But that doesn't work. The query returns only the first row. This query solves the problem:
SELECT * FROM test WHERE COALESCE(name, '') <> 'abc'

I executed the above example in PostgreSQL 9.2, but the same principle applies to many databases.

Friday, March 15, 2013

Listing all column names in a PostgreSQL table

My PostgreSQL database contains a table with more than 100 columns, and I wanted to list the name and data type of each column, in a format that I could copy and paste. I was using Windows 7, but a similar technique should work on Linux. Here's one way to do it:
  • Open a DOS command window.
  • Change to PostgreSQL's bin directory. On my computer, the command is cd C:\Program Files\PostgreSQL\9.2\bin.
  • Make sure the directory C:\temp exists.
  • Execute this command: psql -U <your PostgreSQL user name> -d <your database name> -c "\d <your table name>" > c:\temp\out.txt
  • This will create the text file C:\temp\out.txt with a list of field names and data types.

Friday, February 22, 2013

Setting up Python and Bottle on Ubuntu 12.04

Here are the steps I followed to get Python and the Bottle web framework working on an Amazon Web Services (AWS) Ubuntu server:

  • Log in to AWS and create a new instance. Select the AMI Ubuntu Server 12.04.1 LTS, 64-bit. If you wish to minimize costs, set the instance type to T1 Micro. Accept AWS's default settings for the instance.
  • Configure the instance's security group to, at a minimum, allow access from your own IP address to ports 22, 80 and 5901.
  • Use an SSH client to connect to the instance, logging in as the user ubuntu.
  • This AMI comes with Python pre-installed. You can verify this by entering the command python. The response should indicate that Python 2.7.3 is running. Enter quit() to exit Python.
  • Install Bottle: sudo apt-get install python-bottle.
  • Install a VNC server so you'll be able to run a web browser on your Ubuntu server. See my blog post, VNC server for headless Ubuntu 12.04 and VNC client for Windows 7, for details.
  • Install a web browser: sudo apt-get install firefox.
  • Create a directory for your Python files. I called mine python_test: mkdir python_test.
  • Change to that directory: cd python_test.
  • Create a Bottle "hello world" application. I found an example here and modified it slightly. Use your favoirte text editor to create a file named hello.py as follows:
 from bottle import route, run

@route('/hello')
def hello():
    return "Hello World!"

run(host='localhost', port=8080)                                   
  • Execute this Python script: python hello.py.
  • Use a VNC client to connect to your server. In the VNC client, open Firefox. Visit http://localhost:8080/hello. You should see "Hello World!" in your browser.
  • In your SSH client, press Ctrl+C to stop the Bottle server.

Monday, January 14, 2013

VNC server for headless Ubuntu 12.04 and VNC client for Windows 7 - success!

Objective: To set up a GUI client on Windows 7 to manage my Ubuntu server.

The server is a headless Ubuntu 12.04 box on Amazon Web Services. The client machine is my Windows 7 laptop.

I found lots of advice about GUI interfaces for Ubuntu, but none of it worked in my situation. I encountered problems such as:

  • It was often suggested to use xRDP as the server and the built in Windows Remote Desktop as the client. That would be great if it worked, but I just couldn't get it to work. Once in a while, I could log in, but most of the time I got password-related errors.
  • A lot of the advice was for old  versions of Ubuntu.
  • The advice assumed a GUI was already installed on the server, that is, it was Ubuntu desktop rather than a headless Ubuntu server.
I finally found a solution that worked: vnc4server as the server and TightVNC Viewer as the client. Many thanks to Coddswallop's post on vnc4server setup.

Setting up vnc4server on the Ubuntu server
  • Use an SSH client to connect to the server.
  • Enter sudo apt-get update.
  • Enter sudo apt-get install gnome-core gnome-session-fallback.
  • Enter sudo apt-get install vnc4server.
  • Enter vncserver.
  • You'll be prompted to make up a password. Enter whatever you like, and make a note of it. You'll need this password when using the VNC client to connect.
  • Re-enter the same password when prompted.
  • Enter vncserver -kill :1.
  • Enter cp .vnc/xstartup .vnc/xstartup.bak.
  • Use your favorite text editor (I used VIM) to modify the file .vnc/xstartup, so that it looks like this:


#!/bin/sh

      # Uncomment the following two lines for normal desktop:
        unset SESSION_MANAGER
          #exec /etc/X11/xinit/xinitrc
            gnome-session --session=gnome-classic &

                [ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
                  [ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
                    xsetroot -solid grey
                      vncconfig -iconic &
                        #x-terminal-emulator -geometry 80×4+10+10 -ls -title ?$VNCDESKTOP Desktop? &
                          #x-window-manager &

                          • Enter vncserver -geometry 1200x650. You can change 1200x650 to whatever width and height suits your client machine's display, and you can add an optional depth parameter to change the color depth.
                          • Enable the necessary port in your firewall. In my case, this was port 5901. In the AWS Control Panel, I edited the applicable Security Group, creating an inbound rule for port 5901 for my laptop's IP address.
                          • Note: After rebooting the server, you'll need to reissue the command vncserver -geometry 1200x650. If you wish, you could set up your server to execute this command automatically on startup.

                          Setting up TightVNC Viewer on the Windows 7 client
                          • I visited http://www.tightvnc.com/download.php and downloaded the Installer for Windows (64-bit).
                          • I ran the resulting tightvnc-2.6.4-setup-32bit.msi.
                          • Windows asked whether I wanted to give the installer permission to run, and I said yes.
                          • During the installation, I was asked whether to allow the installer to create a Windows firewall rule. I said no.
                          • I selected custom installation and installed only the client, omitting the server component.
                          • From the Start Menu, I launched TightVNC Viewer.
                          • In the Remote Host field, I entered my server's public IP address followed by port :5901. Example: 12.34.56.78:5901. Then I clicked Connect.
                          • When prompted for a password, I entered the password I made up when installing the server.
                          • A GUI interface to my server opened. Yay!

                          Friday, January 11, 2013

                          Rails - opening a new browser tab or window in the foreground

                          In a Rails website, suppose you'd like to open a new window (or new tab, depending on the user's browser settings) when the user clicks a link. And suppose you'd like to make sure the new tab or window comes to the foreground. And suppose you'd like to specify the URL the "Rails way," using link_to rather than hard-coding an HTML a tag. Here's how...

                          This example assumes "merchant" is an instance of a Rails model class with RESTful routes. It creates a "Shop now" link that opens the merchant's page in a new tab or window and gives it the focus. Of course, this relies on JavaScript's being enabled in the user's browser.

                          <%= link_to "Shop now", merchant, onclick: "var w = window.open(this.href); w.focus(); return false;" %>