Recursion

sunday, october 26th, 2008 6:27pm

After writing the code below recently, I searched out a few cohorts at work to share the joy, but it was late and no one was around...

def makeHierarchicalFolderList( folder_object_list, return_list=[], indent='' ):
    '''
    - Called by: views.uploader3()
    - Purpose: to create a list of community-folders (for upload form) with indents indicating hierarchical relationship. Will likely be replaced by better user-interface.
    '''

    for folder in folder_object_list:
        folder.name = '%s%s' % ( indent, folder.name )
        return_list.append( folder )

        # check for children
        children_count = folder.communityfolder_set.all().count()

        # handle check-results
        if children_count == 0:
            pass
        else:
            children_object_list = folder.communityfolder_set.all()
            indent = indent + '-'
            makeHierarchicalFolderList( children_object_list, return_list, indent=indent )
            indent = indent[1:] # since we're at the end of a processing chain, remove the indent

    return return_list

    # end def makeHierarchicalCommunityFolderList()

What enthused me so was the line a few up from the bottom, where I call the very function in which this line resides. This is called recursion. There's a wonderful slightly mysterious inverted mobius-strip - like quality to recursion, except that the inward tunneling by definition doesn't continue on forever.

The code above neatly meets the needs of a project I'm working on: to prepare a hierarchical listing of folders. Not shown is an initial step in which a query is made for a user-specific list of 'top-level' folders -- that is, folders which do not have a parent folder. For each folder in this list, the folder is first appended to a sort of global list-of-folders-to-return. Then a check is made to see if the folder has any children. If so, those child-folders are selected, and passed to the function itself. So for each top-level folder, a processing-chain begins that might be very, very long, or might be very short. But each processing-chain does terminate, shifting to examine the next sibling folder, and, when there are no more sibling-folders in the lowest-level generation, shifting back upward a generation to examine and process the next sibling-folder.

I use recursion infrequently enough that it sort of gets buried in my toolbox. I forget about it and from habit reach for other tools first that often do the looping job well-enough. Utilizing looping-logic is a fundamental part of programming. Where a built-in looping structure doesn't directly solve looping needs, I most often use a Controller-pattern solution to a looping challenge. That is, I'll have a controller block of code prepare a list of items that need to be looped through, and perhaps set some variables to hold the results of processing, and then call a separate function that handles the actual looping. Sometimes for more complex situations the called looping code can itself call another separate block of looping code. So it is possible to handle some situations, for which recursion might be ideal, with other techniques. But not all situations -- when my usual looping implementation begins feeling overly complex, that's often a sign to root around and dust off the recursion tool.

Despite some left-over 1960's geek-stereotypes about programming being a boring left-brain process, there can be elegance and deep beauty in programming. There are numerous ways to approach and solve a challenge, any of which may work 'well-enough'. Good programmers strive for solutions that are simplest and clearest, and when the right technique lends itself to beautifully simple code, one feels like an artist.

Passwordless logins

friday, may 23rd, 2008 5:48am

[These are notes from a project I worked on in grad-school in 2003-2004. As part of a 'voting' project, I wanted to automate the backup of a postgres database to an offsite location via a dump and rsync. In order to script the backup, my server needed to be able to automatically login to the backup server. A fellow student, J.E., and I worked on this piece together.

Recently a co-worker described a need to do something different, but similar in some ways, so I dug up these notes and pasted 'em in here, fairly raw. Note to hackers: the servers mentioned are long offline.]


Instructions

  • Generate the key...

    [toolbox:~/Desktop] birkin% 
    [toolbox:~/Desktop] birkin% ssh-keygen -t rsa
    Generating public/private rsa key pair.
    Enter file in which to save the key (/Users/birkin/.ssh/id_rsa): 
    /Users/birkin/.ssh/id_rsa already exists.
    Overwrite (y/n)? y
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /Users/birkin/.ssh/id_rsa.
    Your public key has been saved in /Users/birkin/.ssh/id_rsa.pub.
    The key fingerprint is:
    71:04:1a:69:d4:ee:4a:d5:a8:b6:77:65:20:68:12:df birkin@toolbox.local
    [toolbox:~/Desktop] birkin%
    

    The '-t rsa' flag specifies ssh 2 protocol

  • Examine the created keys...

    [toolbox:~/.ssh] birkin% 
    [toolbox:~/.ssh] birkin% ls -alF
    total 48
    drwx------ 7 birkin staff 238 16 Jun 21:15 ./
    drwxr-xr-x 57 birkin staff 1938 16 Jun 17:06 ../
    -rw------- 1 birkin staff 883 17 Jun 08:05 id_rsa
    -rw-r--r-- 1 birkin staff 230 17 Jun 08:05 id_rsa.pub
    -rw------- 1 birkin staff 535 16 Jun 20:39 identity
    -rw-r--r-- 1 birkin staff 339 16 Jun 20:39 identity.pub
    -rw-r--r-- 1 birkin staff 5351 16 Jun 20:56 known_hosts
    [toolbox:~/.ssh] birkin%
    

    The 'identity' files listed were generated when I was initially trying 'ssh -t rsa1', the ssh 1 protocol, and I believe can be ignored.

    [toolbox:~/.ssh] birkin% 
    [toolbox:~/.ssh] birkin% cat id_rsa.pub 
    ssh-rsa
    AAAAB3NzaC1yc2EAAAABIwAAAIEA0xmINQ6w3KGgxEexNJeb5bRDhOyp3R5zWfL6L5ghb8TqWDoF/x1e4KxoVp3NEMd594QISQzb4w74ZNkdGKnIqOEHs1Uy3zbutijsPQhWqXvZ40AMbOpOjawLAcrTWUfqmBcC7MW54cOiu2FIzvlHJhYVOBCyy1nBVduGJUPF5s=
    birkin@toolbox.local
    [toolbox:~/.ssh] birkin%
    
  • Copy the public key to a file titled 'authorized_keys' which will be transferred to the remote computer(s) that I want to connect to.

    [toolbox:~/.ssh] birkin% 
    [toolbox:~/.ssh] birkin% cat id_rsa.pub > ~/Desktop/authorized_keys
    [toolbox:~/.ssh] birkin%
    
  • Let's take a look to make sure it looks right...

    [toolbox:~/.ssh] birkin% 
    [toolbox:~/.ssh] birkin% cd ~/Desktop/
    [toolbox:~/Desktop] birkin% 
    [toolbox:~/Desktop] birkin% ls -alF
    total 64
    drwxr-xr-x 7 birkin staff 238 17 Jun 08:21 ./
    drwxr-xr-x 57 birkin staff 1938 16 Jun 17:06 ../
    -rwxr-xr-x 1 birkin staff 21508 17 Jun 08:20 .DS_Store*
    -rw-r--r-- 1 birkin staff 253 2 Nov 2003 .bash_profile
    -rw-r--r-- 1 birkin staff 0 20 Apr 2003 .localized
    -rw-r--r-- 1 birkin staff 230 17 Jun 08:21 authorized_keys
    drwxr-xr-x 42 birkin staff 1428 17 Jun 08:20 envelope/
    [toolbox:~/Desktop] birkin%         
    [toolbox:~/Desktop] birkin% cat authorized_keys 
    ssh-rsa
    AAAAB3NzaC1yc2EAAAABIwAAAIEA0xmINQ6w3KGgxEexNJeb5bRDhOyp3R5zWfL6L5ghb8TqWDoF/x1e4KxoVp3NEMd594QISQzb4w74ZNkdGKnIqOEHs1Uy3zbutijsPQhWqXvZ40AMbOpOjawLAcrTWUfqmBcC7MW54cOiu2FIzvlHJhYVOBCyy1nBVduGJUPF5s=
    birkin@toolbox.local
    [toolbox:~/Desktop] birkin%
    

    Looks good.

  • Transfer the 'authorized keys' file from my OS X laptop to the remote computer...

    [toolbox:~/Desktop] birkin% 
    [toolbox:~/Desktop] birkin% rsync -v -e /usr/bin/ssh ~/Desktop/authorized_keys birkinbackup@harmonicas.msie.marlboro.edu:/home/birkinbackup/authorized_keys
    birkinbackup@harmonicas.msie.marlboro.edu's password: 
    authorized_keys
    wrote 316 bytes read 42 bytes 31.13 bytes/sec
    total size is 230 speedup is 0.64
    [toolbox:~/Desktop] birkin%
    
  • Make sure it looks right on the remote computer...

    [toolbox:~/Desktop] birkin% 
    [toolbox:~/Desktop] birkin% ssh birkinbackup@harmonicas.msie.marlboro.edu
    birkinbackup@harmonicas.msie.marlboro.edu's password: 
    [birkinbackup@harmonicas birkinbackup]$ 
    [birkinbackup@harmonicas birkinbackup]$ ls -alF
    total 64
    drwx------ 3 birkinbackup birkinbackup 4096 Jun 17 08:27 ./
    drwxr-xr-x 6 root root 4096 Jun 12 15:01 ../
    -rw-r--r-- 1 birkinbackup birkinbackup 230 Jun 17 08:27 authorized_keys
    -rw------- 1 birkinbackup birkinbackup 6306 Jun 17 08:22 .bash_history
    -rw-r--r-- 1 birkinbackup birkinbackup 24 Jun 12 15:01 .bash_logout
    -rw-r--r-- 1 birkinbackup birkinbackup 191 Jun 12 15:01 .bash_profile
    -rw-r--r-- 1 birkinbackup birkinbackup 124 Jun 12 15:01 .bashrc
    -rw-r--r-- 1 birkinbackup birkinbackup 29 Jun 17 08:29 datecrontest
    -rw-r--r-- 1 birkinbackup birkinbackup 847 Jun 12 15:01 .emacs
    -rw-r--r-- 1 birkinbackup birkinbackup 120 Jun 12 15:01 .gtkrc
    drwx------ 2 birkinbackup birkinbackup 4096 Jun 17 00:20 .ssh/
    -rw-rw-r-- 1 birkinbackup birkinbackup 14220 Jun 12 18:59 testdump
    [birkinbackup@harmonicas birkinbackup]$ 
    [birkinbackup@harmonicas birkinbackup]$ cat authorized_keys 
    ssh-rsa
    AAAAB3NzaC1yc2EAAAABIwAAAIEA0xmINQ6w3KGgxEexNJeb5bRDhOyp3R5zWfL6L5ghb8TqWDoF/x1e4KxoVp3NEMd594QISQzb4w74ZNkdGKnIqOEHs1Uy3zbutijs+PQhWqXvZ40AMbOpOjawLAcrTWUfqmBcC7MW54cOiu2FIzvlHJhYVOBCyy1nBVduGJUPF5s=
    birkin@toolbox.local
    [birkinbackup@harmonicas birkinbackup]$
    

    Looks good.

  • Move the file to the right place on the remote computer...

    [birkinbackup@harmonicas birkinbackup]$ 
    [birkinbackup@harmonicas birkinbackup]$ cat authorized_keys >> .ssh/authorized_keys 
    [birkinbackup@harmonicas birkinbackup]$
    

    The double brackets 'append' instead of overwrite. Also, I've checked this out -- the append is correct for our purposes in that it appends the new string on the following line. Actually, what would be nicer for inspection is this...

    [birkinbackup@harmonicas birkinbackup]$ 
    [birkinbackup@harmonicas birkinbackup]$ echo "" >> .ssh/authorized_keys 
    [birkinbackup@harmonicas birkinbackup]$ 
    [birkinbackup@harmonicas birkinbackup]$ cat authorized_keys >> .ssh/authorized_keys 
    [birkinbackup@harmonicas birkinbackup]$
    

    Let's check out the 'real' authorized_keys file (I should name the transfer file something else in the future to avoid any confusion)...

    [birkinbackup@harmonicas birkinbackup]$ 
    [birkinbackup@harmonicas birkinbackup]$ cd .ssh/
    [birkinbackup@harmonicas .ssh]$ 
    [birkinbackup@harmonicas .ssh]$ ls -alF
    total 24
    drwx------ 2 birkinbackup birkinbackup 4096 Jun 17 00:20 ./
    drwx------ 3 birkinbackup birkinbackup 4096 Jun 17 08:27 ../
    -rw-r--r-- 1 birkinbackup birkinbackup 975 Jun 17 08:42 authorized_keys
    -rw------- 1 birkinbackup birkinbackup 887 Jun 17 07:24 id_rsa
    -rw-r--r-- 1 birkinbackup birkinbackup 251 Jun 17 07:24 id_rsa.pub
    -rw-r--r-- 1 birkinbackup birkinbackup 603 Jun 16 17:34 known_hosts
    [birkinbackup@harmonicas .ssh]$ 
    [birkinbackup@harmonicas .ssh]$ cat authorized_keys 
    ssh-rsa
    AAAAB3NzaC1yc2EAAAABIwAAAIEAu4tdcJlZldiAAnfviR3vXWGjwWa4For/kbi/FvBTeTEtctxsS72/ppn5vFydv4V5iLDVdfWKrnTIwfn8BHinq2yvdX9OLsEyjzBqbu+ZIZCi7UefJxEWCdOGtDd0YWiJbQJkyuoHs4ShwF5YcuMcnmiEjOUWJ7B5N9QkXeD3wc0= birkinbackup@harmonicas.msie.marlboro.edu
    
    authorized_keys
    ssh-rsa
    AAAAB3NzaC1yc2EAAAABIwAAAIEA3+PWa9l6hu6sY43u5FASYr26AhRrUQDqcjT5VO+wePg2OaQyTedcNkRIGG6tVquFC+AXH5BOkI+EJAfSCJG2AE0YxSrM16rMgPM1wADJBlmhumiY5wuX5ROOc0azPpvLyjZwwFsSxgqpdtNtvwUCQEl94y3H5qqOvXtR+IVtp30= birkin@toolbox.local
    authorized_keys
    ssh-rsa
    AAAAB3NzaC1yc2EAAAABIwAAAIEA0xmINQ6w3KGgxEexNJeb5bRDhOyp3R5zWfL6L5ghb8TqWDoF/x1e4KxoVp3NEMd594QISQzb4w74ZNkdGKnIqOEHs1Uy3zbutijs+PQhWqXvZ40AMbOpOjawLAcrTWUfqmBcC7MW54cOiu2FIzvlHJhYVOBCyy1nBVduGJUPF5s= birkin@toolbox.local
    
    ssh-rsa
    AAAAB3NzaC1yc2EAAAABIwAAAIEA0xmINQ6w3KGgxEexNJeb5bRDhOyp3R5zWfL6L5ghb8TqWDoF/x1e4KxoVp3NEMd594QISQzb4w74ZNkdGKnIqOEHs1Uy3zbutijs+PQhWqXvZ40AMbOpOjawLAcrTWUfqmBcC7MW54cOiu2FIzvlHJhYVOBCyy1nBVduGJUPF5s= birkin@toolbox.local
    [birkinbackup@harmonicas .ssh]$
    

    The last line is the one we most recently created; the space preceding it is the result of the echo command; the lines 'authorized_keys' are mistakes from issuing echo in my experimentation instead of cat. I'm leaving these in to illustrate that there is tolerance for non-matching entries.

  • Try connecting...

    [birkinbackup@harmonicas .ssh]$ 
    [birkinbackup@harmonicas .ssh]$ exit
    logout
    Connection to harmonicas.msie.marlboro.edu closed.
    [toolbox:~/Desktop] birkin% 
    [toolbox:~/Desktop] birkin% ssh birkinbackup@harmonicas.msie.marlboro.edu
    [birkinbackup@harmonicas birkinbackup]$
    

    No password-prompt: success!

Possible 'gotchas'

  • Before actually trying a connection-script, run a manual ssh first; you may have to once manually ok that key-exchange message you normally see on a first-time ssh.

  • If things aren't working, it could be a permissions issue...

    J.E. sent me a link [2008 note: this was in 2004] to http://kimmo.suominen.com/ssh/ and pointed out the caution to check file and directory permissions if connections still aren't working right after configuring everything.

    This site shows permissions to the ~/.ssh/ directory that allow writing by 'group', even though the text says only the 'owner' should have write permissions to that directory. On my account on the remote-computer, my ~/.ssh/ directory initially allowed group-write permissions, and passwordless login was not working. Changing those to...

    drwx------ 2 birkinbackup birkinbackup 4096 Jun 17 16:18 .ssh/
    

    ...allowed passwordless login to work. The beauteous text...

    [toolbox:~] birkin% 
    [toolbox:~] birkin% ssh birkinbackup@harmonicas.msie.marlboro.edu
    [birkinbackup@harmonicas birkinbackup]$ 
    [birkinbackup@harmonicas birkinbackup]$ ssh birkinbackup@play.msie.marlboro.edu
    [birkinbackup@play birkinbackup]$
    

    No password required. Sweet.

Appreciating django templates

sunday, april 6th, 2008 10:22pm

I'm loving Django templates.

In setting up a site for my bookgroup, I used a model for 'Meeting' that has a 'meeting_date' defined as a 'DateTimeField'. For those who haven't yet tasted the Django kool-aid, that's a field that requires both a valid date and a valid time. Made sense to me, since one of the main reasons for the site is to be able to list the location and date & time of upcoming meetings. However, when entering a few old meetings, the spreadsheet I was working from only listed the month and year (November 1997! -- we've been around for a while!).

There were a couple of ways I could have handled this. What I chose to do was to add two boolean fields: 'fake_date' (meaning 'day') and 'fake_time'. It might have been cleaner to allow we admins to have a year, a month, a date, and a time field -- but I knew going forward the single DateTimeField would work for us and I wanted to build more for the future than the past. So, when entering an old meeting with just a month and year, any date in that month is entered and any time of day, and both fake_date and fake_time are checked.

The complete DateTime object is passed to the template, and then some logic goes to work:

{% if meeting.fake_date %}
    <h2>{{ meeting.date_time|date:"F, Y"|lower }}</h2>
{% else %}
    {% if meeting.fake_time %}
        <h2>{{ meeting.date_time|date:"l, F jS, Y"|lower }}</h2>
    {% endif %}
{% endif %}

{% if not meeting.fake_date and not meeting.fake_time %}
    <h2>{{ meeting.date_time|date:"l, F jS, Y g:iA"|lower }}</h2>
{% endif %}

You can see the results of the non-fake dates here, and the result of the fake dates here (the older meetings) -- the same kind of date-object reaches the template; the logic above handles the presentation.

I could have more efficiently handled the 'happy-path' real-date case via nesting, but I find this a bit more readable.

If the first test matches, the DateTime object info will only show, as an example, 'march 1998'; if the second test matches, the same DateTime object will only show 'march 6, 1998', but not the time.

This is nice. My introduction to templates was via JSPs, using expression-language to pass in values from beans. Since pure java code can be embedded in JSPs, I had trained myself to rigidly keep logic out of templates, and in the above situation would have written that logic within a Java class. When I began working more with php, I looked around for a template system. I had heard good things about 'smarty', but it seemed too heavyweight. That, combined with my fierce aversion to template logic, scared me off. I then attended a wonderful presentation on HTML_Template_ITX, was sold whole-hog, and still use that for my php end-user web work.

What I initially loved about Django's templates is that I didn't have to use any of the logical conditions I show above; it can be used very well very simply. As I've grown more comfortable with Django and python, my philosophical aversion to template-logic has gradually evaporated -- as long as it's presentation logic. The situation above is a perfect example: It's very reasonable for the business-logic end of things to pass to the presentation-layer a date. How that date is then formatted (upper or lowercase, whether or not the day or other elements of the date are shown, etc.) is a very reasonable thing for the template to handle. And that the template can also handle the presentation based on certain conditions of the Meeting instance is very, very, nice.

ssh-tunneling notes

sunday, march 16th, 2008 5:49pm

[This was first posted in 2006, to a no-longer-accessible wiki of mine, to accompany a Brown Internet Programming Group talk I gave. I'm slowly consolidating some of my posts and notes to this site.]


The problem

The situation: My preferred way of working is to program, on my laptop, code that often must communicate with a database running on a remote server. What are good ways of handling this? In the past I used two different approaches.

  • Run a parallel database.
    • Pros: good when lots of database development/reconfiguration is required. No separate connection file is needed.
    • Cons: testing may lead to need to spend effort keeping database structures and sometimes data in sync.
  • Access the remote database by programming the connection-code to determine which host its running on. If running on my laptop, the connection-code would locate the database at an internet address; if running on the same server as the database, the connection-code would locate the database at the localhost address.
    • Pros: Only need to deal with one database.
    • Cons: Non-localhost connectivity may be disabled for security reasons. If others work on the same code, the differing connection-code to detect multiple hosts can be a hassle and can reveal internet passwords. Care must be taken since the password may be transmitted over a non-secure connection.

Solution: Another programmer showed me how he solves this issue via ssh-tunneling. It's a wonderful solution.

Overview

Background info to keep in mind: Common client-server internet connections generally generally do not require specification of originating ports (the computers can pick a port), but do require specification of destination ports.

Example: my browser wants to access a web-page. My browser may send out the http request from any of a range of ports, but will specifically access the server's IP address at port 80, where the web-server is listening.

In ssh-tunneling, the client computer is set up to 'listen' for incoming data on a specified port at the 127.0.0.1 localhost IP address -- and to 'forward' that data, via a pre-established ssh connection, to a specific port at the server's IP address. This terminology may be a bit confusing, because the 'client' -- say, the local development laptop -- is 'listening', which a 'server' normally does. In this case, think of the server as a remote database-server.

What this means for my database situation is that I set up my laptop to listen for incoming data at '127.0.0.1:3306' and to forward that data to 'somehost.services.brown.edu:3306'. All I have to do in my connection code is specify that it attempt to connect to the database at '127.0.0.1:3306'.

The beauty of this is two-fold...

First, the same connection code can run on my laptop and on the server with no modification at all. Example of php connection code...

<?php
    mysql_connect("127.0.0.1:3306", "username", "password") or die ("Sorry, cannot connect to server");
    mysql_select_db("databasename") or die ("Sorry, cannnot connect to database");
?>

Second, because the 'set up' is using SSH as the fowarding mechanism, all data is transferred securely.

Note that there are many different ways of tunneling; this page focuses on one: 'local client' to 'remote service' (in this case, a remote database server).

Setting up the tunnel

Unices

On Linux, Unix, and the Mac, setting up a tunnel is as easy as issuing one command in a terminal window:

ssh -N -L 3306:somehost.services.brown.edu:3306 myaccount@somehost.services.brown.edu

Even if you're going to use a GUI client to set up the tunnel, examine the details of this command to get an understanding of what's going on:

  • The ssh part is the normal secure-shell command.
  • The -N flag specifies that commands flowing over this connection won't be executed on the remote computer, just forwarded.
  • The -L flag specifies the details of the 'localPort:remoteHost:remotePort' section that follows this flag. It means that the local computer should listen for incoming connections on the specified localPort, and forward them over the ssh connection to the remote computer at the remotePort.
  • The 'myaccount@somehost.services.brown.edu' sets up the ssh connection. This prompts me to enter my account-password on the remote computer 'somehost'.

Mac: Fugu

Fugu is an open-source ssh client that supports ssh tunneling.

To set up a tunnel in fugu:

  • Select 'SSH' -> 'New SSH Tunnel'
  • Enter 'somehost.services.brown.edu' in the 'Create Tunnel to' textbox'.
  • Enter '3306' in the 'Service or Port' textbox (think of this as the 'Remote Port').
  • Enter '3306' in the 'Local Port' textbox.
  • Enter 'somehost.services.brown.edu' in the 'Remote Host' textbox.
    • I'm not sure of the distinction between the two 'host' textboxes, but entering info this way works.
  • Enter your username in the 'Username' textbox.
  • Enter '22' in the 'Port' textbox (think of this as the 'SSH Port').

Windows: Putty

Putty is a free Brown-offered ssh client that supports tunneling.

  • Open 'putty.exe' file. The 'PuTTy Configuration' window appears.
  • Click once on 'Category' -> 'Session'.
  • On the right-side of the window enter 'somehost.services.brown.edu' in the 'Host Name (or IP address)' textbox.
  • Enter '22' in the 'Port' textbox.
  • Select 'SSH' for the 'Protocol'.
  • Click once on 'Category' -> 'SSH' -> 'Tunnels'.
  • Enter '3306' in the 'Source port' textbox.
  • Enter 'somehost.services.brown.edu:3306' in the 'Destination' textbox.
  • Select the 'Remote' radio-button under the 'Destination' textbox.
  • Click the 'Add' button.
  • Click the 'Open' button. The putty terminal window opens.
  • When prompted by 'login as', enter your username and hit return.
  • When prompted by for your password, enter it and hit return.
  • That's it; the tunnel is established.

More tunnel fun

Web

The idea...

ssh -N -L 5005:123.123.123.123:80 myaccount@123.123.123.123

To implement this in Fugu or Putty, just switch the IP address, use '5005' for the 'Local Port' (Fugu) or 'Source Port' (Putty), and use '80' for the 'Service or Port' (Fugu) or the port following the host+colon in the 'Category' -> 'SSH' -> 'Tunnels' 'Destination' textbox (Putty).

You can then access a web page on the 123.123.123.123 server using an http://127.0.0.1:5005 address instead of the normal http://123.123.123.123 address.

Note that the 5005 'localPort' can really be any unused port above 1000. The only reason I keep the 'localPort' and 'remoteHostPort' the same in the database example is so my database connection code is the same and works the same on my development laptop and the actual database server.

Other

Note that these techniques can be applied in a wide variety of situations. * I've switched over to tunneled connections from Eclipse, my programming IDE, to my Subversion repositories. * Brown email is encrypted over the network, but if you have a home account that's not, you can check it from a coffee-shop unencrypted wireless network using ssh-tunneling.

Implementing horizontal scrollbars

saturday, march 8th, 2008 8:01am

[2008-03-16 update: I recently realized that IE6 does not display the horizontal scrollbars below properly (IE7 does, as do Firefox & Safari). My understanding is that IE6 does not respect the 'maxwidth' css setting, and that dokuwiki handles this via a section of javascript within a large block of javascript. I don't want to embed all the unnecessary javascript for this specific issue, so until I can learn about and implement an alternate solution, my apologies to those of you using IE6.]


The goal: To implement horizontal scrollbars for code containing long lines.

The reason: It's annoying when long code lines overrun right-hand sections of pages, or when they automatically widen the entire page, such that areas of normally-formatted text end up requiring horizontal scrolling.

Example of solution...

birkinbox:~ birkin$ 
birkinbox:~ birkin$ python
Python 2.4.1 (#1, Feb  1 2006, 18:35:57) 
[GCC 4.0.0 (Apple Computer, Inc. build 5026)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> import urllib
>>> reference = urllib.urlopen('http://google.com')
>>> reference.read()
'<html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><title>Google</title><style>body,td,a,p,.h{font-family:arial,sans-serif}.h{font-size:20px}.h{color:#3366cc}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}.lnc:link,.lnc:visited{color:#00c}.pgtab,.pgtab:hover,.pgtabselected,.pgtabside{text-align:center;text-decoration:none;color:#00c;display:block;height:27px;float:left;overflow:hidden;background:url(/intl/ja/images/productlinktabs.png) no-repeat;padding-top:8px}.pgtab{width:130px;background-position:-274px 0}.pgtab:hover{width:130px;background-position:-144px 0}.pgtabselected{width:144px}.pgtabside{width:3px;background-position:-404px 0}.ptr{cursor:pointer;cursor:hand}.iconl{background:url() no-repeat;overflow:hidden;height:px;width:px}#gbar{float:left;height:22px;padding-left:2px}.gbh,.gb2 div{border-top:1px solid #c9d7f1;font-size:0;height:0}.gbh{position:absolute;top:24px;width:100%}.gb2 div{margin:5px}#gbi{background:#fff;border:1px solid;border-color:#c9d7f1 #36c #36c #a2bae7;font-size:13px;top:24px;z-index:1000}#guser{padding-bottom:7px !important}#gbar,#guser{font-size:13px;padding-top:1px !important}@media all{.gb1,.gb3{height:22px;margin-right:.73em;vertical-align:top}.gb2 a{display:block;padding:.2em .5em}}#gbi,.gb2{display:none;position:absolute;width:8em}.gb2{z-index:1001}#gbar a{color:#00c}.gb2 a,.gb3 a{text-decoration:none}#gbar .gb2 a:hover{background:#36c;color:#fff;display:block}</style><script>window.google={kEI:"XIjSR5-NKZz0iAHLxMxp",kEXPI:"0",kHL:"en"};\nfunction sf(){document.f.q.focus()}\nwindow.gbar={};(function(){var a=window.gbar,b,g,h;function l(c,f,e){c.display=h?"none":"block";c.left=f+"px";c.top=e+"px"}a.tg=function(c){var f=0,e=0,d,m=0,n,j=window.navExtra,k,i=document;g=g||i.getElementById("gbar").getElementsByTagName("span");(c||window.event).cancelBubble=!m;if(!b){b=i.createElement(Array.every||window.createPopup?"iframe":"DIV");b.frameBorder="0";b.scrolling="no";b.src="#";g[7].parentNode.appendChild(b).id="gbi";if(j&&g[7])for(n in j){k=i.createElement("span");k.appendChild(j[n]);g[7].parentNode.insertBefore(k,g[7]).className="gb2"}i.onclick=a.close}while(d=g[++m]){if(e){l(d.style,e+1,f+25);f+=d.firstChild.tagName=="DIV"?9:20}if(d.className=="gb3"){do e+=d.offsetLeft;while(d=d.offsetParent)}}b.style.height=f+"px";l(b.style,e,24);h=!h};a.close=function(c){h&&a.tg(c)}})();</script></head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onload="sf();if(document.images){new Image().src=\'/images/nav_logo3.png\'}" topmargin=3 marginheight=3><div id=gbar><nobr><span class=gb1><b>Web</b></span> <span class=gb1><a href="http://images.google.com/imghp?hl=en&tab=wi">Images</a></span> <span class=gb1><a href="http://maps.google.com/maps?hl=en&tab=wl">Maps</a></span> <span class=gb1><a href="http://news.google.com/nwshp?hl=en&tab=wn">News</a></span> <span class=gb1><a href="http://www.google.com/prdhp?hl=en&tab=wf">Shopping</a></span> <span class=gb1><a href="http://mail.google.com/mail/?hl=en&tab=wm">Gmail</a></span> <span class=gb3><a href="http://www.google.com/intl/en/options/" onclick="this.blur();gbar.tg(event);return !1"><u>more</u> <small>&#9660;</small></a></span> <span class=gb2><a href="http://video.google.com/?hl=en&tab=wv">Video</a></span> <span class=gb2><a href="http://groups.google.com/grphp?hl=en&tab=wg">Groups</a></span> <span class=gb2><a href="http://books.google.com/bkshp?hl=en&tab=wp">Books</a></span> <span class=gb2><a href="http://scholar.google.com/schhp?hl=en&tab=ws">Scholar</a></span> <span class=gb2><a href="http://finance.google.com/finance?hl=en&tab=we">Finance</a></span> <span class=gb2><a href="http://blogsearch.google.com/?hl=en&tab=wb">Blogs</a></span> <span class=gb2><div></div></a></span> <span class=gb2><a href="http://www.youtube.com/?hl=en&tab=w1">YouTube</a></span> <span class=gb2><a href="http://www.google.com/calendar/render?hl=en&tab=wc">Calendar</a></span> <span class=gb2><a href="http://picasaweb.google.com/home?hl=en&tab=wq">Photos</a></span> <span class=gb2><a href="http://docs.google.com/?hl=en&tab=wo">Documents</a></span> <span class=gb2><a href="http://www.google.com/reader/view/?hl=en&tab=wy">Reader</a></span> <span class=gb2><div></div></a></span> <span class=gb2><a href="http://www.google.com/intl/en/options/">even more &raquo;</a></span> </nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div><div align=right id=guser style="font-size:84%;padding:0 0 4px" width=100%><nobr><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Den%26source%3Diglk&usg=AFQjCNFA18XPfgb7dKnXfKz7x7g1GDH1tg">iGoogle</a> | <a href="https://www.google.com/accounts/Login?continue=http://www.google.com/&hl=en">Sign in</a></nobr></div><center><br clear=all id=lgpd><img alt="Google" height=110 src="/intl/en_ALL/images/logo.gif" width=276><br><br><form action="/search" name=f><table cellpadding=0 cellspacing=0><tr valign=top><td width=25%>&nbsp;</td><td align=center nowrap><input name=hl type=hidden value=en><input type=hidden name=ie value="ISO-8859-1"><input maxlength=2048 name=q size=55 title="Google Search" value=""><br><input name=btnG type=submit value="Google Search"><input name=btnI type=submit value="I\'m Feeling Lucky"></td><td nowrap width=25%><font size=-2>&nbsp;&nbsp;<a href=/advanced_search?hl=en>Advanced Search</a><br>&nbsp;&nbsp;<a href=/preferences?hl=en>Preferences</a><br>&nbsp;&nbsp;<a href=/language_tools?hl=en>Language Tools</a></font></td></tr></table></form><br><br><font size=-1><a href="/intl/en/ads/">Advertising&nbsp;Programs</a> - <a href="/services/">Business Solutions</a> - <a href="/intl/en/about.html">About Google</a></font><p><font size=-2>&copy;2008 Google</font></p></center></body></html>'
>>>

Solution

All credit goes to Dokuwiki, which implements this solution via CSS (I've also seen javascript solutions). I used the Firefox web-developer plug-in to figure out what part of the CSS works the magic, and came up with the adapted CSS below. (Note comment implying the second style may not be necessary.)

pre { /* allows horizontal scrollbars to appear; excellent for code */
    font-size: 120%;
    padding-top: 0.5em;
    padding-right: 0.5em;
    padding-bottom: 0.5em;
    padding-left: 0.5em;
    border-top-width: 1px;
    border-right-width: 1px;
    border-bottom-width: 1px;
    border-left-width: 1px;
    border-top-style: dashed;
    border-right-style: dashed;
    border-bottom-style: dashed;
    border-left-style: dashed;
    border-top-color: #8cacbb;
    border-right-color: #8cacbb;
    border-bottom-color: #8cacbb;
    border-left-color: #8cacbb;
    color: Black;
    background-color: #f7f9fa;
    overflow-x: auto;
    overflow-y: auto;
    }
* html .insitu-footnote pre.code, * html .insitu-footnote pre.file { /* 2008-03-08-Sat note to self: this was added for horizontal scrollbar support, but doesn't seem necessary for Safari/Mac or FF/Mac -- after reinstalling Parallels test in IE/Win and delete if it's not necessary */
    padding-bottom: 18px;
    }

Nice!