orthecreedence / wookie

Asynchronous HTTP server in common lisp

Home Page:http://wookie.lyonbros.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

router breaks inside alert where async is used (next-route)

nightshade427 opened this issue · comments

If I do this it just hangs and doesn't go to next route. It prints "routing made it". Then hangs

(defroute (:get "/.*") (req res)
  (format t "~%routing")
  (let ((token (get-var req "token")))
    (alet ((data (http-request "http://www.google.com")))
      (if token
          (progn (format t "~%made it") (next-route))
          (send-response res :status 401 :body "Not Authorized" :headers '\
(content-type "text/plain"))))))

Where as I do this it works and prints " routing made it to next route", and next route is called.

(defroute (:get "/.*") (req res)
  (format t "~%routing")
  (let ((token (get-var req "token")))
    (alet ((data 1))
      (if token
          (progn (format t "~%made it") (next-route))
          (send-response res :status 401 :body "Not Authorized" :headers '\
(content-type "text/plain"))))))

The reason is that the routing system isn't future-aware like the hook system...it triggers the route function and expects either a 'use-next-route condition or a return. Once you spawn an async action, it thinks "ok, the route ran fine" and (next-route) fires a condition that nobody is listening for.

I'm trying to think if there's a reason I never implemented future-aware routing. I think the answer is probably I never had much of a use so didn't think to put it in. It seems like a good idea.

It might be a bigger change, so could take some time to get to. In the meantime I'll keep the issue open.

Thanks for looking into this :)

Any suggestions for work around? Don't use drakma async I gues, but instead use drakma?

On second look, it seems as though you're implementing some form of authentication system. The hook system is perfect for this!

(add-hook :pre-route
  (lambda (req res)
    ;; create a new future which will be passed back to the hook system
    (let ((future (make-future)))
      ;; see if we even need to authenticate
      (if (my-app:page-requires-auth req)
          (alet ((token (drakma-async:http-request "tokens.myserver.com")))
            ;; got a token, check if it's good
            (if (my-app:token-is-cool token)
                ;; everything's great, finish the future
                (finish future)
                ;; bad token, fail
                (progn
                  ;; be sure to send the "fail" response here since Wookie will
                  ;; not do it for us!
                  (send-response res
                                 :status 403
                                 :body "Authentication failed.")
                  ;; signal an error on the future to let Wookie know not to
                  ;; continue processing the request
                  (signal-error future "auth failed!"))))
          ;; everything's great, finish the future
          (finish future))
      future)))

This runs before your routes run, and because it passes a future back, Wookie delays processing a request until the future finishes. The token-is-cool and page-requires-auth functions are obviously up to you, but this is the easiest way to implement app-wide authentication/login.

It was just example :)

We do use an auth system in wookie though, and we do do it as a route because we don't use auth for all routes, just some. So we put the non auth routes before the auth route and the auth route filters the remain to be authorized. Seems to work good that way. Can the hooks you mention achieve the same as clearly?

I really need the async drakma to work in routes or my server will hang for 500ms each request while it gets the auth info stopping it from processing other requests :(

Yes, your page-requires-auth function from the example above would check the (request-uri req) against either a list of approved or auth-required pages or regular expressions.

This is what we do in Turtl and it works great for having both authenticated and non-authenticated pages. I think the hook system is the way to go here, per your requirements.

That duplicates the route information though on two places? One for route definitions and one for the list of allowed routes? Right now we do something like this:

Route 1
Route 2
Route 3
Auth
Route 4
Route 5
Route 6

This keeps everything cleanly on the route file and what is authorized and what's not. Sorta like an ip tables file :)

We also have the need for drakma async for proxying and processing certain content as well in one of our routes. We do a drakma fetch and based on content type returned by server process the data or forward to next route.

I guess I can have a god route and do everything in pre hooks?

I suppose I could also create a macro to handle that for me and keep it mostly readable. Any idea how long it would take to make next-route future aware? Send-response seems to work fine with futures.

Another options is to wrap defroute in your own macro that takes an :auth t keyword and if present, adds it to a list of routes requiring auth.

Jinx :)

I can also take a look and see what would be involved an possibly help out :)

In mean time I will just macro and use hook :)

Okay used macro and pre-route hook, looks good still and works and is async again. This is awesome! Thanks again for all the help.