Community Call - July 2021
Changes to the HTTP Package in
Ballerina Swan Lake
Agenda
- Getting involved and showing your support
- https://github.com/ballerina-platform/community
- Changes to the HTTP package in Swan Lake
- Changes to service and client implementations - Chamil Elladeniya
- Security changes - Chanaka Lakmal
- QnA
Changes to HTTP Package in Swan Lake
July 2021
Outline
➔ Service,
◆ Declaration, Absolute resource path
➔ Resource
◆ Accessor, Name, Path param, Rest param
◆ Signature params such as query, payload, header, caller and
request
◆ Return types
➔ Client
◆ Response payload retrieval
◆ Headers, Media type arguments in client operation
➔ Security
Service declaration
A syntactic sugar for creating services.
Gets desugared into creating a listener object, creating a service object,
attaching the service object to the listener object, etc,.
service-declaration := "service" [[type-descriptor] variable-name] "on" expression-list etc
service http:Service /basePath on new http:Listener(9090) {
resource function get foo() returns string {
return "hello world";
}
}
Service class declaration
service isolated class SClass {
*http:Service;
isolated resource function get greeting() returns string {
return "hello world";
}
}
listener http:Listener serviceListener = new (9090);
http:Service httpService = new SClass();
public function main() {
error? err1 = serviceListener.attach(httpService, ["foo", "bar"]);
error? err2 = serviceListener.start();
runtime:registerListener(serviceListener);
}
Service constructor expression
listener http:Listener serviceListener = new (9090);
http:Service httpService = @http:ServiceConfig {} service object {
resource function get baz() returns string {
return "hello world";
}
};
public function main() {
error? err1 = serviceListener.attach(httpService, "/foo/bar");
error? err2 = serviceListener.start();
runtime:registerListener(serviceListener);
}
Absolute resource path (basePath)
○ Identifiers and String literals are allowed.
○ Starts with “/”
○ Optional and defaults to “/”
service /hello on new http:Listener(9090) {
resource function post test() {
//...
}
}
service “/hello” on new http:Listener(9090) {
resource function post test() {
//...
}
}
Absolute resource path
○ Names with special characters
service http:Service /my/Tes@tHello/go new http:Listener(9090) {
resource function get foo() {
//...
}
}
service http:Service "/Tes@tHello/go" new http:Listener(9090) {
resource function get foo() {
//...
}
}
Resource changes
○ Accessor, Name, Path param, Rest param
○ Signature params such as query, payload, header, caller and
request
○ Return types
Resource methods
○ Resource method has improved in terms of accessor, path and return value
type
resource-accessor-defn := metadata "resource" "function" accessor-name resource-name
function-signature method-defn-body
resource function get foo(string bar) returns http:Response {
http:Response resp = new;
return resp;
}
Resource accessor
○ Confines the resource to a particular HTTP method
○ In additions to the standard HTTP methods, custom methods are also
supported.
○ default identifier can be used to allow any HTTP method
resource function get foo() {
//...
}
resource function ’default foo() {
//...
}
Resource name
○ The relative path from the absolute path
○ Path identifiers separated by “/”
○ Dot identifier is used to denote the “/”
resource function get foo/bar/baz() {
//...
}
service /foo on http:Listener(9090) {
resource function get .() { // curl localhost:9090/foo will get dispatched here
//...
}
}
Path param
○ Include in the resource name itself. string, int, boolean, float, decimal types
are supported
resource function get data/[int age]/[string name]/[boolean status]/[float weight]() returns json {
int balAge = age + 1;
float balWeight = weight + 2.95;
string balName = name + " lang";
if (status) {
balName = name;
}
json responseJson = { Name:name, Age:balAge, Weight:balWeight, Status:status, Lang: balName};
return responseJson;
}
Rest param
○ Allows multiple path segments to be dispatched in the absence of a
specific path. string, int, boolean, float, decimal types are supported
○ Rest parameter must be the last segment of resource path
resource function get foo/[string... s]() returns json {
json responseJson = {"echo":s[0]};
return responseJson;
}
Use case: Default resource
○ Allows any HTTP method.
○ Allow any path
resource function 'default [string... s]() returns json {
//...
}
Resource signature parameters
○ No compulsory parameters
○ No annotation required for Query param, Caller and Request
○ New additions - Payload, Header, Query
resource function XXX NAME_TEMPLATE ([@http:CallerInfo {} http:Caller hc], [http:Request
req], [somedatatype queryParam]*, (@http:Payload somedatatype payload)?, (@http:Header
somedatatype header)?) {
//...
}
Conceptual change
○ 1st choice should be to use signature params and use returns. Avoid caller
unless you have specific requirement.
○ Use data binding, header params and resource returns.
Query param
○ Does not associated with any annotation or additional detail. The type of
query param are as follows.
type BasicType boolean|int|float|decimal|string;
public type QueryParamType ()|BasicType|BasicType[];
resource function get q1(int id, string PersoN, float val, boolean isPresent, decimal dc) {
//...
}
resource function get q2(int? id, string[] PersoN, float[]? val) {
//...
}
Payload param
○ The payload parameter eases access of request payload during the
resource invocation. The payload param is defined with @http:Payload
annotation
public type PayloadType string | json | xml | byte[] | record {| anydata...; |}|
record {| anydata...; |}[];
resource function post body(@http:Payload Person person) returns json {
return {"Person": person};
}
Header param
○ The header parameter is to access the request headers The header param
is defined with @http:Header annotation
service sample on new http:Listener(9090) {
//Single header value extraction
resource function post hello1(@http:Header string referer) {
}
//Multiple header value extraction
resource function post hello2(@http:Header {name: "Accept"} string[] accept) {
}
//The http:Header object contains all the headers
resource function get hello3(http:Headers headers) {
String|error referer = headers.getHeader("Referer");
String[]|error accept = headers.getHeaders("Accept");
String[] keys = headers.getHeaderNames();
}
}
CallerInfo annotation
○ The annotation defines the response payload type.
resource function post foo(@http:CallerInfo {respondType: Person} http:Caller
caller) returns error?{
Person p = {};
error? = caller->respond(p);
}
Resource return type
○ anydata|http:Response|http:StatusCodeResponse|error|()
○ Can decorate with @http:Payload annotation and result in 200 OK
resource function get test() returns @http:Payload {mediaType:"text/id+plain"} string {
return "world";
}
resource function get test2() returns PersonTable[]? {
PersonTable tbPerson = table [
{id: 1, name: "John", salary: 300.50},
{id: 2, name: "Bella", salary: 500.50}
];
return [tbPerson, tbPerson];
}
StatusCodeResponse records
○ Built in records for all status codes
public type Ok record {
readonly StatusOk status;
string mediaType;
map<string|string[]> headers?;
anydata body?;
};
resource function get greeting() returns http:Ok|http:InternalServerError {
http:Ok ok = { body: "hello world", headers: { xtest: "foo"} };
return ok;
}
StatusCodeResponse records
○ Create an inline response
○ Improve readability & helps OpenAPI spec generation
type Person record {
string name;
};
resource function put person(string name) returns record {|*http:Created; Person body;|} {
Person person = {name:name};
return {
mediaType: "application/person+json",
headers: {
"X-Server": "myServer"
},
body: person
};
}
Return nil
○ If resource wants to return nothing, the listener will return 202 Accepted
response
resource function post person(@http:Payload Person p) {
int age = p.age;
io:println(string `Age is: ${age}`);
}
Return nil
○ 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.
resource function get fruit(string? colour, http:Caller caller) {
if colour == "red" {
error? result = caller->respond("Sending apple");
return; // ending the flow, so not 202 response
}
error? result = caller->respond("Sending orange");
}
Return nil in error case
○ 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
resource function get fruit(string? colour, http:Caller caller) {
if colour == "red" {
error? result = caller->respond("Sending apple");
return; // ending the flow
}
return; // 500 internal Server Error
}
Return error from the resource
○ Will lead to a 500 internal error and prints the stack trace on the server
console
○ Same applies to the check and checkpanic operations
resource function get info(http:Request request) returns error? {
string value = check request.getHeader("Some-type");
}
HTTP Client changes records
○ Response payload retrieval
○ Headers, Media type arguments
Response payload retrieval
○ The client operation is able to infer the expected payload type from the
LHS variable type
○ Can access the payload as one of the following types:string, json, xml,
byte[], record, record[].
public function main() returns error? {
http:Client httpClient = check new ("https://person.free.beeceptor.com");
json payload = check httpClient->get("/data");
io:println(payload);
}
Response payload retrieval
○ In case of using var as return type, user can pass the typedesc to the
targetType argument
public function main() returns error? {
http:Client httpClient = check new ("https://person.free.beeceptor.com");
var payload = check httpClient->get("/data", targetType = json);
io:println(payload);
}
Response payload retrieval and 4XX, 5XX responses
○ When the user expects the payload extraction to be happened, the
erroneous HTTP responses (4XX and 5XX status code range) are returned
as ballerina error of the client operation.
public function main() returns error? {
http:Client httpClient = check new ("https://www.google.com/");
json|error result = httpClient->get("/wrongPath");
if result is http:ClientRequestError {
io:println(result.message());
}
}
Response payload retrieval and 4XX, 5XX responses
○ When the user expects the payload extraction to be happened, the
erroneous HTTP responses (4XX and 5XX status code range) are returned
as ballerina error of the client operation.
public function main() returns error? {
http:Client httpClient = check new ("https://www.google.com/");
json|error result = httpClient->get("/wrongPath");
if result is http:ClientRequestError {
int statusCode = result.detail().statusCode;
anydata payload = result.detail().body;
map<string[]> headers = result.detail().headers;
}
}
Payload binding
○ When the response payload type is JSON, you can simply convert a
compatible payload into ballerina record or record[] by having it as return
type
public function main() returns error? {
http:Client httpClient = check new ("https://person.free.beeceptor.com");
Person payload = check httpClient->get("/data");
io:println("Hello " + payload.name + "!nJoining from " + payload.address.state);
}
Usual response
○ Whenever user needs to dig into HTTP headers and response properties
along with the payload, it can still be done by having http:Response as the
expected type.
public function main() returns error? {
http:Client httpClient = check new ("https://person.free.beeceptor.com");
http:Response response = check httpClient->get("/data");
string mediaType = check response.getHeader("Content-type");
io:println("Media: " + mediaType);
io:println("Status: " + response.statusCode.toString());
}
Sending headers in the Client operation
○ Non entity body methods - GET, HEAD, OPTIONS
public function main() returns error? {
http:Client httpClient = check new ("https://www.example.com");
map<string|string[]> headers = {
"my-header": "my-header-value",
"header-2": ["foo", "bar"]
};
string resp = check httpClient->get("/data", headers);
}
Sending headers in the Client operation
○ Entity body methods - POST, PUT, DELETE, PATCH
public function main() returns error? {
http:Client httpClient = check new ("https://www.example.com");
string response = check httpClient->post("/some/endpoint",
{
name: "foo",
age: 25,
address: "area 51"
},
headers = {
"my-header": "my-header-value"
}
mediaType = "application/json",
);
}
Covered so far
➔ Service,
◆ Declaration, Absolute resource path
➔ Resource
◆ Accessor, Name, Path param, Rest param
◆ Signature param such as query, payload, header, caller and
request
◆ Return types
➔ Client
◆ Response payload retrieval
◆ Headers, Media type arguments in client operation
HTTP Security
HTTP Security
Service
● Transport Layer Security
○ SSL/TLS
○ mTLS
● Application Layer Security
○ Basic auth
■ File user store
■ LDAP user store
○ JWT auth
○ OAuth2
Client
● Transport Layer Security
○ SSL/TLS
○ mTLS
● Application Layer Security
○ Basic auth
○ JWT auth
■ Bearer token
■ Self signed token
○ OAuth2
■ Bearer token
■ Client credentials grant type
■ Password grant type
■ Refresh token grant type
How to configure service? import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
// ...
}
);
@http:ServiceConfig {
auth: [
// ...
]
}
service /foo on securedEP {
@http:ResourceConfig {
auth: [
// ...
]
}
resource function get bar() returns string {
return "Hello, World!";
}
}
○ Listener configurations
○ Secure socket config
○ Service configurations
○ Auth config
○ Resource configurations
○ Auth config
How to configure client? import ballerina/http;
http:Client securedEP = check new("https://localhost:9090",
secureSocket = {
// ...
},
auth = {
// ...
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
○ Client configurations
○ Secure socket config
○ Auth config
HTTP Service
Transport Layer Security
Service - SSL/TLS import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
}
}
);
service /foo on securedEP {
resource function get bar() returns string {
return "Hello, World!";
}
}
Service - mTLS import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
},
mutualSsl: {
cert: "/path/to/client-public.crt"
}
}
);
service /foo on securedEP {
resource function get bar() returns string {
return "Hello, World!";
}
}
HTTP Service
Application Layer Security
Authentication
Service - Basic auth
(File user store)
import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
}
}
);
@http:ServiceConfig {
auth: [
{
fileUserStoreConfig: {}
}
]
}
service /foo on securedEP {
resource function get bar() returns string {
return "Hello, World!";
}
}
Config.toml
[[ballerina.auth.users]]
username="alice"
password="alice@123"
scopes=["developer"]
[[ballerina.auth.users]]
username="bob"
password="bob@123"
scopes=["developer", "admin"]
[[ballerina.auth.users]]
Service - Basic auth
(LDAP user store)
import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
}
}
);
@http:ServiceConfig {
auth: [
{
ldapUserStoreConfig: {
domainName: "avix.lk",
connectionUrl: "ldap://localhost:389",
// ...
}
}
]
}
service /foo on securedEP {
resource function get bar() returns string {
return "Hello, World!";
}
}
Service - JWT auth
Signature validation
○ Public certificate
signatureConfig: {
certFile: "/path/to/public.crt"
}
○ Truststore
trustStoreConfig: {
trustStore: {
path: "/path/to/truststore.p12",
password: "password"
},
certAlias: "alias"
}
import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
}
}
);
@http:ServiceConfig {
auth: [
{
jwtValidatorConfig: {
issuer: "wso2",
audience: "ballerina",
signatureConfig: {
certFile: "/path/to/public.crt"
}
}
}
]
}
service /foo on securedEP {
resource function get bar() returns string {
return "Hello, World!";
}
}
Service - OAuth2 import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
}
}
);
@http:ServiceConfig {
auth: [
{
oauth2IntrospectionConfig: {
url: "https://localhost:9445/oauth2/introspect",
tokenTypeHint: "access_token"
clientConfig: {
secureSocket: {
cert: "/path/to/public.crt"
}
}
}
}
]
}
service /foo on securedEP {
resource function get bar() returns string {
return "Hello, World!";
}
}
HTTP Service
Application Layer Security
Authorization
Service - JWT auth import ballerina/http;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
}
}
);
@http:ServiceConfig {
auth: [
{
jwtValidatorConfig: {
issuer: "wso2",
audience: "ballerina",
signatureConfig: {
certFile: "/path/to/public.crt"
}
},
scopes: ["admin"],
}
]
}
service /foo on securedEP {
resource function get bar() returns string {
return "Hello, World!";
}
}
HTTP Client
Transport Layer Security
Client - SSL/TLS import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
secureSocket = {
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
Client - mTLS import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
secureSocket = {
key: {
certFile: "/path/to/server-public.crt",
keyFile: "/path/to/server-private.key"
},
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
HTTP Client
Application Layer Security
Client - Basic auth import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
auth = {
username: "alice",
password: "alice@123"
},
secureSocket = {
cert: "../resource/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
Client - JWT auth import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
auth = {
token: "eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ...."
},
secureSocket = {
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
○ Bearer token
○ Self-signed token
Client - JWT auth import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
auth = {
username: "ballerina",
issuer: "wso2",
audience: ["ballerina", "ballerina.io"],
keyId: "5a0b754-895f-4279-8843-b745e11a57e9",
jwtId: "JlbmMiOiJBMTI4Q0JDLUhTMjU2In",
customClaims: { "scp": "admin" },
expTime: 3600,
signatureConfig: {
config: {
keyFile: "/path/to/private.key"
}
}
},
secureSocket = {
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
○ Bearer token
○ Self-signed token
Client - OAuth2 import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
auth = {
token: "56ede317-4511-44b4-8579-a08f094ee8c5"
},
secureSocket = {
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
○ Bearer token
○ Client credentials grant type
○ Password grant type
○ Refresh token grant type
Client - OAuth2 import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
auth = {
tokenUrl: "https://localhost:9445/oauth2/token",
clientId: "FlfJYKBD2c925h4lkycqNZlC2l4a",
clientSecret: "PJz0UhTJMrHOo68QQNpvnqAY_3Aa",
scopes: ["admin"],
clientConfig: {
secureSocket: {
cert: "/path/to/public.crt"
}
}
},
secureSocket = {
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
○ Bearer token
○ Client credentials grant type
○ Password grant type
○ Refresh token grant type
Client - OAuth2 import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
auth = {
tokenUrl: "https://localhost:9445/oauth2/token",
username: "admin",
password: "admin",
clientId: "FlfJYKBD2c925h4lkycqNZlC2l4a",
clientSecret: "PJz0UhTJMrHOo68QQNpvnqAY_3Aa",
scopes: ["admin"],
refreshConfig: {
refreshUrl: "https://localhost:9445/oauth2/token",
scopes: ["hello"],
clientConfig: {
secureSocket: {
cert: "/path/to/public.crt"
}
}
},
clientConfig: {
secureSocket: {
cert: "/path/to/public.crt"
}
}
},
secureSocket = {
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
○ Bearer token
○ Client credentials grant type
○ Password grant type
○ Refresh token grant type
Client - OAuth2 import ballerina/http;
import ballerina/io;
http:Client securedEP = check new("https://localhost:9090",
auth = {
refreshUrl: "https://localhost:9445/oauth2/token",
refreshToken: "24f19603-8565-4b5f-a036-88a945e1f272",
clientId: "FlfJYKBD2c925h4lkycqNZlC2l4a",
clientSecret: "PJz0UhTJMrHOo68QQNpvnqAY_3Aa",
scopes: ["admin"],
clientConfig: {
secureSocket: {
cert: "/path/to/public.crt"
}
}
},
secureSocket = {
cert: "/path/to/public.crt"
}
);
public function main() returns error? {
string response = check securedEP->get("/foo/bar");
io:println(response);
}
○ Bearer token
○ Client credentials grant type
○ Password grant type
○ Refresh token grant type
Imperative vs Declarative
Service - JWT auth (Imperative method)
import ballerina/http;
import ballerina/jwt;
listener http:Listener securedEP = new(9090,
secureSocket = {
key: {
certFile: "/path/to/public.crt",
keyFile: "/path/to/private.key"
}
}
);
http:ListenerJwtAuthHandler handler = new({
issuer: "wso2",
audience: "ballerina",
signatureConfig: {
certFile: "/path/to/public.crt"
},
scopeKey: "scp"
});
Service - JWT auth (Imperative method)
service /foo on securedEP {
resource function get bar(@http:Header { name: "Authorization" } string header) returns
string|http:Unauthorized|http:Forbidden {
jwt:Payload|http:Unauthorized authn = handler.authenticate(header);
if (authn is http:Unauthorized) {
return authn;
}
http:Forbidden? authz = handler.authorize(<jwt:Payload> authn, ["write", "update"]);
if (authz is http:Forbidden) {
return authz;
}
return "Hello, World!";
}
}
Open Discussion

[Community Call] Ballerina Swan Lake HTTP Module Changes

  • 1.
    Community Call -July 2021 Changes to the HTTP Package in Ballerina Swan Lake
  • 2.
    Agenda - Getting involvedand showing your support - https://github.com/ballerina-platform/community - Changes to the HTTP package in Swan Lake - Changes to service and client implementations - Chamil Elladeniya - Security changes - Chanaka Lakmal - QnA
  • 3.
    Changes to HTTPPackage in Swan Lake July 2021
  • 4.
    Outline ➔ Service, ◆ Declaration,Absolute resource path ➔ Resource ◆ Accessor, Name, Path param, Rest param ◆ Signature params such as query, payload, header, caller and request ◆ Return types ➔ Client ◆ Response payload retrieval ◆ Headers, Media type arguments in client operation ➔ Security
  • 5.
    Service declaration A syntacticsugar for creating services. Gets desugared into creating a listener object, creating a service object, attaching the service object to the listener object, etc,. service-declaration := "service" [[type-descriptor] variable-name] "on" expression-list etc service http:Service /basePath on new http:Listener(9090) { resource function get foo() returns string { return "hello world"; } }
  • 6.
    Service class declaration serviceisolated class SClass { *http:Service; isolated resource function get greeting() returns string { return "hello world"; } } listener http:Listener serviceListener = new (9090); http:Service httpService = new SClass(); public function main() { error? err1 = serviceListener.attach(httpService, ["foo", "bar"]); error? err2 = serviceListener.start(); runtime:registerListener(serviceListener); }
  • 7.
    Service constructor expression listenerhttp:Listener serviceListener = new (9090); http:Service httpService = @http:ServiceConfig {} service object { resource function get baz() returns string { return "hello world"; } }; public function main() { error? err1 = serviceListener.attach(httpService, "/foo/bar"); error? err2 = serviceListener.start(); runtime:registerListener(serviceListener); }
  • 8.
    Absolute resource path(basePath) ○ Identifiers and String literals are allowed. ○ Starts with “/” ○ Optional and defaults to “/” service /hello on new http:Listener(9090) { resource function post test() { //... } } service “/hello” on new http:Listener(9090) { resource function post test() { //... } }
  • 9.
    Absolute resource path ○Names with special characters service http:Service /my/Tes@tHello/go new http:Listener(9090) { resource function get foo() { //... } } service http:Service "/Tes@tHello/go" new http:Listener(9090) { resource function get foo() { //... } }
  • 10.
    Resource changes ○ Accessor,Name, Path param, Rest param ○ Signature params such as query, payload, header, caller and request ○ Return types
  • 11.
    Resource methods ○ Resourcemethod has improved in terms of accessor, path and return value type resource-accessor-defn := metadata "resource" "function" accessor-name resource-name function-signature method-defn-body resource function get foo(string bar) returns http:Response { http:Response resp = new; return resp; }
  • 12.
    Resource accessor ○ Confinesthe resource to a particular HTTP method ○ In additions to the standard HTTP methods, custom methods are also supported. ○ default identifier can be used to allow any HTTP method resource function get foo() { //... } resource function ’default foo() { //... }
  • 13.
    Resource name ○ Therelative path from the absolute path ○ Path identifiers separated by “/” ○ Dot identifier is used to denote the “/” resource function get foo/bar/baz() { //... } service /foo on http:Listener(9090) { resource function get .() { // curl localhost:9090/foo will get dispatched here //... } }
  • 14.
    Path param ○ Includein the resource name itself. string, int, boolean, float, decimal types are supported resource function get data/[int age]/[string name]/[boolean status]/[float weight]() returns json { int balAge = age + 1; float balWeight = weight + 2.95; string balName = name + " lang"; if (status) { balName = name; } json responseJson = { Name:name, Age:balAge, Weight:balWeight, Status:status, Lang: balName}; return responseJson; }
  • 15.
    Rest param ○ Allowsmultiple path segments to be dispatched in the absence of a specific path. string, int, boolean, float, decimal types are supported ○ Rest parameter must be the last segment of resource path resource function get foo/[string... s]() returns json { json responseJson = {"echo":s[0]}; return responseJson; }
  • 16.
    Use case: Defaultresource ○ Allows any HTTP method. ○ Allow any path resource function 'default [string... s]() returns json { //... }
  • 17.
    Resource signature parameters ○No compulsory parameters ○ No annotation required for Query param, Caller and Request ○ New additions - Payload, Header, Query resource function XXX NAME_TEMPLATE ([@http:CallerInfo {} http:Caller hc], [http:Request req], [somedatatype queryParam]*, (@http:Payload somedatatype payload)?, (@http:Header somedatatype header)?) { //... }
  • 18.
    Conceptual change ○ 1stchoice should be to use signature params and use returns. Avoid caller unless you have specific requirement. ○ Use data binding, header params and resource returns.
  • 19.
    Query param ○ Doesnot associated with any annotation or additional detail. The type of query param are as follows. type BasicType boolean|int|float|decimal|string; public type QueryParamType ()|BasicType|BasicType[]; resource function get q1(int id, string PersoN, float val, boolean isPresent, decimal dc) { //... } resource function get q2(int? id, string[] PersoN, float[]? val) { //... }
  • 20.
    Payload param ○ Thepayload parameter eases access of request payload during the resource invocation. The payload param is defined with @http:Payload annotation public type PayloadType string | json | xml | byte[] | record {| anydata...; |}| record {| anydata...; |}[]; resource function post body(@http:Payload Person person) returns json { return {"Person": person}; }
  • 21.
    Header param ○ Theheader parameter is to access the request headers The header param is defined with @http:Header annotation service sample on new http:Listener(9090) { //Single header value extraction resource function post hello1(@http:Header string referer) { } //Multiple header value extraction resource function post hello2(@http:Header {name: "Accept"} string[] accept) { } //The http:Header object contains all the headers resource function get hello3(http:Headers headers) { String|error referer = headers.getHeader("Referer"); String[]|error accept = headers.getHeaders("Accept"); String[] keys = headers.getHeaderNames(); } }
  • 22.
    CallerInfo annotation ○ Theannotation defines the response payload type. resource function post foo(@http:CallerInfo {respondType: Person} http:Caller caller) returns error?{ Person p = {}; error? = caller->respond(p); }
  • 23.
    Resource return type ○anydata|http:Response|http:StatusCodeResponse|error|() ○ Can decorate with @http:Payload annotation and result in 200 OK resource function get test() returns @http:Payload {mediaType:"text/id+plain"} string { return "world"; } resource function get test2() returns PersonTable[]? { PersonTable tbPerson = table [ {id: 1, name: "John", salary: 300.50}, {id: 2, name: "Bella", salary: 500.50} ]; return [tbPerson, tbPerson]; }
  • 24.
    StatusCodeResponse records ○ Builtin records for all status codes public type Ok record { readonly StatusOk status; string mediaType; map<string|string[]> headers?; anydata body?; }; resource function get greeting() returns http:Ok|http:InternalServerError { http:Ok ok = { body: "hello world", headers: { xtest: "foo"} }; return ok; }
  • 25.
    StatusCodeResponse records ○ Createan inline response ○ Improve readability & helps OpenAPI spec generation type Person record { string name; }; resource function put person(string name) returns record {|*http:Created; Person body;|} { Person person = {name:name}; return { mediaType: "application/person+json", headers: { "X-Server": "myServer" }, body: person }; }
  • 26.
    Return nil ○ Ifresource wants to return nothing, the listener will return 202 Accepted response resource function post person(@http:Payload Person p) { int age = p.age; io:println(string `Age is: ${age}`); }
  • 27.
    Return nil ○ Ifthe 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. resource function get fruit(string? colour, http:Caller caller) { if colour == "red" { error? result = caller->respond("Sending apple"); return; // ending the flow, so not 202 response } error? result = caller->respond("Sending orange"); }
  • 28.
    Return nil inerror case ○ 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 resource function get fruit(string? colour, http:Caller caller) { if colour == "red" { error? result = caller->respond("Sending apple"); return; // ending the flow } return; // 500 internal Server Error }
  • 29.
    Return error fromthe resource ○ Will lead to a 500 internal error and prints the stack trace on the server console ○ Same applies to the check and checkpanic operations resource function get info(http:Request request) returns error? { string value = check request.getHeader("Some-type"); }
  • 30.
    HTTP Client changesrecords ○ Response payload retrieval ○ Headers, Media type arguments
  • 31.
    Response payload retrieval ○The client operation is able to infer the expected payload type from the LHS variable type ○ Can access the payload as one of the following types:string, json, xml, byte[], record, record[]. public function main() returns error? { http:Client httpClient = check new ("https://person.free.beeceptor.com"); json payload = check httpClient->get("/data"); io:println(payload); }
  • 32.
    Response payload retrieval ○In case of using var as return type, user can pass the typedesc to the targetType argument public function main() returns error? { http:Client httpClient = check new ("https://person.free.beeceptor.com"); var payload = check httpClient->get("/data", targetType = json); io:println(payload); }
  • 33.
    Response payload retrievaland 4XX, 5XX responses ○ When the user expects the payload extraction to be happened, the erroneous HTTP responses (4XX and 5XX status code range) are returned as ballerina error of the client operation. public function main() returns error? { http:Client httpClient = check new ("https://www.google.com/"); json|error result = httpClient->get("/wrongPath"); if result is http:ClientRequestError { io:println(result.message()); } }
  • 34.
    Response payload retrievaland 4XX, 5XX responses ○ When the user expects the payload extraction to be happened, the erroneous HTTP responses (4XX and 5XX status code range) are returned as ballerina error of the client operation. public function main() returns error? { http:Client httpClient = check new ("https://www.google.com/"); json|error result = httpClient->get("/wrongPath"); if result is http:ClientRequestError { int statusCode = result.detail().statusCode; anydata payload = result.detail().body; map<string[]> headers = result.detail().headers; } }
  • 35.
    Payload binding ○ Whenthe response payload type is JSON, you can simply convert a compatible payload into ballerina record or record[] by having it as return type public function main() returns error? { http:Client httpClient = check new ("https://person.free.beeceptor.com"); Person payload = check httpClient->get("/data"); io:println("Hello " + payload.name + "!nJoining from " + payload.address.state); }
  • 36.
    Usual response ○ Wheneveruser needs to dig into HTTP headers and response properties along with the payload, it can still be done by having http:Response as the expected type. public function main() returns error? { http:Client httpClient = check new ("https://person.free.beeceptor.com"); http:Response response = check httpClient->get("/data"); string mediaType = check response.getHeader("Content-type"); io:println("Media: " + mediaType); io:println("Status: " + response.statusCode.toString()); }
  • 37.
    Sending headers inthe Client operation ○ Non entity body methods - GET, HEAD, OPTIONS public function main() returns error? { http:Client httpClient = check new ("https://www.example.com"); map<string|string[]> headers = { "my-header": "my-header-value", "header-2": ["foo", "bar"] }; string resp = check httpClient->get("/data", headers); }
  • 38.
    Sending headers inthe Client operation ○ Entity body methods - POST, PUT, DELETE, PATCH public function main() returns error? { http:Client httpClient = check new ("https://www.example.com"); string response = check httpClient->post("/some/endpoint", { name: "foo", age: 25, address: "area 51" }, headers = { "my-header": "my-header-value" } mediaType = "application/json", ); }
  • 39.
    Covered so far ➔Service, ◆ Declaration, Absolute resource path ➔ Resource ◆ Accessor, Name, Path param, Rest param ◆ Signature param such as query, payload, header, caller and request ◆ Return types ➔ Client ◆ Response payload retrieval ◆ Headers, Media type arguments in client operation
  • 40.
  • 41.
    HTTP Security Service ● TransportLayer Security ○ SSL/TLS ○ mTLS ● Application Layer Security ○ Basic auth ■ File user store ■ LDAP user store ○ JWT auth ○ OAuth2 Client ● Transport Layer Security ○ SSL/TLS ○ mTLS ● Application Layer Security ○ Basic auth ○ JWT auth ■ Bearer token ■ Self signed token ○ OAuth2 ■ Bearer token ■ Client credentials grant type ■ Password grant type ■ Refresh token grant type
  • 42.
    How to configureservice? import ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { // ... } ); @http:ServiceConfig { auth: [ // ... ] } service /foo on securedEP { @http:ResourceConfig { auth: [ // ... ] } resource function get bar() returns string { return "Hello, World!"; } } ○ Listener configurations ○ Secure socket config ○ Service configurations ○ Auth config ○ Resource configurations ○ Auth config
  • 43.
    How to configureclient? import ballerina/http; http:Client securedEP = check new("https://localhost:9090", secureSocket = { // ... }, auth = { // ... } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); } ○ Client configurations ○ Secure socket config ○ Auth config
  • 44.
  • 45.
    Service - SSL/TLSimport ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" } } ); service /foo on securedEP { resource function get bar() returns string { return "Hello, World!"; } }
  • 46.
    Service - mTLSimport ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" }, mutualSsl: { cert: "/path/to/client-public.crt" } } ); service /foo on securedEP { resource function get bar() returns string { return "Hello, World!"; } }
  • 47.
    HTTP Service Application LayerSecurity Authentication
  • 48.
    Service - Basicauth (File user store) import ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" } } ); @http:ServiceConfig { auth: [ { fileUserStoreConfig: {} } ] } service /foo on securedEP { resource function get bar() returns string { return "Hello, World!"; } } Config.toml [[ballerina.auth.users]] username="alice" password="alice@123" scopes=["developer"] [[ballerina.auth.users]] username="bob" password="bob@123" scopes=["developer", "admin"] [[ballerina.auth.users]]
  • 49.
    Service - Basicauth (LDAP user store) import ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" } } ); @http:ServiceConfig { auth: [ { ldapUserStoreConfig: { domainName: "avix.lk", connectionUrl: "ldap://localhost:389", // ... } } ] } service /foo on securedEP { resource function get bar() returns string { return "Hello, World!"; } }
  • 50.
    Service - JWTauth Signature validation ○ Public certificate signatureConfig: { certFile: "/path/to/public.crt" } ○ Truststore trustStoreConfig: { trustStore: { path: "/path/to/truststore.p12", password: "password" }, certAlias: "alias" } import ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" } } ); @http:ServiceConfig { auth: [ { jwtValidatorConfig: { issuer: "wso2", audience: "ballerina", signatureConfig: { certFile: "/path/to/public.crt" } } } ] } service /foo on securedEP { resource function get bar() returns string { return "Hello, World!"; } }
  • 51.
    Service - OAuth2import ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" } } ); @http:ServiceConfig { auth: [ { oauth2IntrospectionConfig: { url: "https://localhost:9445/oauth2/introspect", tokenTypeHint: "access_token" clientConfig: { secureSocket: { cert: "/path/to/public.crt" } } } } ] } service /foo on securedEP { resource function get bar() returns string { return "Hello, World!"; } }
  • 52.
    HTTP Service Application LayerSecurity Authorization
  • 53.
    Service - JWTauth import ballerina/http; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" } } ); @http:ServiceConfig { auth: [ { jwtValidatorConfig: { issuer: "wso2", audience: "ballerina", signatureConfig: { certFile: "/path/to/public.crt" } }, scopes: ["admin"], } ] } service /foo on securedEP { resource function get bar() returns string { return "Hello, World!"; } }
  • 54.
  • 55.
    Client - SSL/TLSimport ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", secureSocket = { cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); }
  • 56.
    Client - mTLSimport ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", secureSocket = { key: { certFile: "/path/to/server-public.crt", keyFile: "/path/to/server-private.key" }, cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); }
  • 57.
  • 58.
    Client - Basicauth import ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", auth = { username: "alice", password: "alice@123" }, secureSocket = { cert: "../resource/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); }
  • 59.
    Client - JWTauth import ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", auth = { token: "eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ...." }, secureSocket = { cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); } ○ Bearer token ○ Self-signed token
  • 60.
    Client - JWTauth import ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", auth = { username: "ballerina", issuer: "wso2", audience: ["ballerina", "ballerina.io"], keyId: "5a0b754-895f-4279-8843-b745e11a57e9", jwtId: "JlbmMiOiJBMTI4Q0JDLUhTMjU2In", customClaims: { "scp": "admin" }, expTime: 3600, signatureConfig: { config: { keyFile: "/path/to/private.key" } } }, secureSocket = { cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); } ○ Bearer token ○ Self-signed token
  • 61.
    Client - OAuth2import ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", auth = { token: "56ede317-4511-44b4-8579-a08f094ee8c5" }, secureSocket = { cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); } ○ Bearer token ○ Client credentials grant type ○ Password grant type ○ Refresh token grant type
  • 62.
    Client - OAuth2import ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", auth = { tokenUrl: "https://localhost:9445/oauth2/token", clientId: "FlfJYKBD2c925h4lkycqNZlC2l4a", clientSecret: "PJz0UhTJMrHOo68QQNpvnqAY_3Aa", scopes: ["admin"], clientConfig: { secureSocket: { cert: "/path/to/public.crt" } } }, secureSocket = { cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); } ○ Bearer token ○ Client credentials grant type ○ Password grant type ○ Refresh token grant type
  • 63.
    Client - OAuth2import ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", auth = { tokenUrl: "https://localhost:9445/oauth2/token", username: "admin", password: "admin", clientId: "FlfJYKBD2c925h4lkycqNZlC2l4a", clientSecret: "PJz0UhTJMrHOo68QQNpvnqAY_3Aa", scopes: ["admin"], refreshConfig: { refreshUrl: "https://localhost:9445/oauth2/token", scopes: ["hello"], clientConfig: { secureSocket: { cert: "/path/to/public.crt" } } }, clientConfig: { secureSocket: { cert: "/path/to/public.crt" } } }, secureSocket = { cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); } ○ Bearer token ○ Client credentials grant type ○ Password grant type ○ Refresh token grant type
  • 64.
    Client - OAuth2import ballerina/http; import ballerina/io; http:Client securedEP = check new("https://localhost:9090", auth = { refreshUrl: "https://localhost:9445/oauth2/token", refreshToken: "24f19603-8565-4b5f-a036-88a945e1f272", clientId: "FlfJYKBD2c925h4lkycqNZlC2l4a", clientSecret: "PJz0UhTJMrHOo68QQNpvnqAY_3Aa", scopes: ["admin"], clientConfig: { secureSocket: { cert: "/path/to/public.crt" } } }, secureSocket = { cert: "/path/to/public.crt" } ); public function main() returns error? { string response = check securedEP->get("/foo/bar"); io:println(response); } ○ Bearer token ○ Client credentials grant type ○ Password grant type ○ Refresh token grant type
  • 65.
  • 66.
    Service - JWTauth (Imperative method) import ballerina/http; import ballerina/jwt; listener http:Listener securedEP = new(9090, secureSocket = { key: { certFile: "/path/to/public.crt", keyFile: "/path/to/private.key" } } ); http:ListenerJwtAuthHandler handler = new({ issuer: "wso2", audience: "ballerina", signatureConfig: { certFile: "/path/to/public.crt" }, scopeKey: "scp" });
  • 67.
    Service - JWTauth (Imperative method) service /foo on securedEP { resource function get bar(@http:Header { name: "Authorization" } string header) returns string|http:Unauthorized|http:Forbidden { jwt:Payload|http:Unauthorized authn = handler.authenticate(header); if (authn is http:Unauthorized) { return authn; } http:Forbidden? authz = handler.authorize(<jwt:Payload> authn, ["write", "update"]); if (authz is http:Forbidden) { return authz; } return "Hello, World!"; } }
  • 68.