Archive for January, 2009

First (real) Yesod commit

January 31, 2009

OK, I’ve gotten the first real code commit to Yesod. There are a few decisions that I made with this commit:

  • I’d originally intended to put generic web utilities in a separate repository. I changed my mind with this one, deleted my webutils repository on github, and merged all the code into Yesod. This should make it easier to maintain.
  • The name given to this first bit of code is the “backend” code. This includes the definition of a request and a response, and the definition of an application (Request -> IO Response) and an adapter (something which runs an application).

The only adapter included in this commit is Standalone, which- as you might guess- runs a standalone server. I will include CGI in another commit, and then barring bug fixes, that’s about all I want in the backend code. I do want to have a FastCGI adapter, but I don’t want the fastcgi dependency to break building on systems without the C library, so I’d prefer to have a separate yesod-fastcgi repository. That would be a great project for anyone looking to contribute…

Also, I’ve included an “examples” directory. I’m hoping to keep this populated with more and more advanced examples as the framework progresses to give guidance to new users. Nonetheless, I do not expect that to be a replacement for good documentation; this framework will hopefully have both full Haddock documentation and more tutorial-oriented documentation at the appropriate time.

The next piece to work on will be the controller. So if anyone has any architectural ideas, speak now or forever hold or peace. The one thing I am thinking right now is: KEEP IT SIMPLE. It seems like everyone insists on using regexs or parsers for handling routing. I just have to ask: why? I’ve never heard of routing that does something much more complicated than dealing with a path like a filesystem path, ie separating on the slashes.

So here’s my idea: allow some kind of super-general function for advanced routing, but have a simplified interface for the usual case. If that sounds too vague, then just wait for the code to be available ;).

Haskell Web Framework

January 25, 2009

I haven’t said much on the Haskell mailing lists, though I’ve been following them for the past few months. I’ve been using Haskell for close on a year now, and must say that I love it. What I would especially love would be to create websites in Haskell, which is where this post comes in.

Before someone jumps in with a framework suggestion of HAppS or Turbinado, they are simply not options for me (and I assume many other people) because I am stuck with shared hosting services. The only framework I’ve seen so far that seems to be shared hosting-friendly is Kibro, which- although great for its purpose I’m sure- simply does not give the features I’d be looking for in a framework.

I’m not trying to claim here that these other frameworks are The Wrong Way and that I have created a framework that does it The Right Way. So far, I have written some code, but I know that, by myself, I won’t be able to get it to handle everything that it should. I’m hoping to engage others here in some brainstorming, and perhaps ultimately collaborating on code, to design a much more complete web framework.

I have thought quite a bit about some portions of the framework, whereas for others (most notably models) I don’t have anything coherent thought out. So my idea was to throw up all my thoughts here and get people’s feedback. If you think my ideas might be a good basis for collaborating on a Haskell web framework, then we can move on from there. Otherwise, I guess I’ll return to the torture which is (PHP | Python | Ruby) coding.

  1. We should have something similar to WSGI. This way, the same application can be used running its own server (especially useful for testing), CGI, FastCGI or even mod_haskell. You can see my idea for this generic typeclass in Web.Server.Service.WebServer. I don’t have my heart set on most of that interface, except for the next item.
  2. Approot is very useful. Basically, I think every request should carry along with it the following two pieces of information: the URL to the root of the current web application, and the path to the current page within the web application. For example, if I write a web application and am running it with the test server on port 8080, and go to the URL “http://localhost:8080/foo/bar/”, the approot is “http://localhost:8080/” and the path is “foo/bar/”. This becomes very useful when you then stick your web application in a subdirectory of another site, so that the application home page is http://www.example.com/myapp/. Then if you go to http://www.example.com/myapp/foo/bar/, the approot is http://www.example.com/myapp/ and the path is foo/bar/.
  3. By convention, each web application should have a “static” directory for images, CSS, JS, etc. Then, we can automatically generate .htaccess files for (Fast)CGI versions of the site that show those files, and the built in server would know to display those.
  4. Now pulling points 2 and 3 together a little bit: there is a built in concept of internal versus external links. This might be represented as data Link = ILink String | ELink String. You could then create a function to convert the links into URLs as so: urlFromLink :: Request -> Link -> String
    urlFromLink _ (ELink s) = s
    urlFromLink req (ILink s) = approot req ++ s
    This way, the link ILink "static/images/cutecat.jpg" will turn into either http://localhost:8080/static/images/cutecat.jpg or http://www.example.com/myapp/static/images/cutecat.jpg
  5. User authentication should be built in. I like the idea of basing it around OpenID with options for logging in with e-mail addresses, and then having a built in interface for linking OpenIDs and e-mail addresses together into a single account, but I’m flexible on this point.
  6. Another flexible point: we should store session data in encrypted client cookies. I believe I read about this first with Rails 2.0, and that it is apparently supposed to be much faster since it doesn’t require file access for every request. I know that in the past I’ve been bitten by concurrent writes to SQLite databases when writing session information, and this can help.
  7. We should use UTF-8 everywhere and for everything. I suppose we should make it possible for someone to use a different encoding, but UTF-8 is the right answer 99% of the time IMHO.
  8. When it comes to models, I am torn between “let’s build a great ORM” and “relational databases are awesome.” My day job is an actuary, so I am very comfortable with straight SQL, but I understand that other people are not. There is also the issue of type safety to deal with. I defer on models to those wiser than I here.
  9. I really like the idea of generic views, though the implementation obviously depends greatly on how we do models, so I don’t have any clear ideas on this yet.
  10. The framework should handle any type of response, be it an image, a plain text file, XML, JSON or HTML. However, we should have a very high-level framework for generating interactive HTML pages that include AJAX functionality. This last piece is broken up into sub-parts, and makes up the rest of the items.
  11. I’ve always been irked by the thought of treating <head> and <body> content as the same thing. For example, most frameworks allow you to specify, somehow, a layout for other HTML pages. This layout usually looks something like this:<html> <head> <link rel='stylesheet' href='style.css'/> <title>[...]</title> </head> <body>[...]</body> </html>
    See the problem? You have to supply two separate hooks to fill in content in the template: one for the title, one for the actual content. Now, let’s say that you want one page to have an additional stylesheet, or some javascript. You need to make even more hooks available. Instead, I think that we should specify our layouts and pages in something similar to the following:
    data Tag = Tag { isHead :: Bool, name :: String, attrs :: [(String, String)], children :: [Tag] } | TagRaw String
    type Page = [Tag]

    myPageContent :: Page
    myPageContent = [Tag True “link” [(“rel”, “stylesheet”), (“href”, “mypage.css”)] [], Tag True “title” [] [TagRaw “My Page”], Tag False “h2” [] [TagRaw “My Page”]]

    layout :: Page -> Page
    layout content = [Tag True “link” [(“rel”, “stylesheet”), (“href”, “layout.css”)] [], Tag False “h1” [] [TagRaw “My Site”]] ++ content

    myPage :: Page
    myPage = layout myPageContent

  12. User input validation should Just Work. If you specify that a box should be a date, then you should automatically have client- and server-side validation of the content without having to think about it. Along the same lines, you should automatically get a date picker attached. The same theory applies to all input types. This should interact perfectly with the model portion as well.
  13. It should be possible to have the output of one piece of the page be dependent on some other part. For example, let’s say you have an input box on the page for someone’s birthday, and elsewhere an output box for their age. We should be able to simply specify the algorithm necessary to calculate an age from a birthday, and the framework will convert this into both client- and server-side code, so that if Javascript is available, the user will immediately see his/her age; otherwise, it will show up after hitting submit.
  14. Related to the previous note: sometimes it is not possible to generate these results client-side. Take, for example, a drop-down list of blog entries to choose from. If the user selects a new entry, there is no Javascript in the world that can automatically determine the content of the post without consulting the server. So I think we need something like the following: when the framework knows that the server will be consulted for some information, it will automatically generate a controller for that information in JSON format (or XML, I’m not very particular on that issue). Then, when the user selects the drop-down box, it will run an AJAX query to download the content and update the page. If javascript is unavailable, the new content will be filled in after hitting submit.

Sorry for my verbosity, but I wanted to get all these ideas into one place and open up a forum for discussion of them. Once again, if this sounds like a good idea to anyone else, and others would like to help brainstorm and implement this in Haskell, let me know. If you think this is a stupid idea, let me know that too ;-).