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 classDefinition 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
trueGet 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 classAnother 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 classThe 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 classThe 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