Request Routing

Each incoming HTTP request is processed by a configurable routing engine. The engine matches every request against a list of virtual hosts. Subsequently the request is matched against a set of path based rules. The matching rule determines the actual action that is applied to the request. A request not matching any virtual host or rule is denied with 404 Not Found.

The routing engine is configured via routing.yml.

Note

The built-in routing engine can be bypassed based on custom inbound filters.

Virtual Hosts

Virtual hosts are referred to as vhosts in routing.yml. The file requires a vhosts key as top-level directive, under which a list of vhosts are declared.

A vhost specification consists of the following attributes:

hostAddress

Mandatory: Target IP address to match against incoming request.

Can be specified explicitly or as a wildcard (*). The hostAddress can be either an IPv4 or IPv6 address. In case of IPv6 address the compressed and uncompressed notation is allowed.

port

Optional: Target port to match against incoming request.

Can be specified explicitly or as a wildcard (*).

hostNames

Optional: One more more host names, matched against the requested host.

Can be specified explicitly or as a wildcard (*), can not be specified with IPv6 notation.

brand

Optional: A brand identifier that can be applied as header to forwarded requests in multi-tenant environments.

rules

Mandatory: A list of path matching rules. See Path Matching.

hostRewrite

Optional: A list of optional hostname rewrites. The requests original Host will be matched against the value in the from parameter. Wildcards are allowed as prefix. The Host and X-Forwarded-Host headers will be replaced with the value in to, ignoring all present values.

Note

To enable the host rewrite feature, the property has to be set and containing at least one from & to key/value pair. Only the hostname is considered when trying to match the requests original host against the from value, not the port or protocol type. Also, wildcards are allowed for subdomains in the from value. For example: *.example.com and sub.*.example.com.

Example:

vhosts:
  - hostAddress: 203.0.113.1
    port: 80
    hostNames:
      - example.com
      - example.org
    hostRewrite:
      - from: "*.example.com"
        to: "rewrite.com"
    brand: example
    rules:
      - path: /
        ...
  - hostAddress: 1234:1234:0000:0000:0000:0000:3456:3434
    port: 80
    hostNames:
      - example.com
      - example.org
    hostRewrite:
      ...
  - hostAddress: 1235:1235::3457:3476
    port: 80
    hostNames:
      - example.com
      - example.org
    hostRewrite:
      ...

Host matching

With the exception of the host address, all matching related attributes are optional. The default value is always *.

The mechanism of finding the best matching vhost is based on the combination of matching attributes between request and vhost. These are prioritised as follows:

Priority

Host address

Port

Hostname

1

match

match

-

2

match

*

match

3

*

*

*

4

*

match

match

5

*

match

*

6

*

*

match

7

*

*

*

In general hostAddress > port > hostNames > *.

Hostnames are matched against requests Host or X-Forwarded-Host headers. If an X-Forwarded-Host header was preserved according to configuration, its value is taken for matching and Host is ignored.

Example configuration:

vhosts:
  - hostAddress: 172.20.30.50
    port: 9001
    hostNames:
      - www.example.org
    brand: default
    ...
  - hostAddress: 127.0.0.1
    port: "*"
    hostNames:
      - localhost
    brand: default
    ...
  - hostAddress: "*"
    port: 80
    hostNames:
      - webmail.example.com
      - dav.example.com
    brand: default
    ...
  - hostAddress: "*"
    port: 80
    hostNames:
      - "*"
    brand: default
    ...
  - hostAddress: "*"
    port: "*"
    hostNames:
      - www.example2.org
    brand: default
    ...
  - hostAddress: "*"
    port: "*"
    hostNames:
      - "*"
    brand: default
    ...
  - hostAddress: "*"
    brand: default
    ...

Path-based rules

The rules attribute of a vhost contains a list of path matching rules and associated proxy actions. The best match for a given request decides what action is applied to this request.

A rule specification consists of these attributes:

path

Mandatory: A path match expression

action

Mandatory: An action specification

restrictions

Optional: List of restrictions that are applied to matching requests before the action is taken.

Example:

- path: /
  action:
    type: redirect
    location: /other

Path Matching

In general it is possible to define the path to match in three different ways:

  • absolute path matching

  • extended path matching by using wildcards

  • regex based path matching

Each path is associated to a given rule. The mechanism to identify which rule to chose will be outlined at the end of this page (see Best match).

Absolute matching

The easiest path to set is an absolute path. The matching algorithm will match all path below the configured one.

Example:

Configuration:
  /appsuite/api

will match:
  /appsuite/api/
  /appsuite/api/mail

Extended absolute matching

In addition to the absolute path matching it is possible to add wildcards to the configured path. * defines any number of any character while ? defines one of any character.

Example:

Configuration:
  /*/api/v?

will match:
  /appsuite/api/v1/
  /ajax/api/v2/

Regex matching

For anything that cannot be handled with the matching algorithms above you can use the regex format which provides endless configuration options for your path. Regex path definitions have to start with a tilde (~) followed by the regex path.

Example 1:

Configuration:
  ~ /appsuite/api/([^/]+/)?auth

will match:
  /appsuite/api/auth/
  /appsuite/api/example.com/auth/sub/

Example 2:

Configuration:
  ~ /(extra|special)/data

will match
  /extra/data
  /special/data/2

Best match

The mechanism to find the best matching rule based on the path evaluates the requested path against all configured paths regardless of they are defined absolute, by using wildcards or regex. The most specific path (which means it matches most path elements) will be used. If two or more paths match the same number of path elements the first defined will be used.

Example 1:

Configuration:
  Path 1: /
  Path 2: ~ ^/api/.*$
  Path 3: /api/v?/books

Request:
  /api/v1/books/by-isbn/12345

=> Path 2 matches complete path and will be taken

Example 2:

Configuration:
  Path 1: /
  Path 2: /api/v1
  Path 3: /api/v?/books

Request:
  /api/v1/books/by-isbn/12345

=> Path 3 matches 3 path elements and will be taken

Routing Actions

Routing actions take an incoming request and apply all the configured settings and actions. Whether an action is applied or not, depends on the configured condition. It can either be a path match or another conditional match. The conditional match is tested by a dedicated action, the conditional action.

Forwarding

The forward action actually forwards an incoming HTTP request to a an upstream/origin server.

Configuration

- path: /appsuite/api
  action:
    type: forward
      rewritePath: /ajax
      shardDetector: httpApi
      shardBootstrapHandler: httpApi
      backendPool: appsuite-mw
- path: /appsuite
  action:
    type: forward
      shardDetector: httpApi
      shardBootstrapHandler: httpApi
      backendPool: appsuite-ui
- path: /Microsoft-Server-ActiveSync
  action:
    type: forward
      shardDetector: httpApi
      shardBootstrapHandler: eas
      backendPool: appsuite-mw
      clientConfig:
        readTimeout: 1900000
backendPool

Mandatory: Define backend pool to forward the request to. Values are taken from name values found under the global or shards section in backends.yml

rewritePath

Optional: Rewrite request path to specified value before forwarding the request to an origin server.

clientConfig

Optional: Additional configuration parameters that can be applied to the client that handles the forwarding. For additional information see client configuration section.

shardDetector

Optional: The shard detector which should be used to determine the route for the forward request. Values are taken from name values found under the detectors section in sharding.yml. For more information please look at the sharding section.

shardBootstrapHandler

Optional: The shard bootstrap handler which should be used to determine the route for the forward request. Values are taken from name values found under the bootstrapHandlers section in sharding.yml. This setting becomes mandatory if a shardDetector is set. For more information please look at the sharding section.

Path rewrite

Rewriting the request URI path is supported for all types of path definitions. In all cases the part that matches the requested path will be rewritten by the given configuration.

Example 1:

Configuration:
  match: /ajax/
  rewrite: /servlets/ajax/
Request:
  /ajax/chronos
Rewrite result:
  /servlets/ajax/chronos/

Example 2:

Configuration:
  match: ~ /(appsuite/api|ajax)/
  rewrite: /servlets/
Request:
  /appsuite/api/chronos/
Rewrite result:
  /servlets/chronos/

Example 3:

Configuration:
  match: ~ /(appsuite/api|ajax)/
  rewrite: /servlets/$1/
Request:
  /appsuite/api/chronos/accounts
Rewrite result:
  /servlet/appsuite/api/chronos/accounts

It is possible to reference capture groups of regex matches in the rewrite configuration as well. This has to be done by using $ followed by a 1-based index ($1, $2, $3, …).

Redirect

The redirect action redirects any incoming request to the configured location and provides a corresponding status code. If the location is missing, a 500 server error will be returned. It’s also possible to specify placeholders in the location like $scheme, $host, $port, $path and $query.

Configuration

rules:
  - path: /
    action:
      type: redirect
      status: 302
      location: https://$host$path$query
status

Optional: The status code of the response (default value 301 Moved Permanently)

location

Mandatory: The location where the request should be redirected to.

Conditional

A conditional action matches a list of conditions against the request and applies the first match. If no match could be found, a default action can be configured.

Configuration

- path: /
    action:
      type: conditional
      defaultAction:
        type: redirect
        location: /appsuite/
      conditions:
        - type: user-agent
          match: anyOf
          values:
            - Calendar
            - Reminders
            - DataAccess
            - DAVKit
            - Lightning
          action:
            type: redirect
            location: https://dav.example.com/
conditions

Mandatory: The conditions that should be checked with the actions that should be applied on a match

defaultAction

Mandatory: The default action that should be used if no other action can be applied

Conditions

Conditions have a distinct set of attributes that help to identify the condition and the matching algorithm. The properties are:

type

Mandatory: The type of the condition handler that should be applied. Possible values: user-agent.

match

Mandatory: The matching algorithm that should be applied. Regular expression to match against. Expressions starting with an exclamation mark (!) are negated. Possible match value keys are: value: Literal value (regex) to match against, anyOf: List of regular expressions, evaluated in the specified order. Any matching value fulfils the condition., allOf: List of regular expressions, evaluated in the specified order. All values must match to fulfil the condition.

values

Mandatory: A list of values for the matcher

action

Mandatory: An action that should be applied. Can be any of the known actions, another conditional action included.

User agent condition

Checks the value of the requests user-agent header and applies the configured action.

WebSocket Tunnel

The ws-tunnel action actually forwards an incoming HTTP request to an upstream/origin server but unlike the forward action this action enables proper handshake handling and establish a tunneled connection between client and server after successful handshake roundtrip.

Configuration

- path: /socket.io
  action:
    type: ws-tunnel
      backendPool: oxcluster
      clientConfig:
        readTimeout: 60000
- path: /appsuite/rt2
  action:
    type: ws-tunnel
      rewritePath: /rt2
      backendPool: oxcluster
      clientConfig:
        readTimeout: 60000
backendPool

Mandatory: Define backend pool to forward the request to. Values are taken from name values found under the global or shards section in backends.yml

rewritePath

Optional: Rewrite request path to specified value before forwarding the request to an origin server.

clientConfig

Optional: Additional configuration parameters that can be applied to the client that handles the forwarding. For additional information see client configuration section. After successful establish a WebSocket tunnel the settings did not have any affect any more expect the readTimeout. After exceeding this value without any read operation proxy will terminate the connection. If read timeout is set to 0 proxy hold the connection open until client or origin terminate the connection.

Restrictions

It is possible to limit the access to paths of a vhost for requests that match certain restrictions. A restriction can either allow or deny to handle a request if matching a certain criteria.

IP-based access restrictions

Restrict access by client IP.

- path: '/webservices'
  action:
    ...
  restrictions:
    # Possible restriction types:
    # client-ip: Client IP address (from client socket, PROXY protocol or X-Forwarded-For header)
    - type: 'client-ip'
      # (ALLOW, DENY|DENY, ALLOW)
      order: 'ALLOW, DENY'
      # List of IP addresses and subnets in CIDR notation. '*' matches any IP.
      allowFrom:
        - '192.168.0.0/24'
        - '127.0.0.1'
        - '::1'
        - 'fd35:8e34:80d5:5fc6::/64'
      denyFrom:
        - '*'

The order determines how the specified IP addresses are to be checked. In this example, the allowed IP addresses are checked first and afterwards the denied addresses. An asterisk represents all addresses, which in this example means that all addresses that are not explicitly allowed are rejected. In a path without restrictions, all IP addresses are allowed and none are rejected. The check is terminated as soon as a match is found.

By specifying the subnet mask (CIDR), it is also possible to filter by individual subnets. If this is omitted, only the specified IP address will be filtered.

For a detailed explanation review the developers guide on IP-based access restrictions

Response Headers

It is possible to alter upstream response headers before sending them back to the client. Therefor headers can be added to configuration on vHost and / or rule level. In case of any ambiguous naming the rule level configuration takes precedence over vHost level configuration.

Whether changes to response headers take place or not depends on the HTTP response status and the override flag always. If this flag is set to true changes will always be applied, otherwise (set to false or even empty) changes will only be applied if response status is one of 200, 201, 204, 206, 301, 302, 303, 304, 307 or 308.

The concrete change which would be applied to origin response headers can be defined by a type attribute:

  • add -> would simply add the configured header no matter which ones already exists on original response

  • set -> would first remove all headers with the given name from origin and then add a new single header with the configured value

  • remove -> would remove all headers with the given name. In case a value is configured only headers with the exact name and value will be removed from origin response

If no value is configured the default add will be used.

Example with further comments

vhosts:
  - hostNames:
      - 'webmail.example.com'
    responseHeaders:
      - name: 'Access-Control-Allow-Origin'
        # value is mandatory for types 'add' and 'set' and optional for 'remove'
        value: 'wss://webmail-ws.example.com' # mandatory for types 'add' and 'set'
        # type values: add [default], set, remove
        type: 'add'
        # default: false
        always: true
    rules:
      - path: /
        responseHeaders:
          - name: 'Content-Security-Policy'
            value: 'connect-src \self\ wss://webmail-ws.example.com'
            type: 'set'
            always: true

Full Example

# List of virtual hosts to initially match requests against. Order does not matter.
vhosts:
  # Accept any to webmail.example.com on port 80
  - hostAddress: "*"
    port: 80
    hostNames:
      - webmail.example.com
    rules:
      # Redirect any request immediately to the respective HTTPS location
      - path: /
        # Respond with '301 Moved Permanently' and an according Location header
        action:
          type: redirect
          # Value of the Location response header
          location: https://webmail.example.com
  # Route any HTTPS request to webmail.example.com
  - hostAddress: "*"
    port: 443
    hostNames:
      - webmail.example.com
    # Response headers on vHost level
    responseHeaders:
      - name: 'Access-Control-Allow-Origin'
        # value is mandatory for types 'add' and 'set' and optional for 'remove'
        value: 'wss://webmail-ws.example.com' # mandatory for types 'add' and 'set'
        # type values: add [default], set, remove
        type: 'add'
        # default: false
        always: true
    # The brand ID
    brand: example.com.oxcloud.net
    # Set of proxy rules to match requests for this host against. Order does not matter.
    rules:
      - path: /
        # Response headers on rule level
        responseHeaders:
          - name: 'Content-Security-Policy'
            value: 'connect-src \self\ wss://webmail-ws.example.com'
            type: 'set'
            always: true
        # Conditionally matches a list of conditions against the request.
        # The first matching condition determines the actual action.
        action:
          type: conditional
          # Mandatory default action for conditional rules. Will be applied, if no condition matches.
          defaultAction:
            type: redirect
            location: /appsuite/
          # The list of conditions to match against. Matching occurs in the specified order.
          conditions:
            # match: <request-property>
            # Possible request properties are:
            #  - user-agent: User-Agent request header
            - type: user-agent
              # Regular expression to match against. Expressions starting with an exclamation mark (!) are negated.
              # Possible match value keys are:
              # value: Literal value (regex) to match against
              # anyOf: List of regular expressions, evaluated in the specified order.
              #        Any matching value fulfils the condition.
              # allOf: List of regular expressions, evaluated in the specified order.
              #        All values must match to fulfil the condition.
              match: anyOf
              values:
                - Calendar
                - Reminders
                - DataAccess
                - DAVKit
                - Lightning
                - Adresboek
                - dataaccessd
                - Preferences
                - Adressbuch
                - AddressBook
                - Address\ Book
                - CalendarStore
                - CalendarAgent
                - accountsd
                - eM Client
                - OX Sync
                - CalDav
                - CoreDAV
              action:
                type: redirect
                location: https://dav.example.com$path$query
      - path: /appsuite/api
        action:
          type: forward
          # Rewrite request path to specified value before forwarding the request to an origin server.
          rewritePath: /ajax
          # The shard detectors to use to determine the route for the forwarded request. Values are keys
          # of the sharding.detectors section.
          shardDetector: httpApi
          shardBootstrapHandler: httpApi
          # Define backend pool to forward the request to
          backendPool: appsuite-mw
      - path: /appsuite
        action:
          type: forward
          shardDetector: httpApi
          shardBootstrapHandler: httpApi
          backendPool: appsuite-ui
      - path: /Microsoft-Server-ActiveSync
        action:
          type: forward
          shardDetector: httpApi
          shardBootstrapHandler: eas
          backendPool: appsuite-mw
          clientConfig:
            # Override read timeout in milliseconds
            readTimeout: 1900000
      - path: /webservices
        action:
          type: forward
          shardDetector: provisioning
          shardBootstrapHandler: provisioning
          backendPool: appsuite-mw
        # Restrict endpoint access. The list of restrictions is evaluated in the specified order.
        # The first match determines the result and ends processing.
        restrictions:
          # Possible restriction types:
          # client-ip: Client IP address (from client socket or X-Forwarded-For header)
          - type: client-ip
            order: ALLOW, DENY
            # List of IP addresses and subnets in CIDR notation. '*' matches any IP.
            allowFrom:
              - 192.168.0.0/24
              - 127.0.0.1
              - ::1
              - fd35:8e34:80d5:5fc6::/64
            denyFrom:
              - "*"
      - path: /servlet/dav
        action:
          type: forward
          shardDetector: webdav
          shardBootstrapHandler: webdav
          backendPool: appsuite-mw
  - hostAddress: "*"
    hostNames:
      -  dav.example.com
    brand: default
    rules:
      - path: /
        action:
          type: forward
          rewritePath: /servlet/dav
          shardDetector: webdav
          shardBootstrapHandler: webdav
          backendPool: appsuite-mw
      - path: /servlet/dav
        action:
          type: forward
          rewritePath: /servlet/dav
          shardDetector: webdav
          shardBootstrapHandler: webdav
          backendPool: appsuite-mw
      - path: /.well-known/caldav
        action:
          type: redirect
          location: /
      - path: /.well-known/carddav
        action:
          type: redirect
          location: /