Request dispatching patterns of Ballerina HTTP service

Chamil Elladeniya
Ballerina Swan Lake Tech Blog
8 min readSep 29, 2019

--

Since Ballerina 1.0 was released couple of weeks back, you must be excited and eager to know things around language. With the stable language syntax and semantics I thought of contributing a little by posting some stuff I learned and practiced through out the journey.

As topic describes, this story is about request dispatching patterns available in services which behave on top of HTTP transport layer. Request dispatching is more like a letter getting delivered to a house based on the home address, where the letter is the request which dispatch to a house called resource.

Ballerina dispatching logic is implemented to uniquely identify a resource based on the request URI and Method.

Photo by Ishan @seefromthesky on Unsplash

Dispatching based on URI

The service is one of the high level constructs in Ballerina. A service represents a collection of network accessible entry points. Resources represent one such entry point. More like a function which perform a specific task. This entry point (resource) is exposed over a network by depending on the Listener.

Before moving forward, Let’s understand the structure of URI a little bit. Basically generic URI syntax consists of the following sequence of components.

When considering a URI syntax according to the specification, scheme, authority and path components are compulsory in order to provide a valid URI where the query and fragment components are optional. Ballerina provides quite natural way to specify each components among listener, service and resource. Let’s get started with simple service to understanding how ballerina provides these components.

As depicted in the above sample, service foo is attached to a listener which is anonymous and the port is configured to 9090. In addition to port, user can specify host address, security, protocol version, ….etc in the listener configuration. Since the host is not specifically mentioned, it will default to local host(127.0.0.1). In fact, the listener is the construct which configures the scheme and the authority component of a URI. Authority component is basically the combination of host and port. Hence, it is obvious that above service’s authority component would be

localhost:9090

The scheme of the service can vary between HTTP and HTTPS depending on the secure socket listener config availability. If communication between service and client needs to be happened via a secure connection, then user should specify keystore under secure socket config. (How ? -> Refer HTTPS listener sample) Since the above sample service does not concede such config, scheme will also default to HTTP. Therefore both scheme and authority components are configured.

http://localhost:9090

Finally the path component needs to be configured to completed the compulsory URI syntax. For that, user may specify the basePath of the service configuration and the path of the resource configuration as depicted in the sample. So the concatenation of both the basePath and path configs will be considered as the path component of URI. All right, The path is done!!! Requests with following URI should get dispatched to the resource bar

http://localhost:9090/hello/there

When basePath and path configs are not provided specifically, both will default to service name and the resource name respectively. This is the simplest pattern of request dispatching. In that case, the URI should be changed as follows;

http://localhost:9090/foo/bar

In the failure of discovering the resource for a given URI, particular service will return an Error response with 404 Not found status code.

Dispatching based on Method

As mentioned earlier, Ballerina dispatching logic depends on HTTP method of the request in addition to the URI. Therefore matching only the request path will not be sufficient. Once the dispatcher finds a resource, it checks for the method compatibility as well. User can specify a string array of methods which is case-sensitive in the resource configuration. Above sample service has a resource specifying the method as GET. That means particular resource is constrained to be invoked only by GET requests. Anything apart from GET and OPTIONS request will not be dispatched. (Well, the OPTIONS request is capable of invoking any resource regardless of the method as it’s task is to return all the possible allowing methods) Following curl client will invoke the resource

curl http://localhost:9090/hello/there

> GET /hello/there HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain
< content-length: 16
< server: ballerina/1.0.0
<
* Connection #0 to host localhost left intact
Simple response!* Closing connection 0

In the failure of matching the resource’s methods for a given request method, particular resource will return an Error response with 405 Method not allowed status code.

There is no restriction for specify the method in the config. Since it is an array, any number of methods can be provided. Custom methods can also be configured apart from the standard ones and the dispatching logic honor such methods as well. However user should be careful about safe, idempotent methods when designing their resources to provide a fault-tolerant API.

If user has not specified a certain method in the config, then the resource will default to be called by all possible standard methods (GET, HEAD, OPTIONS, POST, PUT, DELETE, PATCH)

How does the dispatching patterns are created with Methods? Well, user can maintain a single URI and have multiple resource with different methods in a service. Following sample is an improved version with multiple resources in the service

User can invoke both getValue and checkValue resources using same URI but with different methods.

curl http://localhost:9090/foo/bar

> GET /foo/bar HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain
< content-length: 16
< server: ballerina/1.0.0
<
* Connection #0 to host localhost left intact
Simple response!* Closing connection 0

curl http://localhost:9090/foo/bar -I

> HEAD /foo/bar HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 202 Accepted
< content-length: 0
< server: ballerina/1.0.0
<
* Connection #0 to host localhost left intact
* Closing connection 0

Design your API with multiple resources

After some context about how ballerina dispatching works, it’s time to design your resources by integrating possible dispatching patterns

The best matching resource is figured out by the longest path match

When discovering the resource, the complete path will be considered when figuring out the best match. Perhaps a part of the request URI can be matched, yet they won’t be picked unless the longest is matched.

In the above sample, a part of the second resource path is exactly similar to the first request’s path. So dispatcher can get confused at the middle of resource traverse. But according to the standard, longest path match will be considered as the best match. Hence when user requests for http://localhost:9090/foo/bar/baz, the latter resource will be invoked. First resource will be matched when the request comes with http://localhost:9090/foo/bar as URI.

Wild card path matching

Path segment can contain a asterisk(*) which represents the wild card. That is some special way to say that if nothing matched, then this is place to be invoked. When the best resource match does not exist, a resource with a wild card path can be a stated in the API design to get requests dispatched without any failure.

Consider the first resource, it’s path has two segments plus a wildcard(expect more than two path segments in the request path). It interprets that any request path starting from those two segments will be dispatched to the first resource unless the service contains an exact matching resource. In above sample, there is no such exact match as other resources also contains wildcards. Hence when user requests for “http://localhost:9090/foo/bar/baz/qux”, the first resource will be invoked as it has the longest match with first two path segments(/bar/baz). So the asterisk will end up representing the “qux”.

Even though the second resource has a single matching segment(/bar) of the above user requested URI, still the priority is given to the longest match. If you consider request path : “http://localhost:9090/foo/bar/baz”, it matches with the two segments of first resource path. But it has only two segments and does not have any segment to match the wildcard. So it won’t be matched to first resource but eventually to the second resource.

Third resource is quite special as it contain only a wildcard as the path. So any request which does not get matched to first two resources will be dispatched to the third resource. Resource with “/*” path is considers as default resource. Any request that does not have a best matching resource will be matched to the default resource.

Importantly, the path segments which represents by the asterisk can be check using the extraPathInfo field in the request. If you invoke the third resource in using this URL: “http://localhost:9090/foo/hello/ballerina”, then the extraPathInfo field will return “/hello/ballerina” which is actually the extra info sent by the caller.

Path param expressions for variable request path segments

PathParam is a parameter which allows you to map variable URI path segments into your resource call. Resource path can have path templates which contain curly braces around the segment to specify that it’s a variable identification. This is really useful way to pass information to the resource. The value of the variable is extracted for the URI and assigned to the resource signature parameter during the run-time execution.

In the above sample, the getInfo resource expects two path segments in the URI where the first segment should an exact match to the “employee”. the second segment(id) is considered as a template and it varies from request to request, so nothing to do with matching. So GET requests with certain URIs such as followings will get dispatched. http://localhost:9090/company/employee/007
http://localhost:9090/company/employee/id051

Here the advantage is, being able to send some useful info to the resource to retrieve or process logic without even touching the request payload. How to access this param? Well, the same path template(id) is stated in the resource signature along with a type(string). So user can access it within the scope of particular resource and importantly path param types support the primitive types(string, int, boolean, float). If user needs the value of id as type int, particular URL segment will be casted to an int and presented in the resource.

Path template can locate as a path segment at any order and still the longest exact match is valid. path params are taken into consideration in the dispatching logic only when the specific matching paths are not present.

Conclusion

Time to wrap things up!!! Throughout this post, I talked about how ballerina services provide necessary configs to create APIs, how path matching works along with the HTTP method. Further, some facts about patterns of path designing using longest match, path params and wildcards. So consider those functionalities and different patterns when you design your API and make use of them whenever possible to provide smooth integration experience to the user. If you find any issues while building blocks, do not hesitate to report them under git issue.

Until the next post, Happy coding!!!

--

--