how does it work?

29 views
Skip to first unread message

Kostas Kougios

unread,
Jan 23, 2012, 4:07:18 PM1/23/12
to Circumflex Public Q&A
Hi, I've been trying out writing Router today. Looks to me like the
Router is basically a constructor and it just tries one by one the
routes. If it finds an appropriate route it then somehow magically
stops execution (does it throw an exception that is later on caught?).
How scalable is that? Say I have 200 routes, will circumflex scan all
of them?

Boris Okunskiy

unread,
Jan 24, 2012, 1:06:17 AM1/24/12
to circumfl...@googlegroups.com
Good day,

Even sequentially matching all 200 routes will do the job rather quickly, but we usually use structural approach with routers nesting, so your main router will only contain few top-level dispatchers and every request will be served in few hops.

E.g. for the route /admin/users/10/plans/8/preferences we will have following nesting:

class MainRouter extends Router {
...
any("/admin/*") = new AdminRouter
...
}

class AdminRouter extends Router("/admin") {
...
any("/users") = new UsersMgmtRouter
...
}

class UsersMgmtRouter extends Router("/admin/users") {
...
any("/:id/*") = User.findByParam("id")
.map(u => new UserMgmt(u))
.getOrElse(sendError(404))
...
}

class UserMgmt(user: User) extends Router {
prefix = "/admin/users/" + user.id
...
any("/plans/:id/*") = Plan.findByParam("id").filter(_.user == user).map(p => new PlanMgmt(p))
...
}

class PlanMgmt(plan: Plan) extends Router {
val user = plan.user
prefix = "/admin/users/" + user.id + "/plans/" + plan.id

get("/preferences/?") = { // render view }
}

This way you not only get super-fast matching, but also clean structural routes and controllers. You'll never spend much time finding out who exactly dispatches your request.

And yes, the exception is thrown -- this is also okay since the performance cost of Scala's `ControlThrowable` is low.

Best regards,
Boris Okunskiy

Konstantinos Kougios

unread,
Jan 24, 2012, 3:00:44 PM1/24/12
to circumfl...@googlegroups.com
Hi Boris,

Thanks for the info, yes all looks good.

Regarding the exception, that is ok as well, exceptions have an overhead
cause they gather the stacktrace but the overall impact should be
minimal for processing http requests. I did a quick test and a loop with
1000 iterations ,each iteration calls a method that throws an exception,
takes 29ms where us if it doesn't throw an exception it takes 5ms. But
29ms/1000 requests per processor is not bad as overhead.

Regards,

Kostas

> �

Boris Okunskiy

unread,
Jan 25, 2012, 1:11:48 AM1/25/12
to circumfl...@googlegroups.com
That's right, but ControlThrowable has even less overhead. Here's the snippet from `scala.util.control` package:

trait NoStackTrace extends Throwable {
override def fillInStackTrace(): Throwable =
if (NoStackTrace.noSuppression) super.fillInStackTrace()
else this
}

So we use the same control throwable without stacktrace. You should try the same test with this kind of exceptions.

As a matter of fact, Scala itself throws such things, for example, if you use `return` inside anonymous high-order function. I'm pretty sure you can find the same discussion in the group.

Best regards,
Boris Okunskiy

>> …

Konstantinos Kougios

unread,
Jan 25, 2012, 3:48:56 PM1/25/12
to circumfl...@googlegroups.com
Thanks Boris, gave it a go, normal exception: 25ms, NostackTrace
exception: 11ms, no exception : 4ms. Performance in this case is not an
issue.

Unfortunately the exception caused an other issue. Our project is a
Java/spring based and it would be useful to be able to @Autowired stuff.
Since routing needs 1 router instance per request, I tried to wire
routers to be "prototypes", this means that spring will create 1
instance every time I request the router. So far so good, but because a
Router does it's job inside the constructor and throws an exception,
spring was logging the exception and probably rethrowing it (and
magically later on circumflex was catching it and the request was
processed normally). So the request was processed, but for every request
an exception was logged by spring.

Anyway, I had to avoid using spring for instantiating routers. I am
doing "new Router", manually wiring the dependencies.

An alternative for the exception maybe would be i.e. to collect the
valid routes (or the 1 and only valid route and then just skip the rest)
in a stack or so. But anyway I don't know if that would be equivalent to
the way circumflex works right now. Router could contain a "var
Router.validRoute" pointing to the next router and then circumflex could
do Router.validRoute.validRoute... till it finds the last one. I am just
giving an idea knowing that it probably won't work due to a reason or
the other.

Regarding scala's return statement using an exception, yes I am aware of
it, not using it myself as it can cause troubles and also it was never
needed so far. Some code may catch the exception without rethrowing it
and then all short of weird bugs can occur. But I don't think the scala
team had any other option to achieve this functionality.

Best Regards,

Kostas

>>> �

Boris Okunskiy

unread,
Jan 26, 2012, 8:07:46 AM1/26/12
to circumfl...@googlegroups.com
On Jan 26, 2012, at 12:48 AM, Konstantinos Kougios wrote:


Unfortunately the exception caused an other issue. Our project is a  Java/spring based and it would be useful to be able to @Autowired stuff. Since routing needs 1 router instance per request, I tried to wire routers to be "prototypes", this means that spring will create 1 instance every time I request the router. So far so good, but because a Router does it's job inside the constructor and throws an exception, spring was logging the exception and probably rethrowing it (and magically later on circumflex was catching it and the request was processed normally). So the request was processed, but for every request an  exception was logged by spring.


In Circumflex we implement a tiny instantiation facility, which pretty much suits all our DI needs (see http://circumflex.ru/api/2.1/circumflex-core/src/main/scala/circumflex.scala.html). If you experience any problem with Spring DI, you can easily fall back to our version.

As for the "magically" caught exceptions, it's the job of CircumflexFilter (http://circumflex.ru/api/2.1/circumflex-web/src/main/scala/filter.scala.html) and is a part of router execution. The behavior of the filter is well-formalized, so you can stick to the provided source code if you will decide to implement your own Spring-based executor.

Best regards,
Boris Okunskiy

Konstantinos Kougios

unread,
Jan 26, 2012, 4:21:16 PM1/26/12
to circumfl...@googlegroups.com
Thanks Boris, I'll stick to the existing filter (seen the src code, I
probably can override serveStatic to always return false as an
optimisation since my routers are mapped to *.pagex). The goal is to use
a scala web framework (circumflex) but also stick to the existing
project view technology (velocity) and DI backend (spring) in order to
be more consistent with the rest of the project (which is a quite big
java codebase). So far i did that with a bit of patch work, velocity
already works and I am able to wire spring beans. I am going to resume
work on it tomorrow, it should be ok.

Regards,

Kostas

>
> Best regards,
> Boris Okunskiy
>

Reply all
Reply to author
Forward
0 new messages