16 Extending ORDS Functionality with Plugins

This chapter explains and provides examples on using ORDS plugin framework.

ORDS has a plugin framework that allows you to add your own custom functionality into the ORDS web application. Plugins can be added to the ORDS runtime by placing the jar files in the lib/ext directory. The ORDS distribution contains the source for example plugins. The plugin examples can be built using Apache ant, a software tool used for automating the build processes.

16.1 Plugin Programming Model

This section explains how ORDS plugin framework is designed to enable developers to extend ORDS functionality.

Developers can customize ORDS to meet specific business needs and requirements using the plugin framework. To leverage the ORDS plugin framework, developers must have experience with Java development and familiar with Java SE and Java EE APIs. When you choose to embed one of the GraalVM-supported polyglot languages, the required lnguage components must be installed and configured. This section briefly demonstrates a JavaScript plugin example.

16.1.1 Plugin API Objectives

This section lists the ORDS plugin framework objectives.

The ORDS plugin framework was designed to meet the following objectives:

  • Minimal learning curve: Build on existing Java SE, Java EE APIs and programming models
  • Low Coupling: Minimizes external code dependencies, requiring only the following dependencies:
    • Java SE
    • Java EE Servlet and javax.inject
    • Minimal ORDS glue code

The ORDS plugin framework follows these principles by leveraging the Java EE Servlet API, the JSR-330 Dependency Injection Framework, and a limited set of custom annotations that enable HttpServlets to be seamlessly integrated as plugins. These capabilities enable developers to extend ORDS without the need to edit web.xml deployment descriptor, while also providing greater flexibility and customization options.

16.1.2 Extension Points

This section describes how the plugins can implement additional logic.

Plugins can extend application functionality through the following extension point objects:

  • Extend the HttpServlet class to process the incoming HTTP requests
  • Implement the servlet Filter interface to intercept and modify requests or responses.
  • Implement the Lifecycle interface to respond to application startup and shutdown events.
  • Instantiate ConfigurationSetting to introduce new settings to alter the behavior of the plugins
  • Implement the CommandProvider interface to add custom commands accessible from the command line.

We refer to these extension points as services. A service is simply any type that another type depends on. The ExtensionPoints type lists the available extension points.

16.1.3 Plugin Provider

This section describes how plugins declare and provide new extension points.

A provider refers to any Java type annotated with the @Provides annotation. A provider supplies one or more services. For detailed information about the services offered by a provider, refer to the @Provides documentation.

16.1.4 Provider Lifecycle

This section describes the provider lifecycles.

Providers can have one of the following two lifecycles:
  • @RequestScoped: instantiated at the start of an HTTP request and destroyed after the request is processed.
  • @ApplicationScoped: Instantiated at the startup of ORDS when the application server starts and terminated upon stopping or restarting ORDS when the server stops or restarts ORDS.

By default all Java types annotated with @Provides are assigned a @RequestScoped life-cycle.

Request Type @RequestScoped Services @ApplicationScoped Services
Threading Exist in a single thread Can be accessed from multiple threads, needs to be fully thread safe.
Instantiation Are instantiated and destroyed very frequently thus instantiation/destruction must be efficient Are instantiated and destroyed very occasionally. Endures ORDS application lifecycle
Can depend on Other @RequestScoped services and @ApplicationScoped services Other @ApplicationScoped services but not on @RequestScoped services
Additional considerations Can safely hold per request state in member variables. Lifecycle providers always have an @ApplicationScoped lifecycle.
16.1.4.1 Best Practices

This section lists the best practices.

Following are some best practices:

Avoid using @ApplicationScoped services unless it is absolutely necessary to share state across multiple requests.

Following are some suitable scenarios for @ApplicationScoped services:
  • Caching data that must be shared among multiple requests. Caching can reduce the cost of certain transactions and operations, and in some cases, the performance benefits outweigh the complexity of thread synchronization and the threat of invalid cache states.
  • Tracking global metrics such as the total number of requests served or the total number of bytes served.
Following are some unsuitable use cases for @ApplicationScoped services:
  • Caching data simply because you believe recomputing it is expensive.
  • Preferring a single instance of a service because creating one per request is inefficient or wasteful.

Instead of sharing state across threads, store global state in backend storage systems such as databases, which offer reliable concurrent access. This approach avoids the risks associated with shared state across threads.

16.1.5 Service Provider Prioritization

This section describes the prioritization of service provider.

When there are multiple providers for a service, the @Priority annotation is provided to assist in choosing the most appropriate provider, or to determine the order in which the providers should be invoked.

16.1.6 Dependency Injection

The runtime leverages the standard dependency injection pattern to specify the services that a provider depends on. It includes a JSR-330 compatible dependency injection framework. A provider declares its dependencies in its constructor, which must be annotated with @Inject.

Example 16-1 Dependency Injection Example

@Provides /* Advertise the Foo provider to the D.I. framework */
class Foo {
 @Inject Foo(Bar bar) { /* Express the dependency on the Bar service */
  this.bar = bar;
 }
 
 public void someMethod() {
  bar.use(); /* use the dependency */
 }
 private final Bar bar;
}

16.1.7 AvailableDependencies

The set of dependencies made available to plugins is enumerated by the AvailableDependencies type.

16.2 Servlet Extensions

This section explains how to create an extension to handle the HTTP requests.

To create an extension that handles the HTTP requests, you must create a class that extends HttpServlet.

The following sections provide more details on building servlet extensions, highlighting how Java annotations can be used to define the metadata of the servlet.

Major portion of the metadata can be specific to a particular HTTP method, a particular URI pattern or an entire servlet. You can define the specific metadata by annotating the corresponding Java method or setting a property of the @PathTemplate annotation.

16.2.1 Servlet Lifecycle

This section describes servlet lifecycle in ORDS.

Servlets in ORDS exist only for the duration of an HTTP request, unlike the servlet lifecycle in a standard JEE application server. As a result, each servlet instance is used by a single thread and does not need to manage threading concerns.

For each HTTP request, a separate instance is created once it has been identified as the target of the request, and the instance is destroyed once the request has been serviced.

16.2.1.1 About the @Dispatches Annotation

This section explains about @Dispatches annotation.

The @Dispatches annotation informs ORDS on which URL patterns the servlet is intended to handle. These patterns are termed as Route Patterns, and they have a specific syntax.

Every servlet must have exactly one @Dispatches annotation. A @Dispatches annotation must have at least one @PathTemplate annotation and each @PathTemplate annotation describes an URL pattern that the servlet is willing to service.

The Javadoc describes in detail how to use the annotation and provides several examples.

16.2.1.2 About the @PathTemplate Annotation

This section describes the @PathTemplate annotation.

The [@PathTemplate][path-template] annotation describes a single route pattern that a servlet services. In addition the @PathTemplate annotation has a number of properties that can be used to provide the metadata specific to the specified route pattern.

16.2.1.3 About PathTemplateMatch

This section describes PathTemplateMatch object.

To identify which @PathTemplate was matched, a servlet should call the PathTemplates.matchedTemplate(HttpServletRequest) method to obtain the corresponding PathTemplateMatch.

Servlet example
@Provides
@Dispatches({
 @PathTemplate(name="collection", value="/examples/collection/"),
 @PathTemplate(name="item", value="/examples/collection/:id")})
class ExampleServlet {

 protected void doGet(HttpServletRequest req,HttpServletResponse resp) {
  PathTemplateMatch matched = this.pathTemplates.matchedTemplate(req);
  switch(matched.name()) {
   case "collection":
    // process collection pattern
    break;
   case "item::
    String id = matched.parameters().get("id");
    // process item pattern
    break;
  }
 }
}

Once the PathTemplateMatch instance has been retrieved, the servlet can branch to the correct logic for handling the request, typically by examining the PathTemplateMatch#name() method. If the route pattern includes any parameters, then the value bound to the parameter can be retrieved through the PathTemplateMatch#parameters()method.

Given the route:/examples/collection/:id

It can be accessed and given an id value of 101 by making a request in one of the following ways:
  • /examples/collection/101
  • /examples/collection?id=101

16.3 Plugin Examples

This section walks you through building and deploying the plugin-demo plugin.

The plugin-demo plugin queries the database to determine the current database user and echo that information in the response. Examples using both Java and Javascript are provided as they are the only two languages currently supported for plug-in creation.

Prerequisites

  • JDK 1.8 or later
  • Apache Ant 1.8.2 or later
  • Installed and configured ORDS with a REST enabled database schema

16.3.1 Java Plugin Demonstration

This section provides the details of the Java plugin-demonstraion example.

The plugin-demonstraion example is located at examples/plugins/plugin-demo and contains the source for a HttpServlet that gets a database connection injected at runtime. The servlet uses that JDBC database connection to run a query in the database and return a response at runtime. All the files referenced are included in the product distribution under the examples/plugins folder:

${ORDS_HOME}
|-- examples
     |-- plugins
          |-- lib
          |-- plugin-demo
              |-- src

where ${ORDS_HOME} is the location from where the product distribution was unzipped.

16.3.1.1 About the plugin-demo Folder Structure

This section describes the folder structure of the plugin-demo files.

The plugin-demo files are located under ${ORDS_HOME} folder at the following location:
${ORDS_HOME}/examples/plugins/plugin-demo

The ${ORDS_HOME}/examples/plugins/plugin-demo folder contains the following:

  • src folder contains:
    • PluginDemo.java: the Java source code of the plugin
  • build.xml: ANT build script that compiles and packages the source code
  • built: folder generated from build.xml which contains the packaged plugin (plugin-demo.jar)
  • ${ORDS_HOME}/examples/plugins/lib folder contains the .jar files required to compile the plugins.
16.3.1.1.1 Required Libraries

This section describes the required libraries.

The required jar files are included in the ${ORDS_HOME}/examples/plugins/lib product distribution folder.

To compile a plugin the following libraries must be in the classpath:

  • plugin-api.jar
  • plugin-apt.jar
  • javax.inject.jar
  • servlet-api-3.1.0.jar
  • ojdbc11.jar

About plugin-api.jar

This library provides the glue code such as @Dispatches to weave the plugin into the runtime.

About plugin-apt.jar

This library provides the annotation processor that makes the classes annotated discoverable with @Provides.

About javax.inject.jar

This library provides the JSR-330 API types such as @Inject.

About servlet-api-3.1.0.jar

This library provides the Java Servlet 3.1.0 API types, such as HttpServlet.

About ojdbc11.jar

This library is optional and is only required if the plugin needs to access the Oracle JDBC Extension APIs such as OracleConnection.

16.3.1.2 About PluginDemo.java

This section shows the sample code snippet of PluginDemo.java plugin along with an explaination for the sample code.

Following is the sample code snippet for PluginDemo.java plugin:
package example;

import java.io.IOException;
import java.sql.*;
import jakarta.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import oracle.dbtools.plugin.api.di.annotations.Provides;
import oracle.dbtools.plugin.api.http.annotations.*;
import oracle.dbtools.plugin.api.routes.*;

/**
 * This example plugin {@link HttpServlet} demonstrates:
 * <ul>
 * <li>Using the injected {@link Connection} to query the database.</li>
 * <li>Using the injected {@link PathTemplates} service to decode the parameters
 * of the servlet's {@link PathTemplateMatch}.</li>
 * </ul>
 *
 * <h4>Testing the Servlet</h4> Invoke the servlet with the following URL:
 *
 * <pre>
 *  http://<i>server</i>/ords/<i>schema</i>/demos/plugin?who=<i>somebody</i>
 * </pre>
 *
 * where:
 * <ul>
 * <li><i>server</i> is the hostname and port of the server.</li>
 * <li><i>schema</i> is the name of the REST enabled database schema.</li>
 * <li><i>somebody</i> is any value you wish, e.g. a person's name.</li>
 * <ul>
 * For example:
 *
 * <pre>
 *  http://localhost:8080/ords/test_schema/demos/plugin?who=Scott
 * </pre>
 *
 * @author cdivilly
 *
 */
@Provides
@Dispatches(@PathTemplate("/demos/plugin"))
class PluginDemo extends HttpServlet {
  @Inject
  PluginDemo(Connection conn, PathTemplates pathTemplates) {
    this.conn = conn;
    this.pathTemplates = pathTemplates;
  }

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {

    PathTemplateMatch match = pathTemplates
                                .matchedTemplate(request);
    try {
      /* retrieve 'who' query parameter */
      String who = match.parameters().get("who");
      who = null == who ? "anonymous" : who;

      /* execute database query */
      PreparedStatement ps = conn
       .prepareStatement("select sys_context('USERENV','CURRENT_USER') from dual");

      ResultSet rs = ps.executeQuery();
      rs.next();

      /* determine the database user */
      String user = rs.getString(1);

      /* Print the greeting */
      response.getWriter().println(user + " says hello to: " + who);
      rs.close();
      ps.close();
    } catch (SQLException e) {
      throw new ServletException(e);
    }
  }

  private final Connection    conn;
  private final PathTemplates pathTemplates;
}

Following is an explaination for the preceding code snippet:

  • The PluginDemo class is annotated with:
    • @Provides annotation, signifying that offers a service, in this scenario, the HttpServlet service.

    • @Dispatches and @PathTemplate annotations, which defines the URL pattern that the servlet responds to.

  • The PluginDemo constructor is annotated with @Inject, indicating that it should be used by the dependency injection framework that this constructor should be used to create instances of the class.

  • The parameters of the constructor specify the following dependencies:
    • The first parameter indicates that a database connection is required, ORDS assigns this connection to a specific database schema, according to the mapping rules of the request URL.
    • The second parameter is the PathTemplates service, which enables the servlet to retrieve the specific PathTemplate associated with the current request.
  • The PluginDemo:
    • Overrides the doGet() method to indicate that it supports the GET HTTP method.

    • Uses the PathTemplates service to determine the PathTemplateMatch bound to the request.
    • Then decodes the template parameters, extracting the value of the who parameter.
    • Connection instance is queried to determine the identity of the current database user.

      In response a message is displayed to indicate that the database user and the value of the who parameter is printed in the response.

16.3.1.3 Building the Plugin

This section explains how to build the plugin.

In the plugin-demo folder type the following command:
$ ant
The source code is compiled and packaged into an archive named built/plugin-demo.jar.
16.3.1.4 Packaging the Plugin

This section explains how to package the plugin.

To package the plugin, copy the plugin-demo.jar to the extension library:
$  cp built/plugin-demo.jar ${ORDS_HOME}/lib/ext/
16.3.1.5 Testing the Plugin

This section explains how to test the plugin.

To test the plugin:

Start ORDS in a standalone mode:
.$ cd ${ORDS_HOME}/bin
.$ cd ${ORDS_HOME}/bin
$ ords --config config_dir serve

16.3.2 Analyzing the Request URLs

This section explains how ORDS analyzes and maps the request URL to the database pool and schema.

The plugin we have developed requires a database connection to operate. Therefore, ORDS must determine the correct database and schema to connect to. ORDS accomplishes this, by analyzing the request URL and then mapping it to the corresponding database pool and schema.

If ORDS cannot determine a mapping, then it returns a 404 Not Found status, for the request URL.

16.3.3 Trying the Request URL

To try the request URL:

ORDS must be running in a standalone mode on localhost, try the following URL:
http://localhost:8080/ords/hr/demos/plugin?who=Scott
The browser should display the following text:
hr says hello to: Scott
  • The /hr portion of the request URL maps the request to the hrdatabase schema.
  • A Connection instance connected to the hr schema is injected into the PluginDemo instance.
  • PluginDemo queries the connection to determine the current user and decodes the who parameter bound to the request URL, and uses this information to construct the message displayed.

16.3.4 Java Examples

The following sections lists some Java examples.

16.3.4.1 Hello World Example

This section shows the Hello World example.

The following Hello World example demonstrates the basics of creating a request handler plugin:

HelloWorld.java
@Dispatches(@PathTemplate("/hello"))
@Provides                            
public class HelloWorld extends HttpServlet {
 public doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
 	response.setContentType("text/plain");
 	response.getWriter().println("Hello World");
 }
}
Follwing is an explaination for the preceding Hello World example:
  • Create a class that sub-classes HttpServlet.
  • Advertise the class to the Dependency Injection framework through the @Provides annotation
  • Advertise the class to the request dispatching framework through the @Dispatches annotation
  • Advertise the request path that the class responds to through the @PathTemplate annotation
  • Override the HttpServlet doGet () method to provide the logic of the handler.
16.3.4.2 Injecting Dependencies

This section provides a sample code snippet showing how plugin can specify its dependencies on external APIs.

A plugin can specify its dependencies on external APIs using the @Inject annotation on its constructor.

UsesLogging.java
@Dispatches(@PathTemplate("/uses-logging"))
@Provides                            
public class UsesLogging extends HttpServlet {

 @Inject
 public UsesLogging(Logger log) {
 	this.log = log;
 }   
 public doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
    log.fine("received request:\n" + request.toString());
 	response.setContentType("text/plain");
 	response.getWriter().println("Hello World");
 	log.fine("processed request");
 }

 private final Logger log;
Following is an explaination for the preceding code snippet:
  • Before instantiating the UsesLogging type, the DI framework looks for a constructor annotated with @Inject. It then examines the arguments of the constructor and attempts to resolve an implementation of the specified type. Once all dependencies have been resolved, the DI framework invokes the constructor with the required arguments.
  • In this case, the servlet uses the Logger service to log some debugging information.
  • The set of services that can be injected is documented in the AvailableDependencies enum.

16.3.5 Javascript Plugin Demonstration

This section describes how to create a plugin using Javascript.

16.3.5.1 Plugin Javascript

ORDS provides a JavaScript as a service framework for customers to define a JavaScript that can be executed in the ORDS instance on request. This is similar to the conventional RESTful services concept used to develop the applications. The framework is based on the module, template, and handler architecture. See Developing Oracle REST Data Services Applications. Rather than defining the modules, templates, and handlers in the database, they are specified in an XML representation that is read from lib/ext/ directory as a plugin.

The ORDS examples directory contains a plugin-javascript example and the source can be found in the examples/plugins/plugin-javascript directory. This section describes the key elements of the plugin.

Note:

GraalVM with JS component is required for JavaScript plugin ORDS feature to work.
GraalVM with JS component is required for this ORDS feature to work. See GraalVM Configuration for more information.

The example contains a number of inline and external definitions for JavaScript source. References to external JavaScript source are to the files that are found in the classpath.

File Description
build.xml The ant build project.
src/js/example.js An example external JavaScript file. External here means, not defined in, but referred to from, the XML Resource Module file.
src/META-INF/manifest.json A plugin configuration metadata file that ORDS reads at startup to register XML Resource Modules.
src/META-ING/modules/javascript.xml An XML Resource Module file that defines an example module with a number of templates and handlers.
Perform the following steps to build and use the example:
  1. Change the directory to examples/plugins/plugin-javascript.
  2. Run ant to build examples/plugins/plugin-javascript/built/plugin-javascript.jar file.
  3. Copy the plugin-javascript.jar file to the ORDS distribution lib/ext directory and start the ORDS instance using a supported GraalVM with JS component.
  4. Invoke the defined handlers using the URL pattern: http://server/ords/javascript-examples/{template pattern}.
    1. For example: http://localhost:8080/ords/javascript-examples/now where the current time is returned.

      Note:

      Unlike the ORDS REST Services, the JavaScript as a service implementation does not require or use a database connection.
16.3.5.1.1 Example Services Purpose and Use

This section provides the information on the purpose and use of the example services.

Purpose Request Action Response
An example of inline Javascript that returns the current UTC time as application/json. /ords/javascript-examples/now GET { "now":"2023-08-31T16:08:55.471Z" }
An example of inline Javascript that accepts a parameter. /ords/javascript-examples/future?days=7 GET { "now":"2023-08-31T16:08:55.471Z", "future":"2023-09-07T16:08:55.471Z", "days":7 }
An example of inline Javascript that accepts various parameters from different sources.
/ords/javascript-examples/hello?name=Ted

curl --location 'ords/javascript-examples/hello' \
--header 'Agent: Test'
GET
Hello Ted
Hello Test
An example of external Javascript file that accepts a parameter. /ords/javascript-examples/fibonacci?length=50 GET {fib: 12586269025}
An example of inline Javascript that uses implicit parameters content_type and body_text for getting the request values as well as using ords_response to invoke setStatus and setContentType on HttpServletResponse.
curl --location '/ords/hr/javascript-examples/countwords' \
--header 'Content-Type: application/json' \
--data '{"text": "How many words are here?"}'
POST
{"text": "How many words are here?","count": 5}
16.3.5.1.2 Embedding Graal JavaScript Component

The JavaScript component must be embedded as a plugin to be able to run JavaScript as a guest language in ORDS that is running in GraalVM for JDK version 21.

The following are the artifacts required to embed JavaScript:
  • GraalVM Polyglot API
  • JavaScript language
The following is a sample code snippet that demonstrates Maven dependency setup that can help you get the required dependencies:
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>polyglot</artifactId>
    <version>${graalvm.version}</version>
</dependency>
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <!-- Language: js -->
    <artifactId>js</artifactId>
    <version>${graalvm.version}</version>
    <type>pom</type>
</dependency>

Refer to section, Embedding Languages in the GraalVM reference manual for more information about dependency setup to embed languages. Once the required artifacts have be dowloaded, place them in lib/ext/ directory to be included in the classpath at runtime.

16.4 Route Patterns

A Route Pattern defines a format used to match specific HTTP request paths. The pattern is matched against the path component of the request URI.

16.4.1 Example

This section provides an example for route pattern.

/objects/:object/:id?
This route pattern matches the following paths:
  • /objects/emp/101: Matches a request for the item in the emp resource with id 101.
  • /objects/emp/: Matches a request for the emp resource, because the :id parameter is annotated with the ? modifier which indicates that the id parameter is optional.

16.4.2 Purpose of Route Patterns

This section is intended to clarify the purpose of the route pattern syntax. It serves as a recommended guideline rather than a standard.

The syntax of route patterns is similar to and is inspired by the pattern routing syntax found in a number of web frameworks, including:

  • Angular Routes
  • Ruby on Rails Routing

Route patterns address the need to create a formal definition of the ad-hoc pattern syntax that these and similar frameworks have popularised.

The goal of Route Patterns is to ensure that it is not possible to define a suite of route patterns that are ambiguous, for any given request path only one or zero route patterns can be chosen to match against the path. As a result, the route pattern syntax may be less flexible or expressive than the ad-hoc syntaxes used in the frameworks.

This is a conscious design trade-off. In the ad-hoc syntaxes, any ambiguity is resolved by the order in which the patterns are declared, the first declared pattern is tested first, the second declared pattern is tested second and so on. Developers can order the pattern declarations to ensure that more specific patterns are tested before less specific patterns. This requires one central code location where routes are declared and requires careful ordering of the patterns to avoid errors. These requirements may not scale to larger applications where many developers are defining route patterns, and may not be fully aware of conflicting or overlapping route patterns, or to the applications where route patterns need to be defined in many different locations (for example: In a pluggable architecture).

The route pattern syntax is also somewhat similar to the URI template syntax, but the applications of URI templates and route patterns differ. URI templates focus on forming concrete URIs from a template, Route Patterns focus on decomposing the path portion of a URI into its component parts.

16.5 Route Pattern Syntax Rules

This section describes the route pattern syntax rules.

A route pattern is a string of printable unicode characters that contains zero or more embedded variable expressions. An expression can be a named parameter, delimited by a leading colon (:) and a trailing slash (/), or end of string, or an expression can be a glob parameter indicated by the wildcard character (*). A pattern that contains one or more named parameters is termed as a named pattern. A pattern that contains a glob parameter is termed as a glob pattern. A pattern must not contain a mixture of named patterns and glob expressions. A pattern lacking any variable expressions is termed as a literal pattern.

16.5.1 Path Separator

This section describes the path separator.

The slash (/) character delimits the pattern into path segments. A path separator must not be followed by another path separator. The leading path separator in a route pattern is implied and can be omitted.

Examples

  • The patterns a/b and /a/b are equivalent
  • The patterns * and /* are equivalent
  • The patterns a/b and a/b/ are not equivalent, the trailing path separator is significant and cannot be ignored

16.5.2 Reserved Characters

This section describes the reserved characters.

The set of reserved characters are those defined in RFC 3986 Section 2.2 .
  • reserved = gen-delims / sub-delims
  • gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
  • sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="

16.5.3 Literal Values

The characters outside of expressions and path separators in a route pattern are termed as literal values.

Literal values can contain any printable unicode character except the reserved characters.

16.5.4 Named Parameters

This section describes the named parameters.

The start of a named parameter is indicated by the colon character (‘:’). The end of a named parameter is indicated by a path separator or the end of string. The named pattern can be suffixed with a modifier. A given parameter name must only appear once in each route pattern. A route pattern can have zero or more named patterns.

Example:

named-expression-pattern = *(literal / path-separator / named-expression )

valid-name = [a-zA-Z0-9] / '-' / '_' 
char = [a-zA-Z]
name = char valid-name*
param-decl = name ('*' / '?' )

named-expression = ':' param-decl path-separator /
                   ':' param-decl <eos>
16.5.4.1 Modifiers

This section describes the modifiers.

A modifier modifies the matching behavior of a named parameter. Only a single named parameter in a route pattern can contain a modifier and it must be the last named parameter in the pattern. A modifier is suffixed at the end of a named parameter expression.

16.5.4.1.1 Eager Modifier

This section describes the eager modifier.

The eager modifier is indicated by the asterisk character (‘*’) and instructs the matcher to eagerly consume all characters matching the named pattern including the path separator character through the end of the string.

Example

/foo/:all-children*

This pattern matches the following paths:

  • /foo/bar : all-children is associated to bar
  • /foo/bar/ : all-children is associated to bar, the eager modifier consumes all characters including the path separator
  • /foo/bar/baz : all-children is associated to bar/baz, the eager modifier consumes all characters to the end of the string

The eager modifier must match at least one character, so the preceding pattern does not match the following path:

/foo/: matching this path would require all-children to be associated to the empty string, which is not permitted.

16.5.4.1.2 Optional Modifier

This section describes the optional modifier.

The optional modifier is indicated by the question mark character (?) and instructs the pattern matcher that the Named Pattern matches zero or more characters until the end of string is reached.

Example

/foo/:item?

This pattern matches the following paths:

  • /foo/bar: item is associated to bar
  • /foo/: item is associated to the empty string, the optional modifier causes the named parameter to match the zero length string
16.5.4.1.3 Compound Named Parameter

This section describes the compound named parameter.

A compound named parameter is a named parameter where the matching text in the request path is decomposed into named components. Each component is delimited by the comma character (,). A compound named parameter can have an optional modifier, but must not have an eager modifier.

Example:

/line-items/:order_id,item_id/detail
16.5.4.1.4 Glob Parameter

This section describes glob parameter.

A glob parameter is denoted by the wildcard modifier (‘*’ character). The wildcard modifier must appear at the end of the pattern and must be preceded by the path separator. Only a single glob parameter is permitted in a pattern. A glob parameter must not occur in the same pattern as a named parameter.
glob-pattern = *(literal / path-separator /  ) / path-separator '*'
A glob parameter matches zero or more characters until the end of the string.
Examples:
  • /*: Matches all paths
  • /foo/*: Matches all paths starting with /foo/ including the /foo/ path.

16.6 Pattern Matching Rules

This section describes the matching rules of the route pattern.

A route pattern is composed of the following tokens:
  • Path Separator
  • Literal Value
  • Named Parameter
  • Named Compound Parameter
  • Glob Parameter

A route pattern is matched against the URL-encoded form of a request path, with each token matching its corresponding segment of the request path.

The tokens are matched from left to right order, the first token matching the left-most segment of the request path, the second token matching the next left most segment and so on. The rules for matching each token type are defined in the following sections.

16.6.1 Path Separator Matching

This section describes the path separator matching rule.

Each path separator token must match exactly one ‘/’ character in the request path. A Path separator must not match the URL encoded form of the ‘/’ character. That is, it must not match the octets: %2F or the octets: %2f. Since the leading path separator in a route pattern is optional, the leading path separator in a request path is also optional and can be omitted.

Examples:

  • The pattern /a/b matches the request paths: a/b and /a/b
  • The equivalent pattern a/b also matches a/b and /a/b
  • The pattern /a/b does not match the request paths: a%2Fb or %2fa%2Fb

16.6.2 Literal Value Matching

This section describes the literal value matching rule.

Each literal value token must match the exact same characters in the request path. Each literal value must be an URL encoded and compared to the URL encoded request path.

Examples

The pattern a/b matches the following request paths:
  • a/b
  • /a/b
  • /%61/%62 – ‘%61’ is the percent encoded form of the ‘a’ character, ‘%62’ is the percent encoded form of the ‘b’ character.

16.6.3 Named Parameter Matching

This section describes the named parameter matching,

A named parameter token matches one or more characters until the next occurrence of a path separator or end of the string.

Optional Modifier Matching

If a named parameter has an optional modifier, then it matches zero or more characters up to the end of the string.

Eager Modifier Matching

If a named parameter has an eager modifier, then it matches all characters up to the end of the string.

Examples

The pattern /test/:item matches the following paths:

  • test/101
  • /test/true%2Ffalse
  • /test/a,b,c

The pattern does not match the following paths:

  • /test/101/: extra trailing slash
  • /test/: named parameter must match at least one character

16.6.4 Compound Named Parameter Matching

This section describes the compound named parameter rules.

A compound named parameter token matches one or more characters up to the next occurrence of a path separator or end of string, where the matched characters are further delimited by the comma (‘,’) character. If the compound named parameters have N components, then there must be at most N-1 commas in the matched text. If there are more than N-1 comma characters (that is, N+1), then there must not be a match. Trailing comma characters can be omitted in the matched request path.

Component values in the request path that must contain the comma character must use the percent encoded form of the comma character (%2C)

16.6.4.1 Optional Modifier Matching

This section describes optional modifier matching

If a compound named parameter has an optional modifier, then it matches zero or more characters up to the end of the string.

Examples

  • The pattern /line-items/:order_id,item_id/detail matches the following paths:

    • /line-items/101,493/detail – order_id is bound to 101, item_id is bound to 493
    • /line-items/101,/detail– order_id is bound to 101, item_id is bound to null
    • /line-items/,493/detail – order_id is bound to null, item_id is bound to 493
    • /line-items/,/detail – order_id is bound to null, item_id is bound to null
  • The pattern /line-items/:order_id, item_id, category_id/detail/category matches the following paths:

    • /line-items/101,493,14/detail/category – order_id is bound to 101, item_id is bound to 493, category is bound to 14
    • /line-items/101,/detail/category – order_id is bound to 101, item_id is bound to null, category is bound to null
    • /line-items/,493/detail/category – order_id is bound to null, item_id is bound to 493, category is bound to null
    • /line-items/,,493/detail/category – order_id is bound to null, item_id is bound to null, category is bound to 493
    • /line-items/,/detail/category – order_id is bound to null, item_id is bound to null, category is bound to null
    • Trailing comma separators may be omitted so the following path is also matched :

    • /line-items/101/detail, order_id is bound to 101, item_id is bound to null
If a component value contains the comma character, it must be percent encoded in the request path, for example given the pattern /books/title,author, then:
  • /books/So%20Long%2C%20and%20Thanks%20for%20All%20the%20Fish,Douglas%20Adams matches, the comma character ias it is percent encoded
  • /books/Eats,%20Shoots%20%26%20Leaves,Lynne%20Truss there are two comma characters in the matched range, since only one comma character was expected, the match fails.

16.6.5 Glob Parameter Matching

This section describes the glob parameter matching token.

A glob parameter token matches zero or more characters up to the end of the string.

Example

The pattern /foo/* matches the following paths:

  • /foo/ matches the empty string
  • /foo/bar matches bar
  • /foo/bar/ matches bar/
  • /foo/bar/baz matches bar/baz

16.7 Route Pattern Sets

A collection of route patterns is termed a route pattern set.

A route attern Set must be unambiguous, meaning that for a given request path it should be possible to choose at most one route pattern from the set to match the request path. route patterns must be ordered within the route pattern Set from most specific pattern to least specific pattern. Matching a request path against a route pattern set must be performed in order from the most specific pattern to the least specific pattern. Matching stops at the first route pattern that matches.

16.7.1 Equivalent and Overlapping Patterns

This section describes the equvalent and overlapping patterns.

Equivalent or overlapping route patterns must not occur in the same route pattern Set.

Equivalent Patterns

Named patterns are equivalent if the only difference between the patterns is the names assigned to parameters.

Example

The following two patterns are not permitted in the same route pattern set because the only difference is the name assigned to the named parameter:
  • /a/:b/
  • a/:c
Both named patterns match the same set of request paths, creating ambiguity about which pattern should be selected for a given request path.

Overlapping Patterns

Overlapping patterns are route patterns where, for a subset of request paths, more than one route pattern matches, and the token precedence ordering described below does not resolve which route pattern should be selected.

Overlapping Modifiers

A route pattern set must not contain two or more named patterns that differ only in the use of a modifier.

Example

The following three patterns are not permitted in the same route pattern set because the only difference is the modifier assigned to the named parameter:

  • /a/:b
  • /a/:b?
  • /a/:b*

Overlapping Literal and Glob Pattern

An optional named pattern must not overlap with a literal pattern within the same route pattern set.

Example

Following is the route pattern set:

  • /a
  • /b
  • /c/d
  • /c/d/a/1
  • /a/b/c/d/e/
The expected ordering of this set is:
  • /c/d/a/1
  • /c/d
  • /b
  • /a/b/c/d/e/
  • /a

16.7.2 Token Precedence

This section describes an approach to determine the precedence order of the route patterns.

The different token types are assigned a precedence order, from most specific to least specific, to enable a deterministic sorting of a route pattern set.

Literal Values and Path Separators

Literal values and path separators have the highest precedence because they require an exact match. Literal values are ordered in reverse lexicographical order, ensuring that longer literal tokens are tested before the shorter ones.

Compound Named Parameters

A compound named parameter has second highest precedence, as the requirement to match the comma characters within the matching value makes it more specific than a named parameter.

Optional Compound Name Parameters

An optional compound named parameter has third highest precedence, it is less specific than a compound named parameter because it can match an empty string.

Named Parameters

A named parameter has fourth highest precedence, matching one or more characters until the next path separator or end of the string.

Optional Named Parameters

An optional named parameter has the fifth highest precedence and matches zero or more characters, excluding the path separator, up to the end of the string.

Eager Named Parameters

An eager named parameter has the sixth highest precedence and matches one or more characters, including the path separator, up to the end of the string.

Glob Parameters

Glob parameters have the lowest precedence, as they are the least specific patterns and match zero or more characters up to the end of the string.

Examples

Given the following Route Pattern Set:

  • /*
  • /foo/*
  • /a/:p1
  • /a/:p1/c
  • /:p1/b/c
  • /b/:p1?
  • /b/c/:p1*
  • /a/:p1/c/:p2
The expected order of these route patterns, from most specific to least specific, is as follows:
  • /foo/*
  • /b/c/:p1*
  • /b/:p1?
  • /a/:p1/c/:p2
  • /a/:p1/c
  • /a/:p1
  • /:p1/b/c
  • /*

Example Implementation of Ordering

Note:

This section is non-normative.

One approach for implementing the specified route pattern ordering is to convert each pattern into a canonical string representation, then sort these canonical strings in reverse lexicographical order. This is achieved through replacing each different parameter token in the pattern with a single low value character. with the lowest precedence pattern getting the lowest value character, and the highest precedence getting the highest value character as outlined in the following list:

  • Glob -> ‘!’
  • Eager Named -> ‘#’
  • Optional Named -> ‘$’
  • Named -> ‘'’
  • Optional Compound -> ‘(’
  • Compound -> ‘)’
Applying this table to the patterns in the preceding example, the canonical strings for each pattern are as follows:
  • /foo/!
  • /b/c/#
  • /b/:$
  • /a/'/c/'
  • /a/'/c
  • /a/'
  • /'/b/c
  • /!

The substitute characters are part of the reserved character set, ensuring they do not conflict with any literal tokens and thus eliminate any ambiguity between patterns.