Specification: Ballerina HTTP Library

Owners: @shafreenAnfar @TharmiganK @ayeshLK @chamil321
Reviewers: @shafreenAnfar @bhashinee @TharmiganK @ldclakmal
Created: 2021/12/23
Updated: 2022/04/08
Edition: Swan Lake

Introduction

This is the specification for the HTTP standard library of Ballerina language, which provides HTTP client-server functionalities to produce and consume HTTP APIs.

The HTTP library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag.

If you have any feedback or suggestions about the library, start a discussion via a GitHub issue or in the Slack channel. Based on the outcome of the discussion, the specification and implementation can be updated. Community feedback is always welcome. Any accepted proposal, which affects the specification is stored under /docs/proposals. Proposals under discussion can be found with the label type/proposal in GitHub.

The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug.

Contents

  1. Overview
  2. Components
  3. Request-routing
  4. Annotations
  5. url-parameters
  6. Request and Response
  7. Header and Payload
  8. Interceptor and error handling
  9. Security
  10. Protocol-upgrade

1. Overview

Ballerina language provides first-class support for writing network-oriented programs. The HTTP standard library uses these language constructs and creates the programming model to produce and consume HTTP APIs.

The HTTP standard library is designed to work with HTTP protocol. It includes high-level abstractions such as http:Request, http:Response, http:Service, and http:Client which allow users to produce and consume HTTP API. Further, developers can use this library to build other libraries. The standard libraries such as GraphQL, Websub, and WebSubHub use this library internally.

In addition to functional requirements, this library deals with nonfunctional requirements such as security, observability, and resiliency. Each requirement is discussed in detail in the coming sections.

2. Components

2.1. Listener

The HTTP listener object receives network data from a remote process according to the HTTP transport protocol and translates the received data into invocations on the resources functions of services that have been attached to the listener object. The listener provides the interface between network and services.

As defined in Ballerina 2021R1 Section 5.7.4 the Listener has the object constructor and life cycle methods such as attach(), detach(), 'start(), gracefulStop(), and immediateStop().

2.1.1. Automatically starting the service

If a service is attached to the listener, then the listener starts listening on the given port after executing attach() and start() methods. HTTP listener can be declared as follows honoring to the generic listener declaration

2.1.2. Programmatically starting the service

Users can programmatically start the listener by calling each lifecycle method as follows.

2.2. Service

Service is a collection of resources functions, which are the network entry points of a ballerina program. In addition to that a service can contain public and private functions which can be accessed by calling with self.

2.2.1. Service type

Above distinct type is provided by HTTP module and user can include the type as *http:Service to refer it. The comprehensive typing support is yet to be added to the language. Until that, the compiler plugin is used to validate the services.

2.2.2. Service base path

The base path is considered during the request dispatching to discover the service. Identifiers and string literals are allowed to be stated as base path and it should be started with /. The base path is optional and it will be defaulted to / when not defined. If the base path contains any special characters, those should be escaped or defined as string literals

A service can be declared in three ways upon the requirement.

2.2.3. Service declaration

The Service declaration is a syntactic sugar for creating a service and it is the mostly used approach for creating a service. The declaration gets desugared into creating a listener object, creating a service object, attaching the service object to the listener object.

2.2.4. Service class declaration

The service value can be instantiated using the service class. This way, user has the completed control of attaching the service to the listener. The life cycle methods to used to proceed.

2.2.5. Service constructor expression

2.3. Resource

A method of a service can be declared as a resource function which is associated with configuration data that is invoked by a network message by a Listener. Users write the business logic inside a resource and expose it over the network.

2.3.1. Accessor

The accessor-name of the resource represents the HTTP method and it can be get, post, put, delete, head, patch, options and default. If the accessor is unmatched, 405 Method Not Allowed response is returned. When the accessor name is stated as default, any HTTP method can be matched to it in the absence of an exact match. Users can define custom methods such as copy, move based on their requirement. A resource which can handle any method would look like as follows. This is useful when handling unmatched verbs.

2.3.2. Resource name

The resource-name represents the path of the resource which is considered during the request dispatching. The name can be hierarchical(foo/bar/baz). Each path identifier should be separated by / and first path identifier should not contain a prefixing /. If the paths are unmatched, 404 NOT FOUND response is returned.

Only the identifiers can be used as resource path not string literals. Dot identifier is used to denote the / only if the path contains a single identifier.

Any special characters can be used in the path by escaping.

2.3.3. Path parameter

The path parameter segment is also a part of the resource name which is declared within brackets along with the type. As per the following resource name, baz is the path param segment and it’s type is string. Like wise users can define string, int, boolean, float, and decimal typed path parameters. If the paths are unmatched, 404 NOT FOUND response is returned. If the segment failed to parse into the expected type, 500 Internal Server Error response is returned.

If multiple path segments needs to be matched after the last identifier, Rest param should be used at the end of the resource name as the last identifier. string, int, boolean, float, and decimal types are supported as rest parameters.

Using both 'default accessor and the rest parameters, a default resource can be defined to a service. This default resource can act as a common destination where the unmatched requests (either HTTP method or resource path) may get dispatched.

2.3.4. Signature parameters

The resource function can have the following parameters in the signature. There are not any mandatory params or any particular order. But it’s a good practice to keep the optional param at the end.

However, the first choice should be to use signature params and use returns. Avoid caller unless you have specific requirement. Also use data binding, header params and resource returns to write smaller code with more readability.

2.3.4.1. http:Caller

The caller client object represents the endpoint which initiates the request. Once the request is processed, the corresponding response is sent back using the remote functions which are associated with the caller object. In addition to that, the caller has certain meta information related to remote and local host such as IP address, protocol. This parameter is not compulsory and not ordered.

The CallerInfo annotation associated with the Caller is to denote the response type. It will ensure that the resource function responds with the right type and provides static type information about the response type that can be used to generate OpenAPI.

The default type is the http:Response. Other than that, caller remote functions will accept following types as the outbound response payload. Internally an http:Response is created including the given payload value

Based on the payload types respective header value is added as the Content-type of the http:Response.

TypeContent Type
()-
stringtext/plain
xmlapplication/xml
byte[], stream<byte[], io:Error?>application/octet-stream
int, float, decimal, booleanapplication/json
map<json>, table<map<json>>, map<json>[], table<map<json>>)[]application/json

In addition to the above types, caller respond() method can accept StatusCodeResponse or error type. In case of error, an error response is returned to the client with the error message.

The HTTP compiler extension checks the argument of the respond() method if the matching payload type is passed as denoted in the CallerInfo annotation. At the moment, in terms of responding error, CallerInfo annotation can only support http:Error type.

When the caller respond() method is invoked from HTTP post resource by providing anydata payload, the status code of the outbound response will be set to HTTP Created (201) by default.

2.3.4.2. http:Request

The http:Request represents the request which is sent and received over the network which includes headers and the entity body. Listener passes it to the resource function as an argument to be accessed by the user based on their requirement. This parameter is not compulsory and not ordered.

See section Request and Response to find out more.

2.3.4.3. Query parameter

The query param is a URL parameter which is available as a resource function parameter and it's not associated with any annotation or additional detail. This parameter is not compulsory and not ordered. The type of query param are as follows

The same query param can have multiple values. In the presence of multiple such values, If the user has specified the param as an array type, then all values will return. If not the first param values will be returned. As per the following resource function, the request may contain at least two query params with the key of bar and id. Eg : “/hello?bar=hi&id=56”

If the query parameter is not defined in the function signature, then the query param binding does not happen. If a query param of the request URL has no corresponding parameter in the resource function, then that param is ignored. If the parameter is defined in the function, but there is no such query param in the URL, that request will lead to a 400 BAD REQUEST error response unless the type is nilable (string?)

The query param consists of query name and values. Sometimes user may send query without value(foo:). In such situations, when the query param type is nilable, the values returns nil and same happened when the complete query is not present in the request. In order to avoid the missing detail, a service level configuration has introduced naming treatNilableAsOptional

Case Resource argument Query Current Mapping (treatNilableAsOptional=true - Default) Ideal Mapping (treatNilableAsOptional=false)
1 string foo foo=bar bar bar
foo= "" ""
foo Error : no query param value found for 'foo' Error : no query param value found for 'foo'
No query Error : no query param value found for 'foo' Error : no query param value found for 'foo'
2 string? foo foo=bar bar bar
foo= "" ""
foo nil nil
No query nil Error : no query param value found for 'foo'

See section Query to understand accessing query param via the request object.

2.3.4.4. Payload parameter

The payload parameter is used to access the request payload during the resource invocation. When the payload param is defined with @http:Payload annotation, the listener deserialize the inbound request payload based on the media type which retrieved by the Content-type header of the request. The data binding happens thereafter considering the parameter type. The type of payload parameter can be one of the anytype. If the header is not present or not a standard header, the binding type is inferred by the parameter type.

Following table explains the compatible anydata types with each common media type. In the absence of a standard media type, the binding type is inferred by the payload parameter type itself. If the type is not compatible with the media type, error is returned.

Ballerina TypeStructure"text""xml""json""x-www-form-urlencoded""octet-stream"
boolean
boolean[]
map<boolean>
table<map<boolean>>
int
int[]
map<int>
table<map<int>>
float
float[]
map<float>
table<map<float>>
decimal
decimal[]
map<decimal>
table<map<decimal>>
byte[]
byte[][]
map<byte[]>
table<map<byte[]>>
string
string[]
map<string>
table<map<string>>
xml
json
json[]
map<json>
table<map<json>>
map
map[]
map<map>
table<map<map>>
record
record[]
map<record>
table<record>

The payload binding process begins soon after finding the correct resource for the given URL and before the resource execution. The error which may occur during the process will be returned to the caller with the response status code of 400 BAD REQUEST. The successful binding will proceed the resource execution with the built payload.

Additionally, the payload parameter type can be a union of anydata. Based on the media type, the potential binding type is decided. For example, if the union is defined as json|xml and the media type is related to json, the deserialization and the binding will proceed according to the type json. But if the media type is related to xml the process will happen according to the type xml. If the given types of the union are not compatible with the media type, an error is returned.

If any of the type is union with ()(i.e string?), then in the absence of the payload, () will be assigned as the value without being responded by a BAD REQUEST response.

Internally the complete payload is built, therefore the application should have sufficient memory to support the process. Payload binding is not recommended if the service behaves as a proxy/pass-through where request payload is not accessed.

User may specify the expected content type in the annotation to shape the resource as described in section Payload binding parameter

2.3.4.5. Header parameter

The header parameter is to access the inbound request headers The header param is defined with @http:Header annotation The type of header param can be defined as follows;

When multiple header values are present for the given header, the first header value is returned when the param type is string or any of the basic types. To retrieve all the values, use string[] type or any array of the basic types. This parameter is not compulsory and not ordered.

The header param name is considered as the header name during the value retrieval. However, the header annotation name field can be used to define the header name whenever user needs some different variable name for the header.

User cannot denote the type as a union of pure type, array type, or record type together, that way the resource cannot infer a single type to proceed. Hence, returns a compiler error.

In the absence of a header when the param is defined in the resource signature, listener returns 400 BAD REQUEST unless the type is nilable.

If the requirement is to access all the header of the inbound request, it can be achieved through the http:Headers typed param in the signature. It does not need the annotation and not ordered.

The header consists of header name and values. Sometimes user may send header without value(foo:). In such situations, when the header param type is nilable, the values returns nil and same happened when the complete header is not present in the request. In order to avoid the missing detail, a service level configuration has introduced naming treatNilableAsOptional

Case Resource argument Header Current Mapping (treatNilableAsOptional=true - Default) Ideal Mapping (treatNilableAsOptional=false)
1 string foo foo:bar bar bar
foo: Error : no header value found for 'foo' Error : no header value found for 'foo'
No header Error : no header value found for 'foo' Error : no header value found for 'foo'
2 string? foo foo:bar bar bar
foo: nil nil
No header nil Error : no header value found for 'foo'

2.3.5. Return types

The resource function supports anydata, error?, http:Response and http:StatusCodeResponse as return types. Whenever user returns a particular output, that will result in an HTTP response to the caller who initiated the call. Therefore, user does not necessarily depend on the http:Caller and its remote methods to proceed with the response.

In addition to that the @http:Payload annotation can be specified along with anydata return type mentioning the content type of the outbound payload.

Based on the return types respective header value is added as the Content-type of the http:Response.

TypeContent Type
()-
stringtext/plain
xmlapplication/xml
byte[]application/octet-stream
int, float, decimal, booleanapplication/json
map<json>, table<map<json>>, map<json>[], table<map<json>>)[]application/json
http:StatusCodeResponseapplication/json
2.3.5.1. Status Code Response

The status code response records are defined in the HTTP module for every HTTP status code. It improves readability & helps OpenAPI spec generation.

Following is the http:Ok definition. Likewise, all the status codes are provided.

2.3.5.2. Return nil

Return nil from the resource has few meanings.

  1. If the resource wants to return nothing, the listener will return 202 ACCEPTED response.
  1. If the resource is dealt with the response via http:Caller, then returning () does not lead to subsequent response. Listener aware that the request is already being served.
  1. If the resource is dealt with the success response via http:Caller and return () in the else case, then the response is 500 INTERNAL SERVER ERROR.
2.3.5.3. Default response status codes

To improve the developer experience for RESTful API development, following default status codes will be used in outbound response when returning anydata directly from a resource function.

Resource AccessorSemanticsStatus Code
GETRetrieve the resource200 OK
POSTCreate a new resource201 Created
PUTCreate a new resource or update an existing resource200 OK
PATCHPartially update an existing resource200 OK
DELETEDelete an existing resource200 OK
HEADRetrieve headers200 OK
OPTIONSRetrieve permitted communication options200 OK

2.3.6. Introspection resource

The introspection resource is internally generated for each service and host the openAPI doc can be generated (or retrieved) at runtime when requested from the hosted service itself. In order to get the openAPI doc hosted resource path, user can send an OPTIONS request either to one of the resources or the service. The link header in the 204 response specifies the location. Then user can send a GET request to the dynamically generated URL in the link header with the relation openapi to get the openAPI definition for the service.

Sample service

Output of OPTIONS call to usual resource

Output of GET call to introspection resource

Output of OPTIONS call to service base path

2.4. Client

A client allows the program to send network messages to a remote process according to the HTTP protocol. The fixed remote functions of the client object correspond to distinct network operations defined by the HTTP protocol.

The client init function requires a valid URL and optional configuration to initialize the client.

2.4.1 Client types

The client configuration can be used to enhance the client behaviour.

Based on the config, the client object will be accompanied by following client behaviours. Following clients cannot be instantiated calling new, instead user have to enable the config in the ClientConfiguration.

2.4.1.1 Security

Provides secure HTTP remote functions for interacting with HTTP endpoints. This will make use of the authentication schemes configured in the HTTP client endpoint to secure the HTTP requests.

2.4.1.2 Caching

An HTTP caching client uses the HTTP caching layer once cache config is enabled.

2.4.1.3 Redirect

Provide the redirection support for outbound requests internally considering the location header when followRedirects configs are defined.

2.4.1.4 Retry

Provides the retrying over HTTP requests when retryConfig is defined.

2.4.1.5 Circuit breaker

A Circuit Breaker implementation which can be used to gracefully handle network failures.

Provides the cookie functionality across HTTP client actions. The support functions defined in the request can be used to manipulate cookies.

Following clients can be created separately as it requires different configurations.

2.4.1.7 Load balance

LoadBalanceClient endpoint provides load balancing functionality over multiple HTTP clients. It uses the LoadBalanceClientConfiguration.

2.4.1.8 Failover

An HTTP client endpoint which provides failover support over multiple HTTP clients. It uses the FailoverClientConfiguration.

2.4.2. Client action

The HTTP client contains separate remote function representing each HTTP method such as get, put, post, delete,patch,head,options and some custom remote functions.

2.4.2.1 Entity body methods

POST, PUT, DELETE, PATCH methods are considered as entity body methods. These remote functions contains RequestMessage as the second parameter to send out the Request or Payload.

Based on the payload types respective header value is added as the Content-type of the http:Request.

TypeContent Type
()-
stringtext/plain
xmlapplication/xml
byte[], stream<byte[], io:Error?>application/octet-stream
int, float, decimal, booleanapplication/json
map<json>, table<map<json>>, map<json>[], table<map<json>>)[]application/json

The header map and the mediaType param are optional for entity body remote functions.

2.4.2.2 Non Entity body methods

GET, HEAD, OPTIONS methods are considered as non entity body methods. These remote functions do not contain RequestMessage, but the header map an optional param.

2.4.2.3 Resource methods

In addition to the above remote method actions, HTTP client supports executing standard HTTP methods through resource methods. The following are the definitions of those resource methods :

The query parameter is passed as field-value pair in the resource method call. The following are examples of such resource method calls :

2.4.2.4 Forward/Execute methods

In addition to the standard HTTP methods, forward function can be used to proxy an inbound request using the incoming HTTP request method. Also execute remote function is useful to send request with custom HTTP verbs such as move, copy, ..etc.

2.4.2.5 HTTP2 additional methods

Following are the HTTP2 client related additional remote functions to deal with promises and responses.

2.4.3. Client action return types

The HTTP client remote function supports the contextually expected return types. The client operation is able to infer the expected payload type from the LHS variable type. This is called as client payload binding support where the inbound response payload is accessed and parse to the expected type in the method signature. It is easy to access the payload directly rather manipulation http:Response using its support methods such as getTextPayload(), ..etc.

Client data binding supports anydata where the payload is deserialized based on the media type before binding it to the required type. Similar to the service data binding following table explains the compatible anydata types with each common media type. In the absence of a standard media type, the binding type is inferred by the payload parameter type itself. If the type is not compatible with the media type, error is returned.

Ballerina TypeStructure"text""xml""json""x-www-form-urlencoded""octet-stream"
boolean
boolean[]
map<boolean>
table<map<boolean>>
int
int[]
map<int>
table<map<int>>
float
float[]
map<float>
table<map<float>>
decimal
decimal[]
map<decimal>
table<map<decimal>>
byte[]
byte[][]
map<byte[]>
table<map<byte[]>>
string
string[]
map<string>
table<map<string>>
xml
json
json[]
map<json>
table<map<json>>
map
map[]
map<map>
table<map<map>>
record
record[]
map<record>
table<record>

In case of using var as return type, user can pass the typedesc to the targetType argument.

If any of the type is union with ()(i.e string?), then in the absence of the payload, () will be assigned as the value without being responded by a BAD REQUEST response.

When the user expects client data binding to happen, the HTTP error responses (4XX, 5XX) will be categorized as an error (http:ClientRequestError, http:RemoteServerError) of the client remote operation. These error types contain payload, headers and status code inside the error detail.

The error detail is useful when user wants to dig deeper to understand the backend failure. Here the error message is the response phrase.

Additionally, the client action return type can be a union of anydata. Based on the media type, the potential binding type is decided. For example, if the union is defined as json|xml and the media type is related to json, the deserialization and the binding will proceed according to the type json. But if the media type is related to xml the process will happen according to the type xml. If the given types of the union are not compatible with the media type, an error is returned.

If the type is union with ()(i.e string?), then in the absence of the payload, () will be assigned as the value without being responded by a BAD REQUEST response.

3. Request routing

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

3.1. URI and HTTP method match

The ballerina dispatcher considers the absolute-resource-path of the service as the base path and the resource function name as the path of the resource function for the URI path match. Ballerina dispatching logic depends on the 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. The accessor name of the resource describes the HTTP method where the name of the remote function implicitly describes its respective method

3.2. Most specific 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.

3.3. Wild card path match

The resource path can contain a template within the bracket along with the type which represents the wild card. i.e [string… s].That is some special way to say that if nothing matched, then the wildcard should be invoked. When the best resource match does not exist, a resource with a wild card path can be stated in the API design to get requests dispatched without any failure.

3.4. Path parameter template match

PathParam is a parameter which allows you to map variable URI path segments into your resource call. Only the resource functions allow this functionality where the resource name can have path templates as a path segment with variable type and the identifier within curly braces.

The value of the variable is extracted from the URI and assigned to the resource name parameter during the run-time execution.

4. Annotations

4.1. Service configuration

The configurations stated in the http:ServiceConfig changes the behavior of particular services and applies it to all the resources mentioned in the particular services. Some configurations such as Auth, CORS can be overridden by resource level configurations. Yet, the service config is useful to cover service level configs.

The openApiDefinition field in http:ServiceConfig annotation serves a unique purpose. It will be automatically populated at compile-time with OpenAPI definition of the particular http:Service if the OpenAPI definition auto generation is available.

4.2. Resource configuration

The resource configuration responsible for shaping the resource function. Most of the behaviours are provided from the language itself such as path, HTTP verb as a part of resource function. Some other configs such as CORS, compression, auth are defined in the resource config.

4.3. Payload annotation

The payload annotation has two usages. It is used to decorate the resource function payload parameter and to decorate the resource return type.

4.3.1. Payload binding parameter

The request payload binding is supported in resource functions where users can access it through a resource function parameter. The @http:Payload annotation is specially introduced to distinguish the request payload with other resource function parameters. The annotation can be used to specify values such as mediaType...etc. Users can define the potential request payload content type as the mediaType to perform some pre-validations as same as Consumes resource config field.

During the runtime, the request content-type header is matched against the mediaType field value to validate. If the validation fails, the listener returns an error response with the status code of 415 Unsupported Media Type. Otherwise the dispatching moves forward.

4.3.2. Anydata return value info

The same annotation can be used to specify the MIME type return value when a particular resource function returns one of the anydata typed values. In this way users can override the default MIME type which the service type has defined based on the requirement. Users can define the potential response payload content type as the mediaType to perform some pre-runtime validations in addition to the compile-time validations as same as produces resource config field.

During the runtime, the request accept header is matched against the mediaType field value to validate. If the validation fails, the listener returns an error response with the status code of 406 Not Acceptable. Otherwise, the dispatching moves forward.

The annotation is not mandatory, so if the media type info is not defined, the following table describes the default MIME types assigned to each anydata type.

Declared return typeMIME type
()(no body)
xmlapplication/xml
stringtext/plain
byte[]application/octet-stream
map<json>, table<map<json>>, (map<json> | table<map<json>>)[])application/json
int, float, decimal, booleanapplication/json

If anything comes other than above return types will be default to application/json.

4.4. CallerInfo annotation

The CallerInfo annotation associated with the Caller is to denote the response type. It will ensure that the resource function responds with the right type and provides static type information about the response type that can be used to generate OpenAPI.

4.5. Header annotation

4.6. Cache annotation

This annotation can be used to enable response caching from the resource signature. This allows to set the cache-control, etag and last-modified headers in the response.

The default behavior (@http:Cache) is to have must-revalidate,public,max-age=3600 directives in cache-control header. In addition to that etag and last-modified headers will be added.

This annotation can only support return types of anydata and SuccessStatusCodeResponse. (For other return values cache configuration will not be added through this annotation)

5. URL parameters

5.1. Path

Path params are specified in the resource name itself. Path params can be specified in the types of string, int, boolean, decimal and float. During the request runtime the respective path segment is matched and cast into param type. Users can access it within the resource function, and it is very useful when designing APIs with dynamically changing path segments.

5.2. Query

Query params can be accessed via the resource signature without an annotation or accessed via request functions.

5.3. Matrix

The matrix params are one of the URL param which is supported access in ballerina using a function which bound to the request

6. Request and Response

The request and the response represent the message/data which travel over the network using HTTP. The request object models the inbound/outbound message with request oriented properties, headers and payload. Followings are the properties associated with the http:Request which get populated for each request during the runtime.

The header and the payload manipulation can be done using the functions associated to the request.

Same as request, the response object also models the inbound/outbound message with the response oriented properties and headers. Followings are the properties associated with the http:Response which get populated for each response during the runtime.

The header and the payload manipulation can be done using the functions associated to the response.

7. Header and Payload

The header and payload are the main components of the request and response. In the world of MIME, that is called Entity header and Entity body. Ballerina supports multiple payload types and allows convenient functions to access headers along with other properties.

7.1. Parse header functions

Hypermedia As the Engine Of Application State (HATEOAS) is one of the key principles in REST, which brings the connectedness to a set of scattered resources. It also brings direction as to what might user could do next. Similar to Web pages REST APIs becomes self-descriptive and dynamic along with this principle.

As an initial support to HATEOAS, HTTP package has the ability to statically record the connectedness of resources through Links object. Links is a map of Link objects which represent the connectedness between resources. The Link record is defines as follows :

This Links is generated from the linkedTo field in the ResourceConfig annotation and added either to the payload or as a Link header depending on the payload type. This Links will not be added when an http:Response is returned.

7.2.1 LinkedTo record

The LinkedTo record is defined as follows :

This record represents a connectedness between two resources. All the fields in the LinkedTo record is case-insensitive. The relation field is defaulted to the IANA link relation self, and for a specific resource, the linked resources should have a unique relation.

The linked resource is resolved using the resource link name specified in the name field. To find the linked resource, the linked resource should be configured with the same name through ResourceConfig annotation. Following is a simple example of creating links :

Resource link name can be duplicated only when the resources have the same path. In this case, the method of the linked resource should be specified in the LinkedTo record to resolve conflicts. Following is an example when we have two resources with the same resource link name :

The static Links generated from the linkedTo field will be injected into the JSON payload when it is not a closed record and not readonly. Suppose the user returns the below record type, the runtime will inject the Links record as in the latter. Therefore, the response should be considered as a record with the Links field.

Following is an example of Links in payload :

The response payload to the GET resource will look like this :

When there is no payload or when Links not supported in the payload, the Links will be added as a Link header. Following is an example of Links in Link header:

The response will have the following header :

link: "</payment/{id}>; rel=\"payment\"; methods=\"\"PUT\"\""

The Links will not overwrite the payload or the header if the user has already added the links.

8. Interceptor and error handling

8.1 Interceptor

Interceptor enhances the HTTP package with interceptors. Interceptors typically do small units of work such as logging, header manipulation, state publishing, etc., before resources are invoked. The ability to execute some common logic for all the inbound requests and outbound responses has proven to be highly useful. It typically includes some small unit of work such as the below.

  • Logging
  • Header manipulation
  • Observability
  • Throttling
  • Validating
  • Securing

Interceptors are designed for both request and response flows. There are just service objects which will be executed in a configured order to intercept request and response. These interceptor services can only have either a resource function or a remote function depends on the interceptor type. Moreover, they do not support ServiceConfig, ResourceConfig and Cache annotations.

8.1.1 Request interceptor

Following is an example of RequestInterceptor written in Ballerina swan-lake. RequestInterceptor can only have one resource function.

Since interceptors work with network activities, it must be either a remote or resource function. In this case resource functions are used for RequestInterceptor as it gives more flexibility. With resource functions interceptors can be engaged based on HTTP method and path.

For instance consider a scenario where there are two resources: one on path foo whereas the other on path bar. If the user writes an interceptor as follows, it would only get hit when the request is directed to foo resource.

8.1.1.1 Request context

Following is the rough definition of the interceptor context.

8.1.1.2 next() method

However, there is an addition when it comes to RequestContext. A new method namely, next() is introduced to control the execution flow. Users must invoke next() method in order to trigger the next interceptor in the pipeline. Then the reference of the retrieved interceptor must be returned from the resource function. Pipeline use this reference to execute the next interceptor.

Previously, this was controlled by returning a boolean value which is quite cryptic and confusing.

8.1.1.3 Return to respond

There is a key difference between interceptors and the final service. Resources in the final service allow returning values which in turn results in HTTP responses. The same can be done inside the RequestInterceptors. However, as mentioned above RequestInterceptor additionally could return the NextService|error? to continue the pipeline which does not translate into HTTP response.

When a RequestInterceptor responded with a response, the response interceptor pipeline will get executed immediately. In case of an error, interceptor pipeline execution jumps to the nearest RequestErrorInterceptor or ResponseErrorInterceptor in the pipeline. These error interceptors are special kinds of interceptor which could be used to handle errors, and they are not necessarily the last interceptor in the pipeline, they can be anywhere in the chain. However, in the case of there is no error interceptors in the pipeline, pipeline returns the internal error response to the client similar to any HTTP service resource.

8.1.2 Response interceptor

Following is an example of ResponseInterceptor written in Ballerina swan-lake. ResponseInterceptor can only have one remote function : interceptResponse().

ResponseInterceptor is different from RequestInterceptor. Since it has nothing to do with HTTP methods and paths, remote function is used instead of resource function.

8.1.2.1 Return to respond

The remote function : interceptResposne() allows returning values other than NextService|error?. Anyway this will continue the response interceptor pipeline with the returned response object and calling RequestContext.next() is redundant in this case.

In case of an error, interceptor pipeline execution jumps to the nearest ResponseErrorInterceptor in the pipeline. . However, in the case of there is no ResponseInterceptor in the pipeline, pipeline returns the internal error response to the client.

8.1.3 Request error interceptor and response error interceptor

As mentioned above, these are special kinds of interceptor designed to handle errors. These interceptors can
be placed anywhere in the request or response interceptor chain. The framework automatically adds default RequestErrorInterceptor and ResponseErrorInterceptor which basically prints the error message to the console.

Users can override these interceptors by defining their own ones as follows. Users don’t have to specifically engage these interceptors as they only have fixed positions and they are always executed. The only additional and mandatory argument in this case is error err. Moreover, the RequestErrorInterceptor resource function can only have the default method and default path.

The same works for ResponseErrorInterceptor, the difference is it has a remote function : interceptResponseError() and deals with response object.

In the case of an error returned within an error interceptor, again execution jumps to the nearest error interceptor. However, if there is no error interceptor to jump to, the internal error response is returned just like in a normal interceptors.

8.1.4 Engaging interceptors

8.1.4.1 Service level

Interceptors could get engaged at service level. One reason for this is that users may want to engage two different interceptor chains for each service even though it is attached to the same Listener. At the service level resource function paths are relative to the service base path.

8.1.4.2 Listener level

Interceptors could get engaged at Listener level as well. Interceptors engaged at listener level should have resource function only with default path i.e. these interceptors will get executed for all the services registered on this listener.

8.1.4.3 Execution order of interceptors

img.png In the above example blue dashed box represents the RequestErrorInterceptor and blue boxes simply represent the RequestInterceptors, whereas green dashed box represents the ResponseErrorInterceptor and green boxes simply represent the ResponseInterceptors.

ResponseInterceptors are executed in the opposite direction of RequestInterceptors i.e. RequestInterceptors are executed head to tail whereas ResponseInterceptors are executed tail to head. The new execution order is as follows, assuming that there are no error occurred in the pipeline :

RequestInterceptor  : 1, 2, 4
ResponseInterceptor : 5, 3

However, if the user decides to respond at 4 and terminate the cycle, only the ResponseInterceptor at 3 gets executed. Also, when the RequestInterceptor at 2 returns an error, the execution jumps from 2 to 6 as the nearest Error Interceptor is at 6. The same goes to the response path.

The execution order when interceptors are engaged at both service and listener levels is as follows:

ListenerLevel : RequestInterceptors -> ServiceLevel : RequestInterceptors -> TargetService -> 
ServiceLevel : ResponseInterceptors -> ListenerLevel : ResponseInterceptors

Execution of interceptors does not depend on the existence of the end service i.e. the interceptors are executed in the relevant order even though the end service does not exist.

8.1.5 Data binding

RequestInterceptor methods support data binding. Which means users can directly access the payload, headers and query parameters. In order to get hold of the headers and the payload, users must use @http:Payload and @http:Headers.

8.2 Error handling

8.2.1 Error interceptors

Error handling is an integral part of any network program. Errors can be returned by many components such as interceptors, dispatcher, data-binder, security handlers, etc. These errors are often handled by a default handler and sent back as 500 Internal Server Error with an entity-body. However, this often causes problems because when designing any API consistency matters. Therefore, all the responses must have a consistent format.

As a result, almost all the real API requires overriding the default error handler and replacing it with their own error handlers. This can be done by error interceptors discussed in Request error interceptor and response error interceptor. These error handlers can be placed anywhere in the pipeline. The only mandatory argument in error interceptors is error. Just like a main service, it is possible to return values from error handlers which will send back as HTTP responses. This overrides the current response and results in triggering the next response interceptor. Following is such an example :

The HTTP module also have a DefaultErrorInterceptor which is a ResponseErrorInterceptor. This will be added by the listener and will be executed at last when there is an error. Hence, any error which is not handled by other error interceptors will be finally handled by this default error handler. In essence, the internal default error interceptor will look like this :

In order to overwrite this default error handling behavior, a custom ResponseErrorInterceptor can be placed as the first interceptor in the listener level configuration which will be executed at last just before the DefaultErrorHandler.

8.2.2 Error types

In addition to error interceptors, HTTP module provides distinct error types in order to intercept errors and handle them differently. These error types have a hierarchical structure starting from the basic HttpError. The following table summarizes the error types which can be intercepted by the error interceptors:

ErrorError Type
Executing Interceptors - Interceptor Level500 - no next service to be returnedInterceptorReturnError
500 - request context object does not contain the configured interceptors
500 - next interceptor service did not match with the configuration
500 - target service did not match with the configuration
Other errors occurred during the resource/remote function executionSame as the returned error type
Finding Service - Listener Level404 - no service has registered for listenerServiceDispatchingError
404 - no matching service found for path
400 - Found non-matrix parameter in path
Finding Resource - Service Level404 - no matching resource found for pathResourceDispatchingError
405 - Method not allowed
Consumes & Produces - Service Level406 - Not Acceptable
415 - Unsupported Media Type
Databinding - Service Level400 - Error in casting path paramPathParameterBindingError
400 - no query param value foundQueryParameterBindingError
400 - Error in casting query param
400 - no header value foundHeaderBindingError
400 - header binding failed
400 - data binding failedPayloadBindingError
Security - Resource Level401 - Unauthorized errorsListenerAuthnError
403 - Forbidden errorsListenerAuthzError
Resource execution - Resource Level500 - Returned errorsSame as the returned error type

8.2.3 Trace log

The HTTP trace logs can be used to monitor the HTTP traffic that goes in and out of Ballerina. The HTTP trace logs are disabled as default. To enable trace logs, the log level has to be set to TRACE using the runtime argument: -Cballerina.http.traceLogConsole=true.

The HTTP access logs and trace logs are disabled as default. To enable, the configurations can be set by the following config.toml file:

The configurations can be set in the config.toml file for advanced use cases such as specifying the file path to save the trace logs and specifying the hostname and port of a socket service to publish the trace logs.

8.2.4 Access log

Ballerina supports HTTP access logs for HTTP services. The access log format used is the combined log format. The HTTP access logs are disabled as default. To enable access logs, set console=true under the ballerina.http.accessLogConfig in the Config.toml file. Also, the path field can be used to specify the file path to save the access logs.

9. Security

9.1 Authentication and Authorization

There are two ways to enable authentication and authorization in HTTP.

  1. Declarative approach
  2. Imperative approach

9.1.1 Declarative Approach

This is also known as the configuration-driven approach, which is used for simple use cases, where users have to provide a set of configurations and do not need to be worried more about how authentication and authorization works. The user does not have full control over the configuration-driven approach.

The service configurations are used to define the authentication and authorization configurations. Users can configure the configurations needed for different authentication schemes and configurations needed for authorizations of each authentication scheme. Also, the configurations can be provided at the service level. The priority will be given from bottom to top. Then, the auth handler creation and request authentication/authorization is handled internally without user intervention. The requests that succeeded both authentication and/or authorization phases according to the configurations will be passed to the business logic layer.

9.1.1.1 Listener - Basic Auth - File User Store
9.1.1.2. Listener - Basic Auth - LDAP User Store
9.1.1.3 Listener - JWT Auth
9.1.1.4 Listener - OAuth2
9.1.1.5 Client - Basic Auth
9.1.1.6 Client - Bearer Token Auth
9.1.1.7 Client - Self-Signed JWT
9.1.1.8 Client - Bearer Token OAuth2
9.1.1.9 Client - Grant Types OAuth2

9.1.2 Imperative Approach

This is also known as the code-driven approach, which is used for advanced use cases, where users need to be worried more about how authentication and authorization work and need to have further customizations. The user has full control of the code-driven approach. The handler creation and authentication/authorization calls are made by the user at the business logic layer.

9.1.2.1 Listener - Basic Auth - File User Store
9.1.2.2 Listener - Basic Auth - LDAP User Store
9.1.2.3 Listener - JWT Auth
9.1.2.4 Listener - OAuth2
9.1.2.5 Client - Basic Auth
9.1.2.6 Client - Bearer Token Auth
9.1.2.7. Client - Self-Signed JWT
9.1.2.8. Client - Bearer Token OAuth2
9.1.2.9. Client - Grant Types OAuth2

9.2 SSL/TLS and Mutual SSL

The HTTPS listener could connect to or interact with an HTTPS client. The http:ListenerSecureSocket configuration of the listener exposes the HTTPS connection related configs.

9.2.1 Listener - SSL/TLS

9.2.2 Listener - Mutual SSL

The mutual SSL support which is a certificate-based authentication process in which two parties (the client and server) authenticate each other by verifying the digital certificates. It ensures that both parties are assured of each other’s identity.

9.2.3 Client - SSL/TLS

9.2.4 Client - Mutual SSL

10. Protocol upgrade

10.1. HTTP/2

The version 2 of HTTP protocol is supported in both Listener and Client space which could be configured through the respective configuration.

There are few API level additions when it comes to the HTTP/2 design such as Push promise and promise response.

10.1.1. Push Promise and Promise Response

Push Promise and Promise response are the only application level new semantics which are introduced by HTTP2.

Other protocol changes such as streams, messages, frames, request prioritization, flow control, header compression, etc. are all lower level changes that can be handled by the HTTP listener seamlessly from the user.