Best way to implement a "front controller" with Racket's web server

This question is primarily directed to @jeapostrophe , but anyone can feel free to comment :slight_smile:

I think back in 2019, Jay mentioned my use of web-server/dispatchers/dispatch-lift in Axio was less than ideal, and I never bothered to follow up for more detail re: why. I'm not using continuations, and I simply want a straightforward way for the Racket web server to dispatch to my own "front controller", and using dispatch-lift seemed like the most straightforward way to dispatch to a function.

The relevant code is axio-app-init and front-controller

If there's a better way to get the web server to call my front controller, I'm all ears :slight_smile:

I'm about to use @bogdan 's sentry package to send unhandled exceptions to Sentry, so it's a good time to tweak this part of the architecture if necessary.

Thanks!

1 Like

Replying partly to keep track of this thread.

IIRC when I started development on Frosthaven Manager's web server, I looked at lift:make, too. I don't recall what for and I don't have any commit records of the experiment (sadly), but I seem to remember the primary issue being that the implementation doesn't handle continuations well:

(define ((make procedure) conn req)
(output-response/method
conn
(procedure req)
(request-method req)))

Again, I don't remember the specific problem, but I think it had to do with adding "middleware" to the request handling and not having that work correctly in certain cases?

Jay and others can probably comment more thoroughly. I know Brian is also interested in how Koyo handles this, and I haven't dived into the code yet to find out :slight_smile:

If you don't want to use continuations in your application, then I don't think there's anything particularly wrong with avoiding dispatch/servlet and using dispatch-lift.

I like to put my middleware at a level below dispatchers, though, so I typically have middleware defined as procedures from a request handler to a request handler:

(define-values (app reverse-uri)
  [("") index-page])

(define (stack handler)
  (~> handler
      (wrap-cors)
      (wrap-auth)
      (wrap-session)
      (wrap-sentry))

(dispatch/servlet (stack app))

where wrap-sentry might look like:

(define ((wrap-sentry hdl) req . args)
  (with-handlers ([exn:fail? (lambda (e)
                               (sentry-capture-exception! e)
                               (raise e))])
    (apply hdl req args)))
1 Like

Bit of thread necromancy here, my apologies.

I checked the the axio link from up above and noticed this bit of code:

 (dynamic-wind void
                run
                void)

I was wondering about the purpose of it -- my understanding of dynamic-wind is that it ensures the pre and post thunks are always called before/after the value thunk regardless of continuation jumps, but if you use void for those thunks then it seems pointless. I went to the docs on dynamic-wind and had trouble following it. Can someone walk me through why this would be used?

1 Like

Ha! When I posted that (very old) code, I noticed the same thing re: dynamic-wind, and wondered if I'd get any comments :slight_smile:

I think that the voids may have been placeholders that never got replaced, and then maybe I went with with-handlers instead. I'm not expecting to use any continuations, so maybe dynamic-wind is unnecessary, but the post thunk may be a good place to report exceptions to Sentry.