Using the REST Library: Difference between revisions
Creating (splitting up original article) |
m Typo |
||
Line 155: | Line 155: | ||
Move (Trim(WebAppUser.Password) = Trim(tCreds.sPassword)) to bOK | Move (Trim(WebAppUser.Password) = Trim(tCreds.sPassword)) to bOK | ||
</source> | </source> | ||
Which assumes that you are not using encrypted | Which assumes that you are not using encrypted passwords, however how to handle those is left as an exercise for the reader! | ||
Then at the top of the file add "'''Open WebAppUser'''", while at the top of the oWebOrderAPI object add "'''Property String psUsername'''". | Then at the top of the file add "'''Open WebAppUser'''", while at the top of the oWebOrderAPI object add "'''Property String psUsername'''". |
Revision as of 13:18, 7 August 2019
The simple example of a RESTful service set out in the article A Simple RESTful Service involved quite a bit of work to produce an API for only one table.
An alternative approach is to use the REST library I (Mike) have created, which can be downloaded from File:RESTLibrary.zip.
To use it, unzip that file into some directory, then, from the workspace you are developing in, use that as a library; in the Studio do: Tools --> Maintain Libraries... --> Add Library, then navigate to where you unzipped it and within it select the "RESTLibrary.sws" file.
That will add a new section to the Studio's Class Palette entitled "REST".
With the WebApp project selected in the Studio, do: File --> New --> Other --> DataFlex Source File, naming it, for this example, "WebOrderAPI", which will create a blank package file. Into that, from the new REST section of the class palette, drag an object of the cRESTfulService class. Rename the object oWebOrderAPI.
Then again do: File --> New --> Other --> DataFlex Source File, this time naming it ApiCustomersHandler. Into that blank file drag an object of the cRestResourceHandler class. Change the object's name to oApiCustomersHandler and set its psInterfacePath property (an empty string by default) to "customers".
Then in the DDO Explorer pane (lower-right in the Studio by default) click the "+" book icon (Add DDO) and select the cCustomerDataDictionary.
Drag a cRESTApiObject from the class palette to below the comment "Add your cRESTApiObjects here". Rename it to "oCustomers". Set its phoDD property to oCustomer_DD, its psCollName (collection name) property to "customers" (plural) and its psInstName (instance name) property to "customer" (singular).
Uncomment one of the "Send AddListColumn" lines and change its "Table.Column" to Customer.Customer_Number, then copy that line and make the copy for Customer.Name. (You can keep adding those to your heart's content, but lists should be sparing of such detail.)
Go back to your WebOrderAPI file and under the comment line: 'Add "Use" statements for your cResourceHandler objects here:' add the line: "Use APICustomersHandler.pkg".
For development you may want to change the pbVerboseErrors setting to True, although for deployment it should probably be set back to False, to avoid confusing users with a pile of technical detail, which they neither should, nor want to, be exposed to.
You should perhaps, for future-proofing a real API, change the psPath setting from its default of "api" to "api/v1" (to provide for v2, v3 and so on in the future) - Note: this will change the URL which you use to make calls to it.
That should leave you with (some additional comments are mine):
WebOrderAPI.pkg:
Use cRESTfulService.pkg Object oWebOrderAPI is a cRESTfulService Set psPath to "api/v1" // Changed to provide for future versions Set pbVerboseErrors to True // For development only, return to False for deployment // Add "Use" statements for your cResourceHandler objects here: Use APICustomersHandler.pkg // You add this line Procedure APIRoot Handle hoResp Get CreateJsonObject to hoResp Send AddRegisteredCollections hoResp Send OutputJson hoResp End_Procedure Procedure ProcessHttpRequest String sVerb String sPart0 Get PathPart 0 to sPart0 Move (Lowercase(sPart0)) to sPart0 Case Begin Case ((sVerb = C_httpGet) and (sPart0 = "")) Send APIRoot Case Break Case (IsRegisteredPath(Self, sPart0)) Send AutoProcess sPart0 Case Break Case Else Send UnrecognisedOperation Case Break Case End End_Procedure End_Object
APICustomersHandler.pkg:
Use cRESTResourceHandler.pkg Use cCustomerDataDictionary.dd Use cRESTAPIObject.pkg Object oAPICustomersHandler is a cRESTResourceHandler // Add your data-dictionary objects here // Use the DDO Explorer in the Studio to select DDOs: Object oCustomer_DD is a cCustomerDataDictionary End_Object Set Main_DD to oCustomer_DD Set psInterfacePath to "customers" // We have to set this // Add your cRESTApiObjects here (below the Set Main_DD line) // Simply drag cRESTApiObjects here from the Studio's Class Palette Object oCustomer is a cRESTApiObject Set phoDD to oCustomer_DD // We have to set these three Set psCollName to "customers" // properties for the object Set psInstName to "customer" // to work properly // Send AddListColumn File_Field Table.Column Send AddListColumn File_Field Customer.Customer_Number // These lines determine Send AddListColumn File_Field Customer.Name // the columns in the list Procedure PostProc Handle hoResp Integer iMode Forward Send PostProc hoResp iMode // Add dependant collections (or other links) in here, e.g.: // Send AddCollection hoResp "{name}" (InstURL(Self, iMode) + "/{collectionName}") // Send AddLink hoResp "{name}" {identifier} {URL} End_Procedure End_Object End_Object
Now you can immediately see the saving: 81 lines (most of which were written for you and a couple of which are comments I have added for clarification) vs. the 574 lines in the Simple example, while most of the functionality is essentially the same (a careful examination with reveal a bit more "HATEOAS"-type information in some of the returned JSON in the former).
In addition this approach has much more generality and extensibility: additional cRESTApiObjects (and DDOs) can be added to the cApiCustomersHandler to provide for dependant sub-collections (since customers have orders, and orders have details), while other cRESTResourceHandler objects can be added to handle other top-level collections (orders, vendors, inventory, salespeople and so on).
The library (in the cRESTApiObjects) provides mechanisms to exclude specific fields from the interface (Send AddExcludeColumn Table.Column), or mark them as read-only through it (Send AddReadOnlyColumn Table.Column), include parent columns in collections or instances (through the AddListColumn and AddInstanceColumn procedures) and even provide calculated pseudo-columns (AddListFunctionColumn and AddInstanceFunctionColumn). Entire collections can be set as read-only (pbReadOnly), no-update (pbNoUpdate) or no-delete (pbNoDelete).
In your browser, or testing tool such as Postman or the DataFlex RESTTester program above, you can access this one as ".../api/v1" (or just ".../api" if you didn't make the versioning change to the psPath setting).
Security
This example does not have any security built into it yet, but the cRESTfulService class does understand a bit about Basic Auth, so we can add the following short OnPreRequest procedure to the oWebOrderAPI object:
Procedure OnPreRequest String sVerb String sPath tBasicAuthCredentials tCreds Boolean bOK Set psUsername to "" // Reset it to blank at the start of *every* request Get BasicAuthCredentials to tCreds If (tCreds.sUserName <> "") Begin Clear WebAppUser Move tCreds.sUserName to WebAppUser.LoginName Find Eq WebAppUser by Index.1 If (Found) Begin Move (ComparePasswords(ghoWebSessionManager, Trim(WebAppUser.Password), ; tCreds.sPassword)) to bOK If bOK ; Set psUsername to tCreds.sUserName End End End_Procedure
As you can see, that relies on the ComparePasswords function of the WebSessionManager object, which will not be present in a Basic Web Project, so if using such, you would have to replace that, perhaps with a simple:
Move (Trim(WebAppUser.Password) = Trim(tCreds.sPassword)) to bOK
Which assumes that you are not using encrypted passwords, however how to handle those is left as an exercise for the reader!
Then at the top of the file add "Open WebAppUser", while at the top of the oWebOrderAPI object add "Property String psUsername".
Finally, right at the beginning of the ProcessHttpRequest procedure add:
If (psUsername(Self) = "") Begin Send BasicAuthRequired "Web Order API" Procedure_Return End
Fuller instructions for creating an API using the REST library can be found in my course notes from Synergy 2019, although complete documentation for the library has sadly yet to make it to the top of my ToDo pile (it will be a lot of work).