Ring branched merged and version 0.1 released

2 views
Skip to first unread message

James Reeves

unread,
Mar 21, 2009, 3:35:45 PM3/21/09
to Compojure
Hi folks,

I've just finished making some large changes to the Compojure
repository. Read on for a breakdown of the new additions:

== Version 0.1 released ==

I've taken the current code that people have been using, and forked it
into the "stable" branch. People have been using this with fairly few
problems reported, so I've released this as version 0.1. This version
uses the tried-and-tested "defservlet" syntax most people are familiar
with.

== Ring branch merged ==

The "ring" branch has now been merged into the master branch. This
represents a big change to how Compojure is put together, with a few
changes to the syntax. Note that this hasn't been used as extensively
as version 0.1, so you might run into bugs that have been missed out
during testing. I'll try and fix any reported issue as quickly as
possible.

= defroutes syntax =

Instead of using defservlet to define a servlet, you now should use
defroutes:

(defroutes my-app
(GET "/"
(html [:h1 "Hello World"])))

This defines a Ring handler. This is a function that takes in a
request map, and returns a response map:

=> (my-app {:request-method :get, :uri "/"})
{:status 200, :headers {}, :body "<h1>Hello World</h1>"}

With this syntax, Compojure achieves a great deal of flexibility. You
can simply define middleware functions to handle caching, and easily
write interfaces to plug Compojure into any web server you wish. It's
also a heck of a lot easier to test.

Read more about how the request and response map are defined in the
Ring SPEC:

http://github.com/mmcgrana/ring/blob/4a035a2c20c654cf8020379aad8625e2...

Once you've applied all your middleware to your routes, you can turn
it into a servlet with the 'servlet' inline:

(run-server {:port 8080}
"/*" (servlet my-app))

Or use the defservice macro to create a "-service" function, suitable
for genclass:

(defservice my-app)

No longer is there a need for messy hacks to abstract your routes from
their implementation. But it gets even better; because defservice and
servlet are macros and inlines respectively, if you redefine your
routes, any changes you make will automatically and instantly be
picked up.

= Embedding routes =

You can also embed routes inside routes. This is useful if you have a
whole bunch of routes and you want to split them up into logical
sections:

(defroutes all-routes
(GET "/admin/*"
admin-routes)
(GET "/*"
public-routes))

= Returning headers =

Compojure now treats any map being returned by a route as a response
map. This means that adding headers is a little more verbose:

(GET "/"
[{:headers {"Content-Type" "text/plain"}}
"Hello World"])

Or alternatively:

(GET "/"
{:headers {"Content-Type" "text/plain"}
:body "Hello World"})

= Route local bindings =

There are now only five local bindings defined for each route:

* request - the request map
* params - a map of all the parameters in the request
* cookies - the cookies in the request
* session - the request session
* flash - the session flash

This seems bare, but that's because a lot of information is now stored
in the request map. Along with all the keys mentioned in Mark's Ring
SPEC file, such as :uri and :request-method, Compojure adds the
following additional keys:

* :route-params - parameters matched by the route
* :query-params - parameters encoded in the query string
* :form-params - parameters encoded in the request body
* :cookies - the cookies in the request
* :session - the request session
* :flash - the session flash

Basically, all the information you could ever want is now stored in
the request map. The local bindings just provide convenient shortcuts.

= Sessions =

Sessions are now completely detached from Java. They're functional and
easily extensible, so you can store your sessions in files, in a
database, or wherever you wish, just by overriding a few multimethods.
Compojure defaults to using in-memory sessions, and this is currently
the only type of session included with Compojure.

To read from sessions, you can use the session binding:

(GET "/"
(html [:h1 "User: " (session :user)]))

To write a session you can return the session in the response map:

(POST "/"
[{:session (assoc session :user (params :user))}
(redirect-to "/")])

That's a little long-winded though, so you can use the session-assoc
and session-dissoc functions to make it easier:

(POST "/"
[(session-assoc :user (params :user))
(redirect-to "/")])

The session-assoc and session-dissoc demonstrate functionality related
to embedded routes. Because routes are just functions, you can return
your own handler functions from routes:

(POST "/"
(fn [request]
{:session (assoc (request :session) :user (params :user))}))

This is why session-assoc can functionally modify the session without
needing to be passed the session explicitly.

= Flash =

The flash is a place to store temporary messages between requests.
Anything added to (session :flash) will persist until the next
request, then be wiped. For example:

(POST "/register"
[(flash-assoc :message "Set username")
(session-assoc :user (params :user))
(redirect-to "/")])
(GET "/"
(html
(if (flash :message)
[:div.message (flash :message)])
...))

= Upcoming features =

That's about it for the current changes. I'll briefly mention what I'm
looking to have in Compojure in the near future:

* Session storage types of :cookie and :file
* File uploads
* SSL support in Jetty server
* Possibly a Grizzly server
* Absolute URL support in routes so you can set subdomains:

(GET "http://:category.example.com/"
(index (params :category)))

- James
Reply all
Reply to author
Forward
0 new messages