Alpha Anywhere Service Endpoint Definition
- Top Level Defaults Pattern to use for API
- API definition
- Method override definition
- Authorize Definition
- CORS Definition
- Binary Types
- Example MongoDb Collection exposed as a REST API
- Definition file for class
- Example CURL calls to Endpoint
- Example usage of 'Auth'.
- Example usage of 'Auth_Type'.
- CORS settings
- Custom CORS behaviour
Description
Files with the .a5svc extension is used to describe how an xbasic or node class or api is exposed as a REST service.
Top Level Defaults Pattern to use for API
The top level fields in the service definition file include the defaults for pattern of HTTP REST endpoints to use.
Contains multiple APIs and an optional authentication.
- Name
- Property and Description
- use_query
Default behavior for method endpoints - use query parameters to store arguments.
- flatten_arguments
Omit the 'arguments.' prefix from arguments passed on command line or in posted data.
- path
Default path template.
- Template
- Description
- {service}
Placeholder for 'service' name.
- {method}
Placeholder for 'method' name.
- {properties.*}
Placeholder for property '*' is the name of the class instance property to set .
- {arguments.*}
Placeholder for argument '*' is the name of the method argument to set .
- api
Array of all the xbasic classes and/or node apis that endpoint exposes.
- auth_type
Array of all the auth types used by this service (see Authorize Definition), names are required for entries.
- auth
Global Xbasic class or node api that implements "Authorize" method for all api entries (see Authorize Definition).
- debugger_header
If set, enable debugging of the endpoint using the agent.
API definition
The Api definition level defines the xbasic classes and node apis to expose as endpoints.
API level definition can include default patterns to use to expose methods.
- Name
- Property and Description
- implementation
Implementation of Service, "xbasic" and "node".
- service
Name of node service api or xbasic class to expose as an api.
- use_query
Default behavior for method endpoints on this API - use query parameters to store arguments. Implies 'GET' method.
- flatten_arguments
Omit the 'arguments.' prefix from arguments passed on command line or in posted data.
- allow
If API defines method overrides, fallback to the default pattern if method override is not defined (default to true).
- allow_empty
Set to false if path parameters must match at least one character - i.e. for a path /account/{id} , id would need to have at least one character.
- path
Default path template.
- Template
- Description
- {method}
Placeholder for 'method' name.
- {properties.*}
Placeholder for property '*' is the name of the class instance property to set .
- {arguments.*}
Placeholder for argument '*' is the name of the method argument to set .
- methods
Method overrides to the default pattern.
Method override definition
Method overrides allow definition of pattern to use for individual methods on a xbasic class or node api.
- Name
- Property and Description
- name
Name of method to override.
- path
Path template.
- Template
- Description
- {properties.*}
Placeholder for property '*' is the name of the class instance property to set .
- {arguments.*}
Placeholder for argument '*' is the name of the method argument to set .
- method
HTTP method to use for this endpoint (i.e. GET/POST/PUT/DELETE/PATCH).
- use_query
Behavior for this endpoints - use query parameters to store arguments.
- flatten_arguments
Omit the 'arguments.' prefix from arguments passed on command line or in posted data.
- single_argument
For methods that have a single argument, this specifies the name of this argument, and places the content of the request into it.
- argument_mapping
Override argument names for this method
- body_encoding
Set the encoding for the body when data is POST or PUT, defaults to json if ommitted.
- header_arguments
Define which input parameters are passed in HTTP headers.
- Template
- Description
- header_key_name
Name of the header 'key'.
- argument_template
Template to populate elements from.
- Template
- Description
- {properties.*}
Placeholder for property '*' is the name of the class instance property to set .
- {arguments.*}
Placeholder for argument '*' is the name of the method argument to set .
- binary
Determine how binary content passed to method is handled for the request (see Binary types)
- response_binary
Determine how binary content returned from method is handled for the request (see Binary types)
- auth
Name of auth type to use for this method.
Authorize Definition
Authorize definition provide the binding of the API call to handle the authentication of the request.
- Name
- Property and Description
- name
Name of the Authorization.
- implementation
Implementation of Authorize method, "xbasic" and "node".
- service
Name of node service api or xbasic class that implements the Authorize method.
- authorize
Override the name of the method used to authorize (will assume the method is called 'Authorize' otherwise).
- path
Path template.
- Template
- Description
- {properties.*}
Placeholder for property '*' is the name of the class instance property to set .
- {arguments.*}
Placeholder for argument '*' is the name of the method argument to set .
- use_query
Behavior for this endpoints - use query parameters to store arguments (i.e. if api_key is passed as a query parameter).
- flatten_arguments
Omit the 'arguments.' prefix from arguments passed on command line or in posted data.
- single_argument
For methods that have a single argument, this specifies the name of this argument, and places the content of the request into it.
- argument_mapping
Override argument names for this method
- header_arguments
Define which input parameters are passed in HTTP headers.
- Template
- Description
- header_key_name
Name of the header 'key'.
- argument_template
Template to populate elements from.
- Template
- Description
- {properties.*}
Placeholder for property '*' is the name of the class instance property to set .
- {arguments.*}
Placeholder for argument '*' is the name of the method argument to set .
- binary
Binary Types
CORS Definition
Specifies Cross Origin Resource Sharing settings for the endpoint.
- headers
String containing comma separated List of allowed headers, use '*' for allow all requested headers.
Binary Types
- Name
- Property and Description
- multipart
Binary content passed as multipart form elements.
- raw
Binary content passed as raw content (requires a single binary return value).
- base64
Binary content inline as base64 encoded text.
- bson
Binary content inline bson format (JSON representation of BSON binary record).
- array
Binary content represented as inline byte arrays.
- javascript
Binary content represented as jaavscript.
Example MongoDb Collection exposed as a REST API
A class that defines methods to get and add, update and delete records from a mongo database, using the extension::MongoDB class.
define class restapi::customer function GetCustomers as p() dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","alphasports","Customer") GetCustomers = json_parse(mongo.GetRecords("")) end function function GetCustomer as p(id as c) dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","alphasports","Customer") dim json as c = mongo.GetRecords("{ \"CUSTOMER_ID\" : \""+id+"\" }") if json <> "" then dim jso as extension::JSON jso.setJson(json) if jso.getLength() > 0 then dim jsi as extension::JSON = jso.getIndexed(0) GetCustomer = json_parse(jsi.getJson()) end if end if end function function AddCustomer as l(obj as p) if obj.data("CUSTOMER_ID") <> "" then dim json as c = json_generate(obj) dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","alphasports","Customer") Mongo.AddRecord(json) AddCustomer = .t. end if end function function DeleteCustomer as l(id as c) dim obj as p = GetCustomer(id) dim _id as c = obj.data("_id") if _id <> "" then dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","alphasports","Customer") mongo.DeleteRecord(_id) DeleteCustomer = .t. end if end function function UpdateCustomer as l(newObj as p) dim id as c = newObj.data("CUSTOMER_ID") if id <> "" then dim obj as p = GetCustomer(id) dim _id as c = obj.data("_id") if _id <> "" then property_recurse_assign(obj,newObj) dim json as c = json_generate(obj) dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","alphasports","Customer") Mongo.UpdateRecord(_id,json) UpdateCustomer = .t. end if end if end function end class
Definition file for class
Example Definition for mapping the REST api to a service endpoint.
{ "path": "/{method}", "method": "get", "api": [ { "implementation": "xbasic", "service": "restapi::customer", "methods": [ { "name": "AddCustomer", "path": "/AddCustomer", "method": "post", "single_argument" : "obj" }, { "name": "DeleteCustomer", "path": "/DeleteCustomer/{arguments.id}", "method": "delete" }, { "name": "GetCustomer", "path": "/GetCustomer/{arguments.id}", "method": "get" }, { "name": "GetCustomers", "path": "/GetCustomers", "method": "get" }, { "name": "UpdateCustomer", "path": "/UpdateCustomer", "method": "put", "single_argument" : "newObj" } ] } ] }
Example CURL calls to Endpoint
Get All Customer Records.
c:\>curl http://127.0.0.1:1580/LivePreview/customer.a5svc/GetCustomers [ { "_id": "55b2567b5590795052a1d171", "CUSTOMER_ID": "1000", "FIRSTNAME": "Cecelia", "LASTNAME": "DeLuca", "COMPANY": "Cartwright Industries", "PHONE": null, "FAX": null, ...
Add a new Customer record
c:\>curl --data "{ \"CUSTOMER_ID\" : \"99\" , \"FIRSTNAME\" : \"John\" }" http://127.0.0.1:1580/LivePreview/customer.a5svc/AddCustomer true
Get the record that was added, given the id used in CUSTOMER_ID.
c:\>curl http://127.0.0.1:1580/LivePreview/customer.a5svc/GetCustomer/99 { "_id": "59a31d36bc16f60290ba7f17", "CUSTOMER_ID": "99", "FIRSTNAME": "John" }
Delete the Customer Record that was created.
c:\>curl -X DELETE http://127.0.0.1:1580/LivePreview/customer.a5svc/DeleteCustomer/99 true
Example usage of 'Auth'.
In this example, we will have two classes, one that implements methods to call, which for this example is a class with a single method called greet.
define class noisy::talker function greet as c(name as c) 'DESCRIPTION:This is a function that greets the caller 'ARGUMENT(name):The name of the guy greet = "Hello "+name end function end class
Another class will implement a well known method called 'Authorize', which is called for definitions that include the optional 'auth' section. The Authorize method returns the HTTP code - if it returns a successful code (i.e. 200) the implementation class method (greet in this case) will get called, otherwise the result code gets immediately returned.
define class noisy::authorize function Authorize as n(api_key as c) if api_key = "bingo" then Authorize = 200 else Authorize = 403 end if end function end class
The definition enables the Authorization by defining an 'auth' at the top level that includes the class that implements authorization.
{ "path": "/{method}", "use_query": true, "flatten_arguments": true, "debugger_header": true, "method": "get", "auth" : { "implementation": "xbasic", "service": "noisy::authorize", "header_arguments" : [ { "header_key_name" : "api_key", "argument_template" : "{arguments.api_key}" } ] }, "api": [ { "implementation": "xbasic", "service": "noisy::talker", "methods": [ { "name": "greet", "path": "/greet/{arguments.name}", "method": "get" } ] } ] }
Calling the endpoint without the 'magic' api_key header value will result in a Forbidden call.
c:\>curl -X GET --header "Accept: text/html" "http://127.0.0.1:1580/LivePreview/talker.a5svc/greet/fred" --header "api_key: badkey" Forbidden c:\>curl -X GET --header "Accept: text/html" "http://127.0.0.1:1580/LivePreview/talker.a5svc/greet/fred" --header "api_key: bingo" "Hello fred"
Example usage of 'Auth_Type'.
In this example, A class defines two different auth methods.
define class A5RESTImpl::MultiAuth function authUser as n(api_key as c) if api_key = "bingo" .or. api_key = "souperyouser" then authUser = 200 else authUser = 401 end if end function function authAdmin as n(api_key as c) if api_key = "souperyouser" then authAdmin = 200 else authAdmin = 401 end if end function function ListUsers as p() if file.exists("c:\data\users.json") then ListUsers = json_parse(file.to_string("c:\data\users.json")) if ListUsers.size() > 0 then dim cleanup as c for i = ListUsers.size() to 1 step -1 if ListUsers[i].data("deleted") then ListUsers.delete(i) cleanup = .t. end if next if cleanup then file.from_string( "c:\data\users.json" , json_reformat(json_generate(ListUsers)) ) end if end if else ListUsers = json_parse("[]") end if end function function GetUserInfo as p(id as c) dim json as c = extension::json::FileJSONGet("c:\data\users.json","{ \"id\" : \""+id+"\" }") if json <> "" then GetUserInfo = json_parse(json) end if end function function AddUser as p(user as A5RESTImpl::MultiAuth::User) if user.id = "" then AddUser = json_parse(json_sanitize("{ added : false , error : 'Id is required' }")) else if extension::json::FileJSONGet("c:\data\users.json","{ \"id\" : \""+user.id+"\" }") = "" then extension::json::FileJSONAppend("c:\data\users.json",json_generate(user)) AddUser = json_parse(json_sanitize("{ added : true }")) else AddUser = json_parse(json_sanitize("{ added : false , error : 'User Already Exists' }")) end if end function function UpdateUserInfo as p(user as A5RESTImpl::MultiAuth::User) if extension::json::FileJSONGet("c:\data\users.json","{ \"id\" : \""+user.id+"\" }") = "" then AddUser = json_parse(json_sanitize("{ updated : false , error : 'User Not Found' }")) else extension::json::FileJSONUpdate("c:\data\users.json","{ \"id\" : \""+user.id+"\" }",json_generate(user)) AddUser = json_parse(json_sanitize("{ updated : true }")) end if end function function DeleteUserInfo as p(id as c) if extension::json::FileJSONGet("c:\data\users.json","{ \"id\" : \""+id+"\" }") = "" then AddUser = json_parse(json_sanitize("{ deleted : false , error : 'User Not Found' }")) else extension::json::FileJSONUpdate("c:\data\users.json","{ \"id\" : \""+id+"\" }","{ \"deleted\" : true } ") AddUser = json_parse(json_sanitize("{ deleted : true }")) end if end function end class define class A5RESTImpl::MultiAuth::User dim id as c dim firstname as c dim lastname as c end class
The example a5svc file for this service defines auth_types of user and admin. The methods in the interface name the method that they use (auth property of method).
{ "method": "get", "allow": false, "auth_type" : [ { "name" : "user", "implementation": "xbasic", "service": "A5RESTImpl::MultiAuth", "authorize": "authUser", "use_query" : true }, { "name" : "admin", "implementation": "xbasic", "service": "A5RESTImpl::MultiAuth", "authorize": "authAdmin", "use_query" : true } ], "api": [ { "implementation": "xbasic", "service": "A5RESTImpl::MultiAuth", "methods": [ { "name": "AddUser", "method": "post", "path": "/AddUser", "use_query": false, "auth" : "admin" }, { "name": "DeleteUserInfo", "method": "delete", "path": "/DeleteUserInfo/{arguments.id}", "use_query": false, "auth" : "admin" }, { "name": "GetUserInfo", "method": "get", "path": "/GetUserInfo/{arguments.id}", "use_query": false, "auth" : "user" }, { "name": "ListUsers", "method": "get", "path": "/ListUsers", "use_query": false, "auth" : "user" }, { "name": "UpdateUserInfo", "method": "put", "path": "/UpdateUserInfo", "use_query": false, "auth" : "admin" } ] } ], "flatten_arguments": true, "debugger_header": false }
Calling the api, notice that the api_key required for access varies between methods. DeleteUserInfo requires a different key than the ListUsers endpoint.
c:\>curl -X GET "http://127.0.0.1:1580/LivePreview/MultiAuth.a5svc/ListUsers" -H "accept: application/json" -H "content-type: application/json" Unauthorized c:\>curl -X GET "http://127.0.0.1:1580/LivePreview/MultiAuth.a5svc/ListUsers?api_key=bingo" -H "accept: application/json" -H "content-type: application/json" [ { "id": "jqp", "firstname": "John", "lastname": "Public" }, { "id": "js", "firstname": "Joe", "lastname": "Shmoe" } ] c:\>curl -X DELETE "http://127.0.0.1:1580/LivePreview/MultiAuth.a5svc/DeleteUserInfo/js?api_key=bingo" -H "accept: application/json" -H "content-type: application/json" Unauthorized c:\>curl -X DELETE "http://127.0.0.1:1580/LivePreview/MultiAuth.a5svc/DeleteUserInfo/js?api_key=souperyouser" -H "accept: application/json" -H "content-type: application/json" { } c:\>curl -X GET "http://127.0.0.1:1580/LivePreview/MultiAuth.a5svc/ListUsers?api_key=bingo" -H "accept: application/json" -H "content-type: application/json" [ { "id": "jqp", "firstname": "John", "lastname": "Public" } ]
CORS settings
Cross Origin Resource Sharing is an important issue for services, especially since services generally are called from different hosts.
There is a "cors" section to the A5SVC definition which controls which headers are allowed in a request. If you set the "headers" to "*", then the endpoint will allow every header that the caller requests.
{ .... "cors" : { "headers" : "*" } }
Custom CORS behaviour
A custom handler can be defined for options if anything more complicated is required.
The service definition 'options_handler' section contains the name of the class, the language used, and the name of the class method to call.
{ ... "options_handler": { "service": "customOptions::Manager", "implementation": "xbasic", "name": "option_request_handler", } ... }
In the above example, the implementation is a function with no arguments that sets the __http_status that is to be returned (you can return 401 if you don't like the request). In this example we are adding headers to allow content-type and apikey headers.
define class customOptions::Manager dim __http_status as n .... function option_request_handler as n () 'handle the browser's pre-flight OPTIONS request self.__http_status = 200 Response.AddHeader("Access-Control-Allow-Headers: content-type,apikey") end function ... end class