Flask/Gevent/Gunicorn on Xubuntu with Upstart

Flask

Flask is a Python web micro-framework. I’ll not go into details too much, you can read more in the docs if you are interested. What I’m going to use it for is to make a small Flask-based (and thus Python-based) test bed.

Flask installation

Here are the steps needed to get Flask up and running on Xubuntu 12.04. It should be straightforward on other systems, too, but bear in mind there might be differences.

On Xubuntu systems, Python is installed by default – on 12.04 you have this version:

python 2.7.3-0ubuntu2

If you don’t have it for some reason:

sudo apt-get install python

I prefer to use virtualenv to manage python environments. A handy extension is called virtualenvwrapper, so let’s install it:

$ sudo apt-get install virtualenvwrapper 

You’ll notice when you run the above that you also install some other packages:

The following extra packages will be installed:
  libjs-sphinxdoc libjs-underscore python-pip python-setuptools python-virtualenv

The important ones for our story are the python-* ones:

  • pip is a python package manager
  • setuptools is used by pip
  • virtualenv is of course the virtual environment manager I mentioned above

Now, one note for Xubuntu. When you install virtualenvwrapper package, it installs some docs here:

/usr/share/doc/virtualenvwrapper/en/html/index.html

which is similar to this online doc page. In both of these, it says that you should do something like this:

$ pip install virtualenvwrapper
...
$ export WORKON_HOME=~/Envs
$ mkdir -p $WORKON_HOME
$ source /usr/local/bin/virtualenvwrapper.sh
$ mkvirtualenv env1
Installing
...

This actually will not work in Xubuntu, at least in the current version that I have installed, which is 2.11.1-2. The thing is, there’s a small file here:

/usr/share/doc/virtualenvwrapper/README.Debian

that explains Debian differences. What is happening here is that Debian package actually puts the file in this location:

/etc/bash_completion.d/virtualenvwrapper

and is intended to be used with bash-completion package. This works fine for me, but if it doesn’t work for you, you can always manually source this file as explained in the docs (and also the above README.Debian file):

$ source /etc/bash_completion.d/virtualenvwrapper

and you are all set to go.

Note that in either case you need to set up virtualenvwrapper – this is explained in /etc/bash_completion.d/virtualenvwrapper, here’s how I have it configured:

$ mkdir $HOME/.virtualenvs
$ echo 'export WORKON_HOME=$HOME/.virtualenvs' >> ~/.bashrc

You need to start a new bash session to pick up the .bashrc config – it will do something like this:

$ bash
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/initialize
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/premkvirtualenv
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/postmkvirtualenv
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/prermvirtualenv
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/postrmvirtualenv
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/predeactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/postdeactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/preactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/postactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/get_env_details
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/premkproject
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/postmkproject
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/prermproject
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/postrmproject

and you should be good to go.

Let’s start a sample python environment:

$ mkvirtualenv default
New python executable in default/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/default/bin/predeactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/default/bin/postdeactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/default/bin/preactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/default/bin/postactivate
virtualenvwrapper.user_scripts creating /home/icyrock.com/.virtualenvs/default/bin/get_env_details

Note that whenever you have a fresh bash run, no virtualenv will be initialized. Do this if so:

$ workon default

This will initialized the virtualenv we aptly named “default”.

Finally, let’s install Flask and its dependencies:

$ pip install flask
Downloading/unpacking flask
  Downloading Flask-0.9.tar.gz (481Kb): 481Kb downloaded
  Running setup.py egg_info for package flask
...
Cleaning up...

Let’s do a quick test that all is OK. Following [this guide] from Flask itself, do this:

$ cat >flask_hello_world.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()
^D
$ python flask_hello_world.py 
 * Running on http://127.0.0.1:5000/

(Note for those not familiar – ^D above means press Ctrl+D to send EOF to cat). Browse to http://localhost:5000 and you should see “Hello World!” written to confirm Flask is installed and all OK.

Flask + gevent + gunicorn

Whenever you restart the machine you are working on, to run a Flask application, you need to:

  • Start your terminal (usually a bash session these days)
  • Initialize a Python virtualenv (“default” in our case) by running “workon default”
  • Run “python application-name.py”

This can be scripted, but you at least need to do something manually. If you are developing an application that should be running all the time, you obviously don’t want to have manual steps. This can be solved in different ways, such as:

  • Run the server on startup (e.g. using upstart)
  • Run it when the user logs in (e.g. Xubuntu has a convenient Session and Startup app in Session Manager)
  • Use xinetd
  • Use a standalone server, such as the ones described in Flask’s deployment options page

I opted for the last one. It requires some sort of communication between the server and the Flask application. When it’s for Python applications, WSGI is typically used for this purpose. I found this page describing different WSGI servers and measuring their performance in depth. It’s a couple of years old, but it’s a great write up nonetheless. The conclusion was that gevent is the best option. Obviously, far from that I need it in this case, but it’s a good exercise for the future.

Flask’s deployment page also has a gevent setup part. To make a gevent front-end for our hello world application, put this in a file called flask_hello_world_gevent.py in the same folder where flask_hello_world.py lies:

from gevent.wsgi import WSGIServer
from flask_hello_world import app

http_server = WSGIServer(('', 5000), app)
http_server.serve_forever()

For the above to work, you’ll have to install libevent-dev and gevent:

$ sudo apt-get install libevent-dev
...
$ pip install gevent
...

Now you can run it:

$ python flask_hello_world_gevent.py 
127.0.0.1 - - [2012-07-14 16:41:40] "GET / HTTP/1.1" 200 12 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0.1"

The above shows the first request, too. You should get the familiar “Hello World!” text at this point by going to http://localhost:5000/.

The problem with the above is that it’s not really a server – you still need to run it via python. Not that this cannot be scripted, but there’s a better option – use Gunicorn with gevent. So install it:

$ pip install gunicorn

and start it:

$ gunicorn flask_hello_world:app -kgevent
2012-07-14 17:46:12 [17983] [INFO] Starting gunicorn 0.14.5
2012-07-14 17:46:12 [17983] [INFO] Listening at: http://127.0.0.1:8000 (17983)
2012-07-14 17:46:12 [17983] [INFO] Using worker: gevent
2012-07-14 17:46:12 [17986] [INFO] Booting worker with pid: 17986

Again, you should get the overly familiar “Hello World!” text, only now by going to http://localhost:8000/. Type “gunicorn -h” for a full page of options gunicorn supports, some of which can be used for controlling things such as the number of worker processes, bind address / port, logging files, etc.

Starting up applications with upstart

Upstart is useful when you have a need to manage process lifecycle, i.e. start / stop on boot or some events. It’s a good solution for having our Gunicorn server auto-started when the machine starts. As of version 1.3, Upstart supports user-level services and as of version 1.4 it supports setuid / setgid, which will be useful here to set the user / group. Let’s create a service called gunicorn:

$ cat|sudo tee /etc/init/gunicorn.conf
setuid icyrock.com
setgid icyrock.com
script
  export HOME=/home/icyrock.com
  . $HOME/.virtualenvs/default/bin/activate
  cd $HOME/web/flask
  gunicorn flask_hello_world:app -kgevent
end script
^D

Now we can start and stop the server easily with start / stop commands and get its status with status:

$ sudo start gunicorn
gunicorn start/running, process 18790
$ status gunicorn
gunicorn start/running, process 18790
$ sudo stop gunicorn
gunicorn stop/waiting
$ status gunicorn
gunicorn stop/waiting

After “start unicorn”, you should get now annoyingly familiar “Hello World!” text at http://localhost:8000/.

Be Sociable, Share!

2 Responses to “Flask/Gevent/Gunicorn on Xubuntu with Upstart”

Assil Ksiksi on August 27th, 2012 02:40:

Thanks for the excellent guide.

One question: I’ve read on other sites that Gunicorn serves dynamic content, so it should be uses with a HTTP server like Apache or nginx. Is this true, or have I misunderstood the concept?

Thanks again.


icyrock.com on August 28th, 2012 18:29:

Thanks Assil – I agree, note that this was mostly for development purposes that I had (and that was low traffic and only dynamic processing). You can read even on Gunicorn site that they recommend using nginx proxy in front – see here. nginx should also be used to serve static content, as there’s no need to go through python for that.


Leave a Reply


seven × = 49

Building Emacs from source on Xubuntu 12.04

Emacs is an extensible text editor written mostly in Emacs Lisp (or elisp for short). It’s quite old – the first version was released in 1976. according to Wikipedia article about Emacs. It’s, however, still regularly updated – the last stable version as of this writing is 23.4 released on January 29th, 2012.

One problem with Emacs is exactly its extensibility and customizability. It’s updated very often, so the last stable release is, well, unusable. All the extensions and testing is usually done on the development branch – currently, the preview release is 24.1-rc, released just yesterday, June 1st, 2012. In the current Xubuntu repository, the current version is present:

Version: 23.3+1-1ubuntu9

In order to compensate for this, I (and I suppose many other people) install directly from source. Here’s how to easy do this using the git mirror repository.

Steps for building Emacs

  • Build packages

In order to build emacs, you’ll need some packages installed that don’t come pre-installed on a fresh Xubuntu install. In case you did not, install these:

$ sudo apt-get install autoconf automake build-essential libjpeg-dev libgif-dev libncursesw5-dev libpng-dev libtiff4-dev texinfo
  • Clone the git mirror of Emacs

On this Emacs Wiki page – called Emacs From Git – you have several git repositories listed. Pick one, e.g. the one on gnu.org, and clone it somewhere:

$ git clone git://git.savannah.gnu.org/emacs.git

This is going to take a while, so be patient.

  • Build Emacs

Emacs uses the standard Linux configure scripts and make command. To build it, enter the newly cloned git repository and issue configure / make:

$ cd emacs
$ ./autogen.sh
$ ./configure
$ make

The above assumes that you want to install emacs in /usr/local – change that if you have other preferences here. To do that, run configure like this instead:

$ ./configure --prefix=/my/emacs/path

The build step will likely take even more then git cloning, so grab a few coffees or something.

  • Installing

After compiling Emacs, you can now install it:

$ sudo make install

For for a custom folder setup:

$ sudo make install --prefix=/my/emacs/path
  • Test run

Run emacs to confirm the version:

$ emacs --version
GNU Emacs 24.1.50.1
Copyright (C) 2012 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

All set.

Be Sociable, Share!

Leave a Reply


nine + = 13

Xubuntu – moving windows between monitors

I just installed Xubuntu 12.04. I like it so far and am getting used to differences (was on Ubuntu 11.10 before). One thing that I had with Ubuntu by default was Compiz. It has a lot of plugins, one of which I used a lot was Compiz Put plugin. I could not find a similar thing in Xfce – there are shortcuts to move between workspaces, but not between the monitors that belong to one workspace.

Now I searched over the net and found some useful things – one of them is xdotool. It’s a nice utility that allows you to perform different operations on available X windows, such as move, hide, maximize, etc. Xfce has keyboard shortcuts readily available – Main menu / Settings / Settings Manager, Keyboard, Application Shortcuts tab. These allow you to bind a keyboard shortcut and run any application, including bash scripts, with it. Exactly what I need here – make a xdotool script that moves the window left or right, depending on the current position.

Searching for a window

To do something to a window, you first search for it. You can do this using the following command:

wid=`xdotool search --name Calculator|head -1`

The above will yield the window ID of the first found window that has “Calculator” in the default set of search categories (window class, window class name or window name / title).

Displaying the names of all windows

In case you don’t know the name of the window – for example, if you turned off the window decorations – you can use this script to show them:

wids=`xdotool search --name ""`
for wid in $wids; do
  wname=`xdotool getwindowname $wid`
  if [ -n "$wname" ]; then
    echo $wid --- $wname
  fi
done

which gives the output such as:

16777220 --- xfce4-panel
50331652 --- xfce4-verve-plugin
54525956 --- xfce4-cpugraph-plugin
37748740 --- xfce4-netload-plugin
52428804 --- xfce4-systemload-plugin
58720260 --- xfce4-xkb-plugin
56623108 --- wrapper
...

One thing you’ll notice is that it’s quite slow – that’s because it needs to spawn a shell and a xdotool instance for each of the window IDs it found in the first line.

Moving windows

Let’s say we wanted to move our calculator instance 10px to the left, just for fun. What we need to do is:

  • Find the window ID of the calculator
  • Find it’s geometry (position and size)
  • Calculate the new value for position
  • Move the window there

Here’s a script that does these steps:

wid=`xdotool search --name Calculator|head -1`
eval `xdotool getwindowgeometry --shell $wid`
X=$(($X - 10))
xdotool windowmove $wid $X $Y

You can test this easily – just run this in a terminal while you have a calculator instance running.

Maximized windows

Now, there’s a slight problem with the above – it doesn’t work with maximized windows. Although that’s not usually a problem (i.e. maximized windows are considered stationary for regular purposes), for the purpose of moving to the other monitor this will be a problem. There’s another utility – wmctrl – that can be used for this job:

wid=`xdotool search --name Calculator|head -1`
wmctrl -ir $wid -b remove,maximized_vert,maximized_horz

Similarly, to maximize:

wid=`xdotool search --name Calculator|head -1`
wmctrl -ir $wid -b add,maximized_vert,maximized_horz

You can even toggle:

wid=`xdotool search --name Calculator|head -1`
wmctrl -ir $wid -b toggle,maximized_vert,maximized_horz

Now, we can use the third tool – xprop – to actually get the state:

wid=`xdotool search --name Calculator|head -1`
xprop -id $wid _NET_WM_STATE

This should print something like this:

$ xprop -id $wid _NET_WM_STATE
_NET_WM_STATE(ATOM) = 
$ xprop -id $wid _NET_WM_STATE
_NET_WM_STATE(ATOM) = _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_MAXIMIZED_VERT

In the first case, the window is not maximized, in the second case it is (both vertically and horizontally).

Hiding windows

Another issue with this is – if we are to move the windows, it’s going to actually be very flickery. This is especially true if the difference between maximized and minimized versions of the window are great. So, we need to hide / show them before we do all the things. xdotool can do that:

wid=`xdotool search --name Calculator|head -1`
xdotool windowunmap $wid

hides the window. To show it:

wid=`xdotool search --name Calculator|head -1`
xdotool windowmap $wid

Unfortunately, this is not going to help due to the issue – when the window is shown (mapped), all the changes to its position / size seem to be lost.

Getting the current window

As this is going to be a keyboard shortcut, it needs to work on the current window – xdotool allows getting the current window ID like this:

wid=`xdotool getactivewindow`

Raising the window

When moved to the other monitor, the window may actually be below the other windows, depending on how their creation / switching happened, so we need to raise it – this can be done like this:

wid=`xdotool getactivewindow`
xdotool windowraise $wid

Moving the active window to the other monitor (finally!)

Here’s what we need to do:

  • Find the active window
  • Get its maximized state and remember it
  • Remove maximization
  • Get its geometry
  • Calculate the new position
  • Move it
  • Maximize based on the previous state
  • Raise it

Here’s a script that does that:

wid=`xdotool getactivewindow`
max_state=`xprop -id $wid _NET_WM_STATE`

wmctrl -ir $wid -b remove,maximized_vert,maximized_horz
eval `xdotool getwindowgeometry --shell $wid`

new_x=1600
if [[ "$X" -ge "$new_x" ]]; then
  new_x=0
fi

xdotool windowmove $wid $new_x $Y
if [ -z "${max_state/*_NET_WM_STATE_MAXIMIZED_*/}" ]; then
  wmctrl -ir $wid -b add,maximized_vert,maximized_horz
fi

xdotool windowraise $wid

Some problems

There are some problems with the above that I’ve noticed:

  • Obviously, as made works only with two horizontally located monitors side by side
  • It’s a bit slow as it spawns a lot of shells / runs a lot of commands
  • Although it’s not hard to get one, hard-coding the new_x to the half of your monitor setup speeds this up – a trade-off here
  • When you have a panel on one monitor only, moving the windows slides below the panel if they are in the same position on the other monitor
Be Sociable, Share!

2 Responses to “Xubuntu – moving windows between monitors”

Johannes on March 22nd, 2013 13:49:

@DISPLAYING NAMES OF WINDOWS

xdotool search –name “” will return other elements than top level windows as well. Add –onlyvisible is probably what you want to get a list of accessible windows:

wids=xdotool search --onlyvisible --name "" for wid in $wids; do wname=xdotool getwindowname $wid if [ -n "$wname" ]; then echo $wid — $wname fi done

Anyway, Thank You!


icyrock.com on March 25th, 2013 18:39:

Ha! Didn’t know about --onlyvisible until now, thanks for a nice addition Johannes!


Leave a Reply


9 − eight =

Clojure, Seesaw and Java Web Start with Leiningen

Here’s a quick tutorial about how you can put your Clojure + Seesaw applications on the Web to be started using Java Web Start. I will be using Leiningen build tool.

Sample Clojure + Seesaw application using Leiningen

Let’s make a sample app first.

$ lein new com.icyrock.clojure.csjws
Created new project in: /home/icyrock.com/clojure/com.icyrock.clojure.csjws
Look over project.clj and start coding in com/icyrock/clojure/csjws/core.clj

Edit your project.clj to this:

(defproject com.icyrock.clojure.csjws "1.0.0-SNAPSHOT"
  :description "com.icyrock.clojure.csjws"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [seesaw "1.4.0"]]
  :main com.icyrock.clojure.csjws.core)

Run lein deps to fetch the dependencies.

Edit the only file in src subtree:

src/com/icyrock/clojure/csjws/core.clj

to the following:

(ns com.icyrock.clojure.csjws.core
  (:gen-class)
  (:use [seesaw core mig]))

(defn make-button [row col]
  (button :text (format "(%d, %d)" row col)
          :listen [:action
                   (fn [e] (alert (format "Hi, you clicked on (%d, %d)!" row col)))]))

(defn make-content []
  (mig-panel
   :constraints ["fill"]
   :items
   (let [rows 4 cols 4]
     (for [row (range rows) col (range cols)]
       [(make-button row col)
        (if (and (< row (dec rows)) (= col (dec cols))) "grow, wrap" "grow")]))))

(defn -main [& args]
  (native!)
  (-> (frame :title "com.icyrock.clojure.csjws.core"
             :content (make-content)
             :width 400
             :height 400
             :on-close :exit)
      show!))

Run it with lein run – this will give you a 4×4 button grid such as this:

Signing

Seesaw is using reflection, for which you need security permissions to run. To be able to grant these, jar needs to be signed. Here are the steps needed for a self-signed jar:

  • Generate public / primary key pair
  • Sign jar given that certificate

The first step is needed only if you don’t have the certificate (or want to use a new one). All you need is contained in the JDK itself.

Generate public / primary key pair

$ keytool -genkeypair -alias icyrock.com \
-keystore icyrock.com.jks -validity 365 \
-storepass password -keypass password \
-dname "CN=icyrock.com,O=icyrock.om,C=US"

This will give you a file called icyrock.com.jks, protected by password “password”, which contains a key pair with alias “icyrock.com” valid for 365 days and protected by password “password”. You can list this:

$ keytool -list -keystore icyrock.com.jks -storepass password -keypass password -v

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: icyrock.com
Creation date: Apr 11, 2012
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=icyrock.com, O=icyrock.om, C=US
Issuer: CN=icyrock.com, O=icyrock.om, C=US
Serial number: 4f864b76
Valid from: Wed Apr 11 23:26:46 EDT 2012 until: Thu Apr 11 23:26:46 EDT 2013
Certificate fingerprints:
     MD5:  34:8E:22:BF:D8:1A:C8:6F:40:ED:8B:B5:97:5A:65:75
     SHA1: 2E:49:87:1D:14:11:C6:C3:F1:AE:FC:6A:60:82:10:38:8E:B4:67:FA
     Signature algorithm name: SHA1withDSA
     Version: 3

Note that the above is actually a X.509 certificate (see keytool docs for more info). This certificate can be used to sign jars with jarsigner, which is all we need.

Security note

The file that keytool generated (icyrock.com.jks) is called JKS – Java Key Store – for a very good reason. It stores key information. This line from the above is important:

Entry type: PrivateKeyEntry

That should be sufficient for anyone to consider this file very important from security standpoint. It contains information that allows anyone in its possesion to create signed files. Those files will bear that certificate details, but the content might be vastly different. In real life, this is called identity theft. In the same way you would not give your house key to a random person, you should not give your .jks files to random people.

On the other hand, you can give out your certificate – this can be used by other people to verify your jar. To export the certificate, you do this:

$ keytool -export -keystore icyrock.com.jks -storepass password -keypass password -file icyrock.com.crt -alias icyrock.com

You can now give this cert file, which others can import into their .jks keystore like this:

$ keytool -importcert -file icyrock.com.crt -keystore icyrock.com-cert.jks -storepass password -keypass password -alias icyrock.com-cert

The certificate itself is different than the key, here’s the output:

$ keytool -list -keystore icyrock.com-cert.jks -storepass password -keypass password -v
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: icyrock.com-cert
Creation date: Jul 14, 2012
Entry type: trustedCertEntry

Owner: CN=icyrock.com, O=icyrock.om, C=US
Issuer: CN=icyrock.com, O=icyrock.om, C=US
Serial number: 4f864b76
Valid from: Wed Apr 11 23:26:46 EDT 2012 until: Thu Apr 11 23:26:46 EDT 2013
Certificate fingerprints:
     MD5:  34:8E:22:BF:D8:1A:C8:6F:40:ED:8B:B5:97:5A:65:75
     SHA1: 2E:49:87:1D:14:11:C6:C3:F1:AE:FC:6A:60:82:10:38:8E:B4:67:FA
     SHA256: 2D:63:93:C0:26:8D:8C:5C:16:AD:22:D9:31:42:05:F2:9D:B8:9C:75:BC:68:F1:40:D6:8B:95:7C:61:D2:79:2B
     Signature algorithm name: SHA1withDSA
     Version: 3

The important line here is:

Entry type: trustedCertEntry

which says it contains the certificate only. To compare to the house analogy, this would be as if you gave someone your address. They could then come to your house and confirm that’s really your house, but without the key that only you have they could not come in. In a similar way, this certificate can be used to verify .jar files, but not sign them.

Creating uberjar and signing it

Leiningen has uberjar command which creates a jar that contains your code plus all dependencies packed into one jar. Run that and you should get a jar called com.icyrock.clojure.csjws-1.0.0-standalone.jar in the root folder of the project.

Let’s sign that jar with the certificate we created above:

$ jarsigner -keystore icyrock.com.jks \
-storepass password -keypass password \
-signedjar com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone.jar icyrock.com

This will generate a new file:

com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar

that is now signed. You can verify this file:

$ jarsigner -verify \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 
jar verified.

Warning: 
This jar contains entries whose certificate chain is not validated.

Re-run with the -verbose and -certs options for more details.

Due to our certifiacte being self-signed, the above warning is printed. Now the certificate file we generated above comes into play:

$ jarsigner -keystore icyrock.com-cert.jks -verify \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 
jar verified.

In this case, jarsigner used the provided certificate to confirm that all contents of the .jar file actually were signed with the key corresponding to that certificate.

You can start this using regular java -jar command:

$ java -jar \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 

Java Web Start

Now that you have a signed file, you can put it on some Web server to be picked up by javaws. For this, you first need to make a .jnlp file. Say you have a local Web server accessible via http://localhost and you want to put this under http://localhost/clojure/. Here’s a JNLP file that would work for that arrangement:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" 
      codebase="http://localhost/clojure/"
      href="csjws.jnlp">
  <information>
    <title>com.icyrock.clojure.csjws.core</title>
    <vendor>icyrock.com</vendor>
    <icon href="com.icyrock.clojure.csjws.png"/>
    <offline-allowed/>
  </information>
  <security>
      <all-permissions/>
  </security>
  <resources>
    <j2se version="1.6.0+"/>
    <jar href="com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar"/>
  </resources>
  <application-desc main-class="com.icyrock.clojure.clsjws.core"/>
</jnlp> 

Now if you put this JNLP and the signed jar file (com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar) so they are accessible via http://localhost/clojure/, you should be able to then do:

$ javaws http://localhost/clojure/com.icyrock.clojure.csjws.jnlp

Of course, you can do that with any browser that has Java plug-in enabled by going to the same location:

http://localhost/clojure/com.icyrock.clojure.csjws.jnlp

This should launch Java Web Start. It will download the file and ask you to acknowledge the certificate (as it is self-signed, not by a trusted CA authority). To confirm this is the right certificate, you can compare the fingerprints presented by javaws and the certificate itself. In general, only if these match and only if you trust the provider of the certificate should you proceed – otherwise, you can potentially allow malicious code to execute without security restrictions, which is not a desirable result.

This may look like this:

When you click on “More information…”, you get this:

When you click on “Certificate details…”, you get this:

Here you can see the SHA1 fingerprint. Take this and compare to the certificate. Usually, for self-signed certificates, this can be given e.g. on the site for users to compare. If these match, you can be pretty sure the code was signed by that certificate and all is good. The best way is to give the certificate (icyrock.com-cert.jks or icyrock.com.crt) to your users, which can then import it and the browser will automatically do the verification for them. You can close these two and click Run on the original dialog – the app will launch.

Be Sociable, Share!

Leave a Reply


six + 9 =

Next round of 4clojure.com solutions

After the first round, ten more 4clojure.com solutions:

(ns com.icyrock.clojure.a4clojure_com.core
  (:use clojure.test))

;;; Problem 30 - Compress a Sequence
(deftest test-problem-30
  (let [pins
        [(fn [xs] (reduce #(if (= (peek %1) %2) %1 (conj %1 %2)) [] xs))
         #(map first (partition-by identity %))]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (apply str (pin "Leeeeeerrroyyy")) "Leroy"))
      (is (= (pin [1 1 2 3 3 2 2 3]) '(1 2 3 2 3)))
      (is (= (pin [[1 2] [1 2] [3 4] [1 2]]) '([1 2] [3 4] [1 2])))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 29 - Get the Caps
(deftest test-problem-29
  (let [pins
        [(fn [s] (apply str (filter #(Character/isUpperCase %) s)))
         #(apply str (re-seq #"[A-Z]" %))
         (fn [s] (-> (re-seq #"[A-Z]" s) (#(apply str %))))]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin "HeLlO, WoRlD!") "HLOWRD"))
      (is (empty? (pin "nothing")))
      (is (= (pin "$#A(*&987Zf") "AZ"))
      (is (= true true))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 28 - Flatten a Sequence
(deftest test-problem-28
  (let [pins
        [(fn ftn [coll]
           (loop [xs coll ys []]
             (if (seq xs)
               (let [x (first xs)
                     nys (if (coll? x) (into ys (ftn x)) (conj ys x))]
                 (recur (rest xs) nys))
               ys)))
         (fn ftn [xs]
           (if (coll? xs)
             (mapcat ftn xs)
             [xs]))]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin '((1 2) 3 [4 [5 6]])) '(1 2 3 4 5 6)))
      (is (= (pin ["a" ["b"] "c"]) '("a" "b" "c")))
      (is (= (pin '((((:a))))) '(:a)))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 27 - Palindrome Detector
(deftest test-problem-27
  (let [pins
        [#(= (seq %) (reverse %))]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (false? (pin '(1 2 3 4 5))))
      (is (true? (pin "racecar")))
      (is (true? (pin [:foo :bar :foo])))
      (is (true? (pin '(1 1 3 3 1 1))))
      (is (false? (pin '(:a :b :c))))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 26 - Fibonacci Sequence
(deftest test-problem-26
  (let [pins
        [(fn [n]
           (loop [acc [1 1] cnt (count acc)]
             (if (= n cnt)
               (reverse acc)
               (recur (cons (apply + (take 2 acc)) acc) (inc cnt)))))
         (fn [n]
           (take n
                 (map first (iterate (fn [[a b]] [b (+ a b)]) [1 1]))))
         (fn [n]
           (take n
                 ((fn step [a b]
                    (lazy-seq (cons a (step b (+ a b)))))
                  1 1)))]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin 8) '(1 1 2 3 5 8 13 21)))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 25 - Find the odd numbers
(deftest test-problem-25
  (let [pins
        [(partial filter odd?)]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin #{1 2 3 4 5}) '(1 3 5)))
      (is (= (pin [4 2 1 6]) '(1)))
      (is (= (pin [2 2 4 6]) '()))
      (is (= (pin [1 1 1 3]) '(1 1 1 3)))
      (is (= true true))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 24 - Sum It All Up
(deftest test-problem-24
  (let [pins
        [#(reduce + %)]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin '(1 10 3)) 14))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 23 - Reverse a Sequence
(deftest test-problem-23
  (let [pins
        [#(reduce (fn [out in] (cons in out)) [] %)]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin [1 2 3 4 5]) [5 4 3 2 1]))
      (is (= (pin (sorted-set 5 7 2 7)) '(7 5 2)))
      (is (= (pin [[1 2][3 4][5 6]]) [[5 6][3 4][1 2]]))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 22 - Count a Sequence
(deftest test-problem-22
  (let [pins
        [#(loop [cnt 0 acc %]
            (if (empty? acc)
              cnt
              (recur (inc cnt) (rest acc))))
         #(reduce (fn [cnt _] (inc cnt)) 0 %)]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin '(1 2 3 3 1)) 5))
      (is (= (pin "Hello World") 11))
      (is (= (pin [[1 2] [3 4] [5 6]]) 3))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 21 - Nth Element
(deftest test-problem-21
  (let [pins
        [#(first (drop %2 %1))
         #((vec %) %2)]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin '(4 5 6 7) 2) 6))
      (is (= (pin [:a :b :c] 0) :a))
      (is (= (pin [1 2 3 4] 1) 2))
      (is (= (pin '([1 2] [3 4] [5 6]) 2) [5 6]))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

(run-tests)
Be Sociable, Share!

Leave a Reply


8 − one =

Testing Ajax on jsFiddle with jQuery

jsFiddle is a wonderful site. Just got to love the idea and the implementation. It has so much that’s invaluable for quick testing. Recently, I discovered that you can actually test Ajax calls on jsFiddle. They call it “Echo” requests, as they do exactly that – they echo back whatever you send. You can read more about this in jsFiddle documentation.

What’s nice about this is that you write both the client and the “server” – it’s really strong to call echo server a server, but I digress – on the same page, you see the whole code here and it’s great for quick tests for situations such as “why is this 20+-page web site not working after adding these 5 lines of straightforward code?” which usually end in banging the head against the wall and then realizing that you misplaced a comma, added one parentheses too many, mispelled that 15-character identifier or something frustrating along these lines. So you fire a browser (that is, a new tab, as you always have your browser open, don’t you), write a quick jsFiddle and you get the “doh!” moment relatively quickly.

Here’s how one could write a simple Fibonnaci number “generator” that uses jsFiddle’s echo:

  • HTML
<button id="next-fib">Next Fibonnaci</button>
<ul id="fibs">
<li/>1
<li/>1
</ul>

We just created a button and a list of fibs. The button will launch an Ajax call when clicked, see Javascript below.

  • CSS
#fibs {
  list-style-type: none; 
}
#fibs li {
  float: left;
  padding-left: 10px;
}

Some basic styling for nicer number placement.

  • Javascript:
function make_next_fib() {
  var all_lis = $('#fibs > li');
  var last_two_lis = all_lis.slice(all_lis.length - 2);
  var last_two_fibs = last_two_lis.map(function() {
    return parseInt($(this).text());
  });

  var sum = last_two_fibs[0] + last_two_fibs[1];
  var json = $.toJSON({ next_fib: sum });
    
  $.ajax({
    url: '/echo/json/',
    dataType: 'json',
    type: 'POST',
    data: {
      json: json,
    },
    success: function(data) {
      var li = $('<li>').text(data.next_fib);
      $('#fibs').append(li);
    },
  });
}

$('#next-fib').click(function() {
  make_next_fib();
});

Javascript is pretty self-explanatory. make_next_fib is the meat. It will:

  • Find all li elements within parent fibs
  • Select the last two (as Fibonnaci numbers are made by summing the last two)
  • Map the li into actual numbers
  • Get the sum of these numbers – this is what we are going to send via jsFiddle echo service
  • Make the Ajax call
  • In success, just append a new li to #fibs

A few things about the Ajax call itself:

  • The call has to target a specific URL made available by jsFiddle. These are provided in the documentation. We use json one, so the url is /echo/json/
  • It needs to be a POST to send the data and we set dataType to json, as we are sending and receiving the json
  • The json is actually sent as a string. In order to convert, a plugin can be used – such as jquery-json. This needs to be added to your jsFiddle. Use Add Resources (which changes to Manage Resources when you add some) on the left to point to the minified version (currently version 2.3)

The click event handler at the end is there to enable all this to happen when the button is clicked.

That’s it! Here’s a working jsFiddle:

After clicking a few times, you should see something like this in the result part:

Be Sociable, Share!

Leave a Reply


six + 6 =

Clojure and Seesaw

There’s a very nice desktop graphics library called Seesaw for Clojure. You can see a lot of examples of how to use it inside seesaw/test/examples folder of the distribution or you can browse online at GitHub.

I played with it – here’s a small LED-matrix digital clock implementation. On Ubuntu 11.10 it looks like this:

Here’s the code:

(ns com.icyrock.clojure.seesaw.led-matrix
  (:import [ java.util Calendar])
  (:use seesaw.core
        seesaw.graphics))

(def lcd-dot-style-off
  (style
   :background "#181818"
   :stroke (stroke :width 3)))

(def lcd-dot-style-on
  (style
   :background "#00bc00"
   :stroke (stroke :width 3)))

(def lcd-dot-styles
  {false lcd-dot-style-off
   true lcd-dot-style-on})

(defn draw-lcd-dot [g width height is-on]
  (let [dot-style (lcd-dot-styles is-on)
        border (-> dot-style :stroke .getLineWidth)
        border2 (* 2 border)]
    (draw g
          (ellipse border border (- width border2) (- height border2)) dot-style)))

(def lcd-symbol-dots
  {\0
   [".***."
    "*...*"
    "*...*"
    "*...*"
    "*...*"
    "*...*"
    ".***."]
   \1
   ["..*.."
    ".**.."
    "..*.."
    "..*.."
    "..*.."
    "..*.."
    "*****"]
   \2
   [".***."
    "*...*"
    "....*"
    "...*."
    "..*.."
    ".*..."
    "*****"]
   \3
   [".***."
    "*...*"
    "....*"
    "..**."
    "....*"
    "*...*"
    ".***."]
   \4
   ["...*."
    "..**."
    ".*.*."
    "*..*."
    "*****"
    "...*."
    "...*."]
   \5
   ["*****"
    "*...."
    "****."
    "....*"
    "....*"
    "*...*"
    ".***."]
   \6
   [".***."
    "*...*"
    "*...."
    "****."
    "*...*"
    "*...*"
    ".***."]
   \7
   ["*****"
    "....*"
    "...*."
    "..*.."
    "..*.."
    "..*.."
    "..*.."]
   \8
   [".***."
    "*...*"
    "*...*"
    ".***."
    "*...*"
    "*...*"
    ".***."]
   \9
   [".***."
    "*...*"
    "*...*"
    ".****"
    "....*"
    "*...*"
    ".***."]
   \:
   ["....."
    "....."
    "..*.."
    "....."
    "..*.."
    "....."
    "....."]
   })

(defn draw-lcd-symbol [g width height symbol]
  (let [dots (lcd-symbol-dots symbol)
        dot-width (/ width (count (first dots)))
        dot-height (/ height (count dots))]
    (doseq [row dots]
      (doseq [cell row]
        (draw-lcd-dot g dot-width dot-height (= cell \*))
        (translate g dot-width 0))
      (translate g (- width) dot-height))))

(defn get-time-string []
  (let [ c (Calendar/getInstance)
        h (.get c Calendar/HOUR_OF_DAY)
        m (.get c Calendar/MINUTE)
        s (.get c Calendar/SECOND)]
    (format "%02d:%02d:%02d" h m s)))

(defn paint-lcd-symbol [ c g]
  (try
    (let [symbols (get-time-string)
          symbol-count (count symbols)
          width (.getWidth c)
          height (.getHeight c)
          symbol-width (/ width symbol-count)]
      (doseq [symbol symbols]
        (push g
              (draw-lcd-symbol g (- symbol-width 20) height symbol))
        (translate g symbol-width 0)))
    (catch Exception e
      (println e))))

(defn content-panel []
  (border-panel
   :center (canvas :id :clock
                   :background "#000000"
                   :paint paint-lcd-symbol)))

(defn make-frame []
  (let [f (frame :title "com.icyrock.clojure.seesaw.led-matrix"
                 :width 1200 :height 300
                 :on-close :dispose
                 :visible? true
                 :content (content-panel))]
    (.setLocation f (java.awt.Point. 100 300))
    (timer (fn [e] (repaint! (select f [:#clock])) 1000))))

(defn -main [& args]
  (native!)
  (make-frame))
(-main)

You can find the code at com.icyrock.clojure GitHub repository – just clone and fire up in your favorite IDE.

Be Sociable, Share!

One Response to “Clojure and Seesaw”

Gnome seesaw | Oxuyentt on May 3rd, 2012 12:57:

[...] Clojure and Seesaw « Blog Archive « icyrock.comJan 25, 2012 … You can see a lot of examples of how to use it inside seesaw/test/examples folder of the distribution or you can browse online at GitHub. [...]


Leave a Reply


8 − four =

Working through 4clojure.com problems

4clojure.com is a site that has fill-in type of problems for exercising your Clojure skills. It’s very easy to use and has a built-in unit-test kind of solution checker, including a syntax-highlighting editor. Very simple and neat, like it.

I’ve been working through these, here are solutions to the first 20 basic problems:

(ns com.icyrock.clojure.a4clojure_com.core
  (:use clojure.test))

;;; Problem 20 - Penultimate Element
(deftest test-problem-20
  (let [pins
        [(comp second reverse)]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin (list 1 2 3 4 5)) 4))
      (is (= (pin ["a" "b" "c"]) "b"))
      (is (= (pin [[1 2] [3 4]]) [1 2]))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 19 - Last Element
(deftest test-problem-19
  (let [pins
        [#(nth % (- (count %1) 1))
         (comp first reverse)
         (comp peek vec)
         (fn [x] (reduce #(identity %2) x))]]
    (loop [pin (first pins) rest-pins (rest pins)]
      (is (= (pin [1 2 3 4 5]) 5))
      (is (= (pin ["b" "c" "d"]) "d"))
      (if (not (empty? rest-pins))
        (recur (first rest-pins) (rest rest-pins))))))

;;; Problem 18 - Sequences: filter
(deftest test-problem-18
  (is (= [6 7] (filter #(> % 5) '(3 4 5 6 7)))))

;;; Problem 17 - Sequences: map
(deftest test-problem-17
  (is (= '(6 7 8) (map #(+ % 5) '(1 2 3)))))

;;; Problem 16 - Hello World
(deftest test-problem-16
  (is (= (#(str "Hello, " % "!") "Dave") "Hello, Dave!")))

;;; Problem 15 - Double Down
(deftest test-problem-15
  (is (= (#(* 2 %) 11) 22))
  (is (= ((partial * 2) 11) 22)))

;;; Problem 14 - Intro to Functions
(deftest test-problem-14
  (is (= 8 ((fn add-five [x] (+ x 5)) 3))))

;;; Problem 13 - Sequences: rest
(deftest test-problem-13
  (is (= [20 30 40] (rest [10 20 30 40]))))

;;; Problem 12 - Intro to Sequences
(deftest test-problem-12
  (is (= 3 (first '(3 2 1)))))

;;; Problem 11 - Maps: conj
(deftest test-problem-11
  (is (= {:a 1, :b 2, :c 3} (conj {:a 1} [:b 2] [:c 3]))))

;;; Problem 10 - Intro to Maps
(deftest test-problem-10
  (is (= 20 ((hash-map :a 10, :b 20, :c 30) :b))))

;;; Problem 9 - Sets: conj
(deftest test-problem-9
  (is (= #{1 2 3 4} (conj #{1 4 3} 2))))

;;; Problem 8 - Intro to Sets
(deftest test-problem-8
  (is (= #{:a :b :c :d} (set '(:a :a :b :c :c :c :c :d :d)))))

;;; Problem 7 - Vectors: conj
(deftest test-problem-7
  (is (= [1 2 3 4] (conj [1 2 3] 4))))

;;; Problem 6 - Intro to Vectors
(deftest test-problem-6
  (is (= [:a :b :c] (list :a :b :c) (vec '(:a :b :c)) (vector :a :b :c))))

;;; Problem 5 - Lists: conj
(deftest test-problem-5
  (is (= '(1 2 3 4) (conj '(2 3 4) 1))))

;;; Problem 4 - Intro to Lists
(deftest test-problem-4
  (is (= (list :a :b :c) '(:a :b :c))))

;;; Problem 3 - Intro to Strings
(deftest test-problem-3
  (is (= "HELLO WORLD" (.toUpperCase "hello world"))))

;;; Problem 2 - Simple Math
(deftest test-problem-2
  (is (= (- 10 (* 2 3)) 4)))

;;; Problem 1 - Nothing but the Truth 
(deftest test-problem-1
  (is (= true true)))

(run-tests)

Note that in problems 19 and 20 I added support for multiple solutions. I found out this will be important going forward when problems get harder, so that I can track what I’ve done and save this for later. I think it will be useful for looking at the mistakes I made and make sure I don’t do them again.

Be Sociable, Share!

One Response to “Working through 4clojure.com problems”

Next round of 4clojure.com solutions « Blog Archive « icyrock.com on March 21st, 2012 19:35:

[...] the first round, ten more 4clojure.com [...]


Leave a Reply


3 + = eleven

Basic French samples

I’ve been learning French lately and in the course of learning I often found a need to find some pages on the Internet that were related to what I’ve just learned and at the same time simple enough so I can actually understand majority of the page. Unfortunately, most of the pages are of two kinds:

  • Endless repetition of things such as all conjugations of -er verbs, colors, numbers, etc. I find this rather useless, as I’m never going to be asked to recite all the colors in order – I need these words put in context
  • Texts that are rather advanced or offer no translation to English, either being equally useless, as the point is to actually confirm whatever I’ve learned is correct.

I’ve compiled some things that I’ve run into when learning and that were useful to me – hope you’ll find them useful in your learning.

Note: Please understand that I’m still learning, so I don’t vouch for the correctness of the below. Please make sure you confirm that these are correct, as learning mistakes is something that can be hard to unlearn after.

EnglishFrench
We have two beige beds in the room.Nous avons deux lits beiges dans la pièce.
Are you buying a red or green lamp?Est-ce que tu achètes une lampe rouge ou verte?
You are happy people.Vous êtes des gens heureux.
There are twenty two players on the field.Il y a vingt-deux joueurs sur le terrain.
She has six chairs around the table. Elle a six chaise autour de la table.
I am in Springfield now.Je suis à Springfield maintenant.
Are they still cooking?Est-ce qu’elles cuisent encore?
Where are you going to eat?Où est-ce que tu vas déjeuner?
A very good restaurant is near here.Un très bon restaurant est près d’ici.
He hopes she is there.Il espère qu’elle est là.
At what time is your boyfriend going to arrive?À quelle heure est ton petit ami va arriver?
Are they warm people?Est-ce qu’ils sont chaleureux?
Are we late?Est-ce que nous sommes en retard?
Where is your wife?Où est ta femme?
When is her husband finishing?Quand est-ce que son mari finit?
They don’t know where we are.Ils ne savent pas où nous sommes.
Be Sociable, Share!

2 Responses to “Basic French samples”

Pete BD on January 14th, 2012 02:39:

have you tried google translate? browse to a website and have translate it into french. when you hover over a sentence it will show you the English Pete


icyrock.com on January 16th, 2012 19:54:

Sure, use Google Translate all the time. However, I noticed a lot of times it doesn’t translate correctly – not that you would not understand the translation, but if you are learning a new language I think it’s very important to learn correctly and not remember it wrong or be confused about how to use some language constructs.


Leave a Reply


6 − = one

Sample CouchDB curl session

CouchDB has a lot of documentation in its wiki. A good overview of how to access it directly through HTTP is at this wiki page. When starting learning CouchDB I found it quite succinct. This is great after you are “in business” and just need to confirm something or refresh your memory, but I think it’s hard for the first couple of sessions.

The below sample is a sample interaction that shows:

  • Using pure curl access – the basics
  • The following operations:
    • Creating a database
    • Deleting a database
    • Creating documents
    • Creating design documents
    • Which implies updating documents, as design documents are nothing but regular documents with a special name
    • Creating views
    • Updating views
    • Getting view results
  • Python curl is used for simplicity – this could have been done through command line curl or using some other programming language
  • Actual HTTP calls if enabled via
    • conn.set_debuglevel(1) setting (see code)
  • Most importantly – how much easier is to use some of the libraries (such as these) that wrap all this in simpler calls, after you grasp the basics

Some important notes:

  • Be sure to change the parameters at the start of the script if they are different
  • Important: this is destructive – it will ruin the given database, so be careful
  • For the above reason, I put the ‘raise Exception’ statement – comment it out to run the code

Here’s the code:

#!/usr/bin/python

# License: MIT (http://www.opensource.org/licenses/mit-license.php)
#
# Copyright (c) 2011. icyrock.com
# 
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# --------------------------------------------
# Sample plain HTTP Python <-> CouchDB session
# --------------------------------------------
#
# This sample python script is a small curl-based interaction with
# CouchDB. When you run it, you should be able to see what you can
# actually do using low-level curl interface that CouchDB supports. If
# you uncomment the following line:
#
# #conn.set_debuglevel(1)
#
# you will see pure HTTP calls that are actually being made. Looking at
# these you should get a better understanding about how CouchDB works
# if you take out all the libraries that you might use and which cover
# the details of what actually happens beneath.
#
# Of course, after you have this all figured out, go and use the
# library - it's easier that way. Take a look at this page for more
# info: http://wiki.apache.org/couchdb/Getting_started_with_Python

import httplib
import urllib2
import json
from textwrap import dedent

# Some defaults, change to match your system
# !!! Note the database dbname will be destroyed !!!
base_url = 'localhost:5984'
dbname = 'robocouch'
docprefix = 'rcd'
raise Exception('Comment out after confirming the above parameters')

# Some helpers
class PropertyDict(dict):
  def __init__(self, **items):
    self.__dict__.update(items)
    for k in items:
      self[k] = items[k]

conn = httplib.HTTPConnection(base_url)
# If you want to see the actual HTTP calls made by httplib, uncomment the below
# Very useful to grasp the low level stuff
#conn.set_debuglevel(1)

def couchdb_http_action(method, url, body=None):
  if body != None:
    json_send = json.dumps(body)
    headers = {'Content-Type': 'application/json'}
    conn.request(method, '/%s' % url, json_send, headers)
  else:
    conn.request(method, '/%s' % url)
  resp = conn.getresponse()
  raw = resp.read()
  info = PropertyDict(
    status=resp.status,
    raw=raw,
    data=json.loads(raw),
  )
  return info

# Helper methods - these basically do a HTTP call as given the name 
# For example, hget = HTTP GET
def hget(url):
  return couchdb_http_action('GET', url)

def hpost(url, data):
  return couchdb_http_action('POST', url, data)

def hdelete(url):
  return couchdb_http_action('DELETE', url)

def hput(url, data=None):
  return couchdb_http_action('PUT', url, data)

def pretty(data):
#  return json.dumps(data, sort_keys=True, indent=2)
  return json.dumps(data, indent=2)

#
# Let's get to work 
#
print('Welcome!')

# Create our database, delete if currently existing
print('Our database: %s' % dbname)
alldbs = hget('_all_dbs').data
print('All databases: %s' % alldbs)

if dbname in alldbs:
  print('Our database %s is present, deleting' % dbname)
  hdelete(dbname)
  print('Deleted %s, all databases: %s' % (dbname, hget('_all_dbs').data))

hput(dbname)
print('Created database %s, all databases: %s' % (dbname, hget('_all_dbs').data))

# Create some documents
for i in range(1, 6):
  docid = '%s%d' % (docprefix, i) 
  print('Creating document %s' % docid)
  hput('%s/%s' % (dbname, docid), dict(number=i*33))
#print('All document IDs in %s: %s' % (dbname, [row['id'] for row in hget('%s/_all_docs' %dbname).data['rows']]))
print('All document IDs in %s: %s' % (dbname, hget('%s/_all_docs' %dbname).raw))

# Create a design document with the same name as our database (defined by dbname variable)
# It will contain a single view called 'all_numbers'
# This view will have only a map part and will return all documents with 'number' attribute
hput('%s/_design/%s' % (dbname, dbname), dict(
    language='javascript',
    views=dict(
      all_numbers=dict(
        map=dedent('''
               function(doc) {
                 if(doc.number) {
                   emit(null, doc)
                 }
               }
            ''')
      ),
    )
))

# Actually run the view
print("All documents with 'number' attribute:")
#print(pretty(hget('%s/_design/%s/_view/all_numbers' % (dbname, dbname)).data))
print(hget('%s/_design/%s/_view/all_numbers' % (dbname, dbname)).raw)
 
# Update the design document by adding another view called count_sum_numbers
# Since this is an update, we need to fetch to get the current revision of the document
# The revision is part of our JSON outbound request
# This one will sum all documents which have 'number' attribute and give us their count
# Note that the design document is a whole, thus our previous view needs to be preserved
rev = hget('%s/_design/%s' % (dbname, dbname)).data['_rev']
print('Updating revision %s' % rev)
hput('%s/_design/%s' % (dbname, dbname), dict(
    _rev=rev,
    language='javascript',
    views=dict(
      all_numbers=dict(
        map='function(doc) { if(doc.number) emit(null, doc) }',
      ),
      count_sum_numbers=dict(
        map='function(doc) { if(doc.number) emit(null, doc.number) }',
        reduce=dedent('''
                  function(key, values, rereduce) {
                    var sum = 0
                    var count = 0
                    for(var i = 0; i < values.length; i++) {
                      sum += values[i]
                      count += rereduce ? values.count : 1
                    }
                    return {count:count, sum:sum}
                  }
               ''')
      ),
    )
))

# Get the sum of all numbers
# Rows contains one row per key. Since we had only one (null) key, we have only one row
res = hget('%s/_design/%s/_view/count_sum_numbers' % (dbname, dbname)).data['rows'][0]['value']
print('Sum of all %d available numbers: %d' % (res['count'], res['sum']))
Be Sociable, Share!

Leave a Reply


6 + six =