Feeds:
Posts
Comments

Archive for the ‘Web Development’ Category

Until recently, Python and Ruby were ahead of Perl when it came to developing web applications. Now, thanks to the work of the plackists, we’re right back up there again. By the time I could have become proficient Python + WSGI, Plack and PSGI will be equally good, or better.

So, yeah, when it comes to libraries, we are the champions. Okay, there might be a couple of utilities like Rake and Capistrano that haven’t been duplicated yet, but I’m sure someone is working on it1.

However, there is a reason why you might want to use Python or Ruby ahead of Perl. And that is Google App Engine and heroku. If you have an idea for a web app, you can get started for $0. I know about perl-appengine but it seems to be moribund.

With Perl, if shared hosting is enough for you, a half-decent plan costs $6/month which is $72/year. More likely, you would want a VPS so you can install Plack and Starman which will cost closer to $20 – $25 / month or $240 – $300 / year.

Okay, so it isn’t a reason why I personally would use Python or Ruby over Perl – my free time is somewhat valuable to me. But if you’re getting started and you’re not sure which of the big three scripting languages to choose, it is a strike against Perl.


1. And if they’re not, I don’t need ‘em anyway

Read Full Post »

Okay, so down to business. One thing I like about straight cgi vs mod_perl is that any changes are immediately reflected on page refresh. And plackup (and twiggy) both offer an option to auto-restart when files change. Sounds good.

jared@win32 $ plackup -h
...
-r, --reload
        Make plackup to watch updates from your development directory and
        restarts the server whenever a file is updated. This option by
        default watches the "lib" directory and the base directory where
        *.psgi* file is located. Use "-R" if you want to watch other
        directories.
...
jared@win32 $ twiggy -r --listen :8080 hello.psgi
Watching ./lib hello.psgi for file updates.
./lib: No such file or directory at c:/strawberry/perl/site/lib/Filesys/Notify/Simple.pm line 156
Terminating on signal SIGINT(2)

Hmmm... I didn't ask it to watch ./lib (and it doesn't acknowledge changes to hello.psgi). Let me change the watched files with -R.

jared@win32 $ plackup -r -R hello.psgi --listen :8080 hello.psgi
HTTP::Server::PSGI: Accepting connections at http://0:8080/
Watching hello.psgi ./lib hello.psgi for file updates.
./lib: No such file or directory at c:/strawberry/perl/site/lib/Filesys/Notify/Simple.pm line 156
Terminating on signal SIGINT(2)

Er, okay, -R only allows you to add paths. So there is not an obvious way of removing the paths that already exist. *sigh*. I submit to the inevitable.

jared@win32 $ mkdir lib
jared@win32 $ twiggy -r --listen :8080 hello.psgi
Watching ./lib hello.psgi for file updates.

So I make a change to hello.psgi and get the following:

-- C:\home\jared\plack-tests\hello.psgi updated.
Killing the existing server (pid:-2872)

Almost! But I'm missing the message that says it was able to restart the server.

waitpid($pid, 0);
warn "Successfully killed! Restarting the new server process.\n";

In actual fact, the process it claims it has killed is still running and I'm still able to connect to it. So I hack Restarter.pm a bit (more on that at the end).

jared@win32 $ twiggy -r --listen :8080 hello.psgi
Watching ./lib hello.psgi for file updates.
-- C:\home\jared\plack-tests\hello.psgi updated.
Killing the existing server (pid:-2156)
Successfully killed! Restarting the new server process.
bind: Unknown error at c:/strawberry/perl/site/lib/Twiggy/Server.pm line 71
    -L, --loader

Using plackup instead of twiggy works.

jared@win32 $ plackup -r --listen :8080 hello.psgi
HTTP::Server::PSGI: Accepting connections at http://0:8080/
Watching ./lib hello.psgi for file updates.
-- C:\home\jared\plack-tests\hello.psgi updated.
Killing the existing server (pid:-3544)
Successfully killed! Restarting the new server process.
HTTP::Server::PSGI: Accepting connections at http://0:8080/

Although having thought about it, maybe the Shotgun loader is what I really want.

jared@win32 $ twiggy --listen :8080 hello.psgi -L Shotgun
Attempt to free unreferenced scalar: SV 0x2c78b04,
Perl interpreter: 0x2400054 at
c:/strawberry/perl/site/lib/Plack/Loader/Shotgun.pm line 48.

Again, plackup works here where twiggy does not.

jared@win32 $ plackup --listen :8080 hello.psgi -L Shotgun
HTTP::Server::PSGI: Accepting connections at http://0:8080/
127.0.0.1 - - [22/Mar/2010 21:21:31] "GET / HTTP/1.1" 200 42 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2) Gecko/20100115 Firefox/3.6 (.NET CLR 3.5.30729)"
127.0.0.1 - - [22/Mar/2010 21:21:35] "GET /favicon.ico HTTP/1.1" 200 42 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2) Gecko/20100115 Firefox/3.6 (.NET CLR 3.5.30729)"
127.0.0.1 - - [22/Mar/2010 21:22:07] "GET / HTTP/1.1" 200 42 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2) Gecko/20100115 Firefox/3.6 (.NET CLR 3.5.30729)"

# --

And at the risk of getting pointed comments for using the KILL signal, here is the hack I made to Restarter.pm. (Hey, what can I do? My OS blows.)

--- Plack.orig/Loader/Restarter.pm      2010-03-22 20:50:16 +0000
+++ Plack/Loader/Restarter.pm   2010-03-22 21:31:06 +0000
@@ -37,6 +37,12 @@
     my $pid = $self->{pid} or return;
     warn "Killing the existing server (pid:$pid)\n";
     kill 'TERM' => $pid;
+
+    if (lc($^O) =~ /mswin32/) {
+        sleep 1;
+        kill 'KILL' => $pid;
+    }
+
     waitpid($pid, 0);
     warn "Successfully killed! Restarting the new server process.\n";
 }

Read Full Post »

Now I have my command prompt set up the way I like it, installing modules into strawberry perl from from cpan is easy. Okay, fine, it was easy before, but now I just need to run my batch file and type cpan.

Okay, so one of the things I’m interested in is running Plack on Windows. And Twiggy looks like the obvious choice for HTTP servers supporting PSGI – the benchmarks I looked at state it is the second most performant pure(ish)-perl option after Starman and Starman clearly states it isn’t supported on Windows. Does the fact that Twiggy doesn’t say that mean that it works?

c:\home\jared>cpan

cpan shell -- CPAN exploration and modules installation (v1.9452)
Enter 'h' for help.


cpan> install Plack::Handler::Twiggy

...

t/02_signals.t .......... skipped: Broken perl detected,
skipping tests.
t/03_child.t ............ skipped: Your perl interpreter
is badly BROKEN. Child watchers will not work, ever. Try
upgrading to a newer perl or a working perl (cygwin's perl
is known to work). If that is not an option, you should be
able to use the remaining functionality of AnyEvent, but
child watchers WILL NOT WORK.

...

Yikes! This was emitted when building AnyEvent (a pretty key part of Twiggy). It doesn't sound good does it? It comes from the following test:

BEGIN {
   # check for broken perls
   if ($^O =~ /mswin32/i) {
      my $ok;
      local $SIG{CHLD} = sub { $ok = 1 };
      kill 'CHLD', 0;

      unless ($ok) {
         print <<EOF;
1..0 # SKIP Your perl interpreter is badly BROKEN. Child watchers will not work, ever. Try upgrading to a newer perl or a working perl (cygwin's perl is known to work). If that is not an option, you should be able to use the remaining functionality of AnyEvent, but child watchers WILL NOT WORK.
EOF
         exit 0;
      }
   }
}

Okay, so child watchers don't work (whatever they are). Hopefully Twiggy doesn't need 'em. Let's plough on regardless. This is the standard Hello World app.

my $app = sub {
    my $env = shift;
    return [
        200,
        ['Content-Type' => 'text/plain'],
        [ "Hello stranger from $env->{REMOTE_ADDR}!"],
    ];
};

And firing up twiggy works, at least for this simple example.

jared@win32 $ twiggy --listen :8080 hello.psgi

Read Full Post »

There are a number of different methods for passing data between pages. These include:

1. Passing the data in a query string.
2. Storing the data in a hidden field and using Javascript to respond to onClick on the links.
3. Storing the data on the server and retrieving it using a session id.

The first option can be very wasteful if we have a large table with many columns. We would have to replicate the data for each column.

The second option seems reasonable but excludes people who disable Javascript so I favour using sessions.

I’m not aware of a PLT scheme library that handles sessions for CGI but it is easy enough to create a very basic example. For our purposes, we will store the sessions in the temporary directory and not have any type of session expiry.

PLT scheme has a nice library called serialize.ss which replaces a lot of the code I discussed earlier. We define a session as a serializable struct which contains the session id, the filename where the data lives and the data itself.

(define-serializable-struct session (id filename data))

The data in a session is simply key value pairs so we provide functions to store and retrieve the data and check if it exists. This implementation obviously assumes that data is a hash table.

(define (session-data-put! session key value)
  (hash-table-put! (session-data session) key value))

(define (session-data-get session key)
  (hash-table-get (session-data session) key (lambda () #f)))

(define (session-data-exists? session key)
  (with-handlers ((exn:fail:contract? (lambda (exn) #f)))
    (hash-table-get (session-data session) key)
    #t))

We also need some way of storing and retrieving sessions on the filesystem. Loading the session checks if one exists for a given session ID. If not, it uses make-temporary-file.

(define *temp-file-prefix* "cgisession-")
(define *temp-file-format-string*
  (string-append *temp-file-prefix* "~a"))

(define (filename->session-id filename)
  (cadr (regexp-match (string-append *temp-file-prefix*
                                     "([-0-9]+)$")
                      (path->string filename))))

(define (load-session session-id)
  (let ((tmp-filename (build-path (find-system-path 'temp-dir)
                                  (string-append *temp-file-prefix*
                                                 session-id))))
    (if (file-exists? tmp-filename)
        (call-with-input-file tmp-filename
          (lambda (port) (deserialize (read port))))
        (let ((filename (make-temporary-file *temp-file-format-string*)))
          (make-session (filename->session-id filename)
                        filename
                        (make-hash-table 'equal))))))

This is less robust than it could be, e.g. if the temporary file exists and does not contain something that we can deserialize then it will fall over. Fixing that is left as an exercise.

Saving the session is trivial.

(define (save-session session)
  (let ((port (open-output-file (session-filename session) 'truncate)))
    (print (serialize session) port)
    (close-output-port port)))

Finally, here is the full code listing including a few tests.

(require (lib "file.ss"))
(require (lib "serialize.ss"))

(define *temp-file-prefix* "cgisession-")
(define *temp-file-format-string*
  (string-append *temp-file-prefix* "~a"))

(define-serializable-struct session (id filename data))

(define (filename->session-id filename)
  (cadr (regexp-match (string-append *temp-file-prefix*
                                     "([-0-9]+)$")
                      (path->string filename))))

(define (load-session session-id)
  (let ((tmp-filename (build-path (find-system-path 'temp-dir)
                                  (string-append *temp-file-prefix*
                                                 session-id))))
    (if (file-exists? tmp-filename)
        (call-with-input-file tmp-filename
          (lambda (port) (deserialize (read port))))
        (let ((filename (make-temporary-file *temp-file-format-string*)))
          (make-session (filename->session-id filename)
                        filename
                        (make-hash-table 'equal))))))

(define (save-session session)
  (let ((port (open-output-file (session-filename session) 'truncate)))
    (print (serialize session) port)
    (close-output-port port)))

(define (session-data-put! session key value)
  (hash-table-put! (session-data session) key value))

(define (session-data-get session key)
  (hash-table-get (session-data session) key (lambda () #f)))

(define (session-data-exists? session key)
  (with-handlers ((exn:fail:contract? (lambda (exn) #f)))
    (hash-table-get (session-data session) key)
    #t))

;;; --- Test code --- ;;;

(define *session-id* "1234")
(define s (load-session *session-id*))
(set! *session-id* (session-id s))

(session-data-put! s 'k 'v)
(session-data-get s 'k)
(session-data-exists? s 'k1)

(save-session s)

Read Full Post »

This time we said that we would talk about extracting the parameters from the query string and rendering the table. The string (srfi-13) and character set (srfi-14) SRFIs make it very easy to parse the query string.

(string-tokenize *query-string* (char-set-complement (char-set #\= #\&)))

We are not quite ready to talk about passing data from one page to another so we won't discuss this any further until next time. How about rendering the table? Concatenating strings is messy and error-prone. We need some helper functions. e.g. let's say there was a function tag that took a tag name, some attributes and content and formatted it into HTML. How would we want that to look? Maybe something like this?

(tag 'br) --> <br/>
(tag 'a '(href "www.google.com") "google") --> <a href="www.google.com">google</a>
(tag 'tr '() (tag 'td '() "hello")) --> "<tr><td>hello</td></tr>"

First of all, we need something to convert attributes into a string. Perhaps we might want multiple attributes or an attribute may want to concatenate a number of elements together:

e.g.

(list 'href "data=" data "&xyz=" xyz)
(define (attributes->string attributes)
  (if (pair? attributes)
      (let ((h (car attributes))
            (t (cdr attributes)))
        (if (pair? h)
            (string-join (map attributes->string attributes) " ")
            (string-append (symbol->string h)
                           "="
                           (qstr (string-concatenate t)))))
      ""))

A little helper function prefixes the attributes with a space if they result in a non-empty string.

(define (prefix-space s)
  (if (and (string? s)
           (> (string-length s) 0))
      (string-append " " s) ""))

We may not want to pass all of the parameters in so only the tag name is compulsory. Everything else is passed into args as a list. We then extract the appropriate variables parameters if there are any.

(define (tag name . args)
  (let* ((s-name (symbol->string name))
         (args-len (length args))
         (s-attributes (if (>= args-len 1)
                           (attributes->string (first args)) ""))
         (s-content (if (>= args-len 2)
                        (string-concatenate (cdr args)) ""))
         (content-length (string-length s-content)))
    (string-append "<" s-name
                   (prefix-space s-attributes)
                   (if (> content-length 0)
                       (string-append ">" s-content
                                      "</" s-name ">")
                       "/>"))))

srfi-13 provides some convenience functions for concatenating lists of strings and joining strings together.

I briefly thought about providing a nicer interface with two functions providing a tag interface without attributes and with attributes respectively and delegating to a similar function to the one above.

(define (tag name . args) ...)
(define (tag-attribs name . args) ...)

Alternatively the keyword library can be used to make a nicer interface:

(require (lib "kw.ss"))

(define/kw (tag/kw tag-name #:key attributes (content ""))
  (tag tag-name attributes content))

This would then be called like this:

(print-ln (tag/kw 'x #:attributes '(a "b")))
(print-ln (tag/kw 'td #:content "hi"))

Now that we have a tag function, we can use it to render a table. A basic table is straight-forward enough. We map a render row function across each table row, joining the result with string-join. The render row maps a function that surrounds each element with <td> ... </td> string-concatenates that, and surrounds that with <tr> ... </tr>. We want to alternate different classes to each row and closures are great for this. We provide a function that returns a closure that returns a different id each call.

(define (id-generator ids)
  (let ((i -1)
        (ids (list->vector ids))
        (len (length ids)))
    (lambda ()
      (set! i (+ i 1))
      (vector-ref ids (remainder i len)))))

We can the use this generator to pass a different id to the row renderer for each row in the table.

(define (render-table-row row . tr-class)
  (let ((tr-attribs (if (null? tr-class) '()
                        (list 'class (car tr-class)))))
    (tag 'tr tr-attribs
         (string-concatenate (map (lambda (e) (tag 'td '() e)) row)))))

(define (render-table table . ids)
  (let ((columns (table-columns table))
        (data (table-data table))
        (f-row (if (null? ids)
                   render-table-row
                   (let ((f (id-generator (map symbol->string (car ids)))))
                     (lambda (e)
                       (render-table-row e (f)))))))
    (tag 'table '() (string-join (map f-row data) "\n"))))

And here is a little test function:

(print-ln (render-table (make-table
                         '("a" "b" "c")
                         '(("1" "2" "3")
                           ("4" "5" "6")
                           ("7" "8" "9")))
                        '(id0 id1)))

I'm not really happy with the interface yet so maybe I'll look at fixing that next time along with passing data from one page to the next and sorting based on the columns.

Postscript: Danny Yoo mentioned an xml library I need to look into to see if it will replace (tag ...) and related functions. The uri-codec library replaces a lot of the code from the earlier article.

(define *encodings-list*
  '((%20 " ")
    (+a  "&")
    (+e  "=")
    (+p  "+")
    (+q  "\"")
    (+r  "\r\n")))

(define *hash* (make-hash-table 'equal))

(for-each (lambda (e)
            (let ((k (symbol->string (car e)))
                  (v (second e)))
              (hash-table-put! *hash* k v)
              (hash-table-put! *hash* v k)))
          *encodings-list*)

(define (encode-chars regex s)
  (pregexp-replace* regex s
                    (lambda (k)
                      (hash-table-get *hash* k))))

The data-encode and data-decode functions also become simpler as they now use the uri-encode and uri-decode functions.

(define (data-encode data)
  (let ((i (open-input-string data))
        (o (open-output-bytes)))
    (deflate i o)
    (let ((r (base64-encode (get-output-bytes o))))
      (uri-encode (bytes->string/utf-8
                   (subbytes r 0 (- (bytes-length r) 2)))))))

(define (data-decode data)
  (let ((i (open-input-bytes
            (base64-decode
             (string->bytes/utf-8
              (uri-decode data)))))
        (o (open-output-string)))
    (inflate i o)
    (get-output-string o)))

Read Full Post »

Last time we were looking at encoding data. HTML has a nice way of encoding its special characters, e.g. < and > are mapped to &lt; and &gt; We can do something similar. Important characters to encode are quote, equals, ampersand and maybe a few others. Then we have to decide on a character for encoding – I like plus (+) as it doesn’t look like a special character to my eyes. As we are not planning for the future, plus and a single character should do.

First of all list all the encodings.

(define *encodings-list*
  '((+s " ")
    (+a "&")
    (+e "=")
    (+p "+")
    (+q "\"")
    (+r "\r\n")))

Then store the encodings and the reverse encodings in a hash.

(define *hash* (make-hash-table 'equal))

(for-each (lambda (e)
            (let ((k (symbol->string (car e)))
                  (v (second e)))
              (hash-table-put! *hash* k v)
              (hash-table-put! *hash* v k)))
          *encodings-list*)

pregexp-replace* is perfect for encoding and decoding.

(define (encode-chars regex s)
  (pregexp-replace* regex s
                    (lambda (k)
                      (hash-table-get *hash* k))))

This would be called like

(encode-chars "[+ &\"]|\r\n" "<a string>")

I've decided to compress and base64 encode my data in case it becomes really big.

(define (data-encode data)
  (let ((i (open-input-string data))
        (o (open-output-bytes)))
    (deflate i o)
    (let ((r (base64-encode (get-output-bytes o))))
      (encode-chars "[+=]|\r\n"
                    (bytes->string/utf-8
                     (subbytes r 0 (- (bytes-length r) 2)))))))

We also need a decoder that performs the inverse operation.

(define (data-decode data)
  (let ((i (open-input-bytes
            (base64-decode
             (string->bytes/utf-8
              (encode-chars "\\+." data)))))
        (o (open-output-string)))
    (inflate i o)
    (get-output-string o)))

And finally, my url looks reasonable:

(print-ln "<a href=\"ser.scm?data="
          (data-encode (object->string *data*))
          "\">next page</a><br/>")

Does decode perform the inverse of encode?

(print-ln (data-decode (data-encode (object->string *data*))))

which gives:

> (("Task" "Monday" "Tuesday" "Wednesday") ("Cooking" "1hr" "1hr" "2hrs"))

The encoded data itself looks like this:

> "BcExCsAgDEDRq4Q/mdFeoXM3oXMgUougYOjg7fteShSLjnDN4bYRylfDbSPc1UcNt41K4pyz+rv+pNByG0h5LYQjrYC1R8+e"

I fixed print-ln too - display is for human-readable stuff, print is for the computer. Next time we will have to extract the data from the query string, load the data into a variable (this shouldn't be too difficult) and think about rendering the table. For anyone following along at home, the full script is as follows:

#!mzscheme -mqf

(require (lib "1.ss" "srfi")
         (lib "pregexp.ss")
         (lib "base64.ss" "net")
         (lib "deflate.ss")
         (lib "inflate.ss"))

(define-struct table-data (columns data))

(define (print-ln . args)
  (for-each display args)
  (newline))

(define (header type)
  (string-append "Content-type: " type "; charset=iso-8859-1~n~n"))

(printf (header "text/html"))

(define *encodings-list*
  '((%20 " ")
    (+a  "&")
    (+e  "=")
    (+p  "+")
    (+q  "\"")
    (+r  "\r\n")))

(define *hash* (make-hash-table 'equal))

(for-each (lambda (e)
            (let ((k (symbol->string (car e)))
                  (v (second e)))
              (hash-table-put! *hash* k v)
              (hash-table-put! *hash* v k)))
          *encodings-list*)

(define (blank-if-null s) (if (string? s) s ""))
(define *query-string* (blank-if-null (getenv "QUERY_STRING")))

(when (> (string-length *query-string*) 0)
      (printf "[~a]<br/>~n" *query-string*))

(define (string->object s)
  (read (open-input-string s)))

(define (object->string o)
  (let ((string-port (open-output-string)))
    (write o string-port)
    (get-output-string string-port)))

(define (manifest-string-encode s)
  (pregexp-replace* "'" s "\""))

(define (manifest-string->data s)
  (string->object (manifest-string-encode s)))

(define *data* #f)

(set! *data*
      (manifest-string->data
       "(('Task' 'Monday' 'Tuesday' 'Wednesday')
         ('Cooking' '1hr' '1hr' '2hrs'))"))

(define (encode-chars regex s)
  (pregexp-replace* regex s
                    (lambda (k)
                      (hash-table-get *hash* k))))

(define (data-encode data)
  (let ((i (open-input-string data))
        (o (open-output-bytes)))
    (deflate i o)
    (let ((r (base64-encode (get-output-bytes o))))
      (encode-chars "[+=]|\r\n"
                    (bytes->string/utf-8
                     (subbytes r 0 (- (bytes-length r) 2)))))))

(define (data-decode data)
  (let ((i (open-input-bytes
            (base64-decode
             (string->bytes/utf-8
              (encode-chars "\\+." data)))))
        (o (open-output-string)))
    (inflate i o)
    (get-output-string o)))

(print-ln "<a href=\"ser.scm?data="
          (data-encode (object->string *data*))
          "\">next page</a><br/>")

(print-ln "<a href=\"ser.scm\">Restart</a><br>")

(print-ln (data-decode (data-encode (object->string *data*))))

(exit)

Read Full Post »

Seaside has inspired me to try to write a basic HTML/CGI based grid style widget. In Seaside, that widget is called WATableReport which is described here and provides column sorting as is available in most decent GUI widget sets. My web-fu is a little rusty. Last time I was writing web code was back in the dark ages pre-servlets and nice session managing frameworks using pure CGI. This may well be available somewhere already but what the heck, let’s get started…

First of all, we need a data-type for the table data. For now we will keep it simple and just have the column names and the data which will probably be a list of lists.

(define-struct table-data (columns data))

We will need to serialise the data so we can pass it to another page through the query string. I briefly toyed with the idea of having some custom format of lists of comma seperated values surrounded by square brackets. e.g.

[Task, Monday, Tuesday, Wednesday],
[Cooking, 1hr, 1hr, 2hrs],
...

The parser tools supplied with PLT Scheme would make this fairly straight-forward. Even easier is using the read and print functions so we will go with that. I also want to be able to define a whole package of data for testing purposes and escaping every quote will be a pain so let me allow single quotes too. I had a look in the Scheme Cookbook for a simple way to transform characters within a string. string-map from srfi-13 offered a possibility, but we will almost certainly want something more powerful later so we might as well start with regexes.

(require (lib "pregexp.ss"))

(define (string-encode s)
  (pregexp-replace* "'" s "\""))

Together with a function to convert strings to scheme objects we can provide a convenience function for converting manifest strings into our data.

(define (string->object s)
  (read (open-input-string s)))

(define (string->data s)
  (string->object (string-encode s)))

(define *data* #f)

(set! *data*
      (string->data
       "(('Task' 'Monday' 'Tuesday' 'Wednesday')
         ('Cooking' '1hr' '1hr' '2hrs'))"))

Next time we will look at encoding the data so we can pass it through the query string. At the moment, we will have a small problem with quotes and a few other characters...

(define (print-ln . args)
  (for-each print args)
  (newline))

(print-ln "<a href=\"ser.scm?data=" *data* "\">next page</a>")

> "<a href=\"ser.scm?data="(("Task" "Monday" "Tuesday" "Wednesday") ("Cooking" "1hr" "1hr" "2hrs"))"\">next page</a>"

Read Full Post »

After fiddling around for a couple of minutes I found the correct incantation to get cgi scripts to work with Windows Apache. It uses the traditional Unix hash-bang line. Here we have a nice little hello world cgi:

#!mzscheme -mqf

(define *query-string* (getenv "QUERY_STRING"))

(define (header type)
  (string-append "Content-type: " type "; charset=iso-8859-1~n~n"))

(printf (header "text/html"))
(printf "Hello World~n")

(when *query-string*
      (printf "[~a]~n" *query-string*))

(exit)

But why bother with CGI when there are all these wonderful frameworks around? Isn't that old-school? Well, I've been inspired by something in Seaside. I'm not thinking of porting the whole framework, no way. If I wanted to, then why not just use Seaside? No, I want to try something much smaller.

Read Full Post »

Virtual Machines

Targeting a ubiquitous virtual machine is all the rage these days. All the cool kids are doing it. On the client-side, everything is the browser and/or flash with silverlight “coming soon”. On the server things are not much different with every man and his language running on the JVM or .NET. Of course HaXe follows this very sensible tradition and runs on both flash and the browser (and more) on the client and, erm, Neko on the server.

Neko?

So, why does HaXe target Neko? Well, it is common knowledge that on the server, it doesn’t really matter what you run. As long as you are web enabled, no-one cares. Actually, that is only true for the small number of people who run their own server. A far larger group of people write code that runs on a free host.

PHP

PHP isn’t usually thought of as a virtual machine but it is very common on free hosts. If you are writing your own compiler, you can target whichever runtime you like including PHP. I know Neko has many advantages over PHP including a huge speed advantage but ubiquity counts for lot. I suspect a PHP backend for HaXe would boost the number of potential users through the roof.

Read Full Post »

Seaside or HaXe?

Recently, I decided there was something of a hole in my programming toolbox – a good tool to develop websites. Now I want to change that so I have a number of options. I’ll group them into three main categories.

  1. Choose a framework that works for one of the languages I know already, e.g. Catalyst
  2. Choose a framework for another language, e.g. Rails or Django
  3. Choose a tool that is specifically designed for the web, e.g. Seaside, GWT or HaXe

For various reasons, I like option 3, and as I’m not interested in Java, it comes down to a choice between Squeak/Seaside or HaXe. This puts me in the unenviable position of making a choice between two technologies I know almost nothing about. Let me try and enumerate the pros and cons that I’m aware of at my superficial level of understanding.

Seaside positives

  • HTML/CSS generation using Smalltalk only
  • Better debugging
  • Image development – install pain is a thing of the past

Seaside negatives

  • Squeak is difficult to integrate with C.
  • Documentation is not as good as HaXe

HaXe positives

  • Language looks more familiar
  • Relatively bigger community
  • There is going to be a book available soon
  • Seems to be improving rapidly (in comparison with Seaside)
  • Integrates with C

HaXe negatives

  • You still need to write HTML/CSS (I think)
  • Not quite so easy to install

The big points here are obviously the integrates with C – e.g. if I want to use a Sqlite (ignoring the single-threaded blocking FFI version) or Sybase library then I have a lot of work to do. On the other hand:

  • I’m not sure I’ll ever need that functionality
  • even if I do, there are ways around it.
  • I love the idea of not writing HTML/CSS directly.

I think I’m going to go with Seaside and keep HaXe as backup.

Update: Looks like you do still need to know/write some amount of CSS which weakens my primary reason for choosing Seaside over HaXe.

Read Full Post »

Follow

Get every new post delivered to your Inbox.