swindon-rs / swindon

An HTTP edge (frontend) server with smart websockets support
Apache License 2.0
101 stars 9 forks source link

The new routing table #52

Open tailhook opened 7 years ago

tailhook commented 7 years ago

Motivation

Currently, we have two tables matching urls routing and authorization, let's take an example [1]:


routing:
  example.org: main
  example.org/admin: admin
  example.org/admin/metrics: metrics

authorization:
  example.org/admin: internal_zone

The motivation was: if you need to specify authorization info in the handler, it's easy to omit it for child route and get a security issue. On the other hand, we can't introduce inheritance between handlers, like nginx does, because they are of very different types and we would need very complex rules of which settings are inherited between handlers when matching down the tree and which do not inherit.

In reality, we need more things that can be either inherited or not, and don't depend on a handler. Here is a list of all things we think could potentially have their own routing table:

  1. Handlers
  2. Authorization
  3. Request rate limits (i.e, DoS protection, not implemented yet, but is planned)
  4. Access logs (and maybe error logs too)
  5. Error pages, and also maintenance page (which is basically a 50x error page)

Another point is: we need a compact and comprehensible routing table, i.e. the bad example [2]:

routing:
  example.org:
    handler: main
    authorization: no-auth
    access-logs: example-org-logs
    error-pages: example-error-pages
  example.org/admin:
    handler: admin
    authorization: admin-auth
    access-logs: example-org-logs
    error-pages: example-error-pages
  example.org/admin/metrics:
    handler: admin-metrics
    authorization: admin-auth
    access-logs: example-org-logs
    error-pages: example-error-pages

Few routes take a screenful of text, while in the real sites there are much more of them. Even if text editors can collapse mapping keys for you, they also hide important information.

Transposition isn't super-useful too, let's look at another bad example [3]:

routing:
  example.org: main
  example.org/admin: admin
  example.org/admin/metrics: metrics
authorization:
  example.org: main
  example.org/admin: admin
logging:
  example.org: main
  example.org/admin: admin
error-pages:
  example.org: main
  example.org/admin: admin

While it's quite good for small table, but it scales too bad for multi-site installations.

Another alternative would be to introduce two-level table, here is another bad example [4]:

routing:
  example.org:
    error-pages: example-log
    log: example-log
    child-routes:
      /: root-handler
      /admin: admin-handler

While at a glance this introduces short and comprehensible routing table, in which each site has their own settings, which also allows putting that section into another file, still it has shortcomings both ways:

  1. Not all sites depend on domain so much, an application can span multiple and potentially infinite number of domains
  2. Many sites have "subsites" hosted in a folder rather than different domain. I.e the /admin section in the first example is actually another site (i.e. it has another handler, error pages, logs, access control and rate limits)

Specification

So the idea is to create small DSL for making routing table compact:

routing:
  example.org: main->example-org-log errors=admin-err
  example.org/admin: admin@admin->example-admin-log
  example.org/admin/metrics: metrics

DSL looks like:

  1. Adding @something to the name of the handler uses the authorizer something for the route and all the enclosed routes
  2. Adding ->something uses logger named something for the route and all enclosed routes
  3. Error pages are marked as errors=something as probably will be overridden more sparingly

Notes:

  1. errors= syntax is extensible to xx=yy but we don't plan to add anything more here yet
  2. Handlers are still not inherited, as they are most crucial part of routing table
  3. Authorization routes and DoS protection domains are combined under a single @something umbrella, we think they are integrated quite close
  4. All names of loggers, authorizers, handlers and error-page domains are defined in their own sections. The function of the routing table is to show where routing does change, rather than provide full details.

Unsolved Problems

Sometimes unauthorized users should be presented different route than authorized. Currently we presume that it's application's problem, rather than the one of a frontend server.

/cc @popravich, @anti-social

popravich commented 7 years ago

So the idea is to create small DSL

Ouch, it looks creepy at the first sight...

tailhook commented 7 years ago

So the idea is to create small DSL

Ouch, it looks creepy at the first sight...

So what are the alternatives? All ones mentioned in "motivation" section are clearly worse than this one. The nginx and caddy are basically example [2]. While linkerd has basically a mapping between a routing table and handler written as a weird DSL for rewrite rules (but note that linkerd has only proxying so it doesn't have our auth-rate-limit-error-page dilemma, and has access log only at granularity of protocol level as far as I can see).