536 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			536 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| Function HTTP_Contacts_Services(RemainingURL)
 | |
| /***********************************************************************************************************************
 | |
| 
 | |
|     This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
 | |
|     permission from SRP Computer Solutions, Inc.
 | |
| 
 | |
|     Name        :   HTTP_Contacts_Services
 | |
| 
 | |
|     Description :   Handler program for the HTTP Contacts service module.
 | |
| 
 | |
|     Notes       :   In the comments below, the term "resource" will be used. In most cases this is synonymous with a
 | |
|                     database row, but the web (and especially REST) abstracts all information being returned simply as a
 | |
|                     "resource". This provides developers more flexibility in their web API designs. For instance, a
 | |
|                     resource can be a combination of various different database rows and other data (like images,
 | |
|                     documents, etc.)
 | |
| 
 | |
|                     In this sample service, the "contact" resource will closely map to a sample CONTACTS database table.
 | |
|                     This is meant to provide the OpenInsight web API developer an easy way to create a web-based CRUD
 | |
|                     API that can also be extended as needed. Locking is performed on the resource at the database row
 | |
|                     level using the Lock statement, but this is only done just prior to the Write statement since HTTP
 | |
|                     is a stateless protocol. While some attempts to wait for the lock to be available could be added,
 | |
|                     this is normally discouraged since this could cause the HTTP request to take too long to finish.
 | |
| 
 | |
|                     All HTTP web services should include the HTTP_SERVICE_SETUP insert. This will provide several useful
 | |
|                     variables:
 | |
| 
 | |
|                         HTTPMethod              - The HTTP Method (Verb) submitted by the client (e.g., GET, POST, etc.)
 | |
|                         APIURL                  - The URL for the API entry point (e.g., api.mysite.com/v1).
 | |
|                         SelfURL                 - The URL path representing the current service.
 | |
|                         FullEndPointURL         - The URL submitted by the client. This can be the same or longer than
 | |
|                                                   the SelfURL.
 | |
|                         NextSegment             - The URL segment immediately following the SelfURL (if any). This
 | |
|                                                   could contain the name of the next service or it could contain the
 | |
|                                                   Item ID for the current service (aka resource).
 | |
|                         CurrentServiceHandler   - The name of this stored procedure.
 | |
| 
 | |
|     Parameters  :
 | |
|         RemainingURL    [in] -- The remaining portion of the URL that follows the URL that launched this current
 | |
|                                 service. This information is used in the HTTP_SERVICE_SETUP insert to populate other
 | |
|                                 useful variables (see Notes above).
 | |
|         Response       [out] -- Response to be sent back to the Controller (HTTP_MCP) or requesting procedure. Web API
 | |
|                                 services do not rely upon anything being returned in the response. This is what the
 | |
|                                 various services like SetResponseBody and SetResponseStatus services are for. A response
 | |
|                                 value is only helpful if the developers want to use it for debug purposes.
 | |
| 
 | |
|     History     :   (Date, Initials, Notes)
 | |
|         04/17/15    dmb     Original programmer. - [SRPFW-96]
 | |
|         03/09/16    dmb     Refactor to use the updated RunHTTPService service. - [SRPFW-112]
 | |
|         07/01/17    dmb     Refactor using Enhanced BASIC+ syntax. - [SRPFW-184]
 | |
|         07/07/17    dmb     Add support for PUT and PATCH so this routine can serve as a more complete CRUD example.
 | |
|                             - [SRPFW-187]
 | |
|         07/08/17    dmb     Remove checks for query parameters from the main router and make this a function of the
 | |
|                             GET method/URL handler. - [SRPFW-187]
 | |
|         07/19/18    dmb     Fix minor typo in the NextSegment variable in the GetItem method. - [SRPFW-248]
 | |
| 
 | |
| ***********************************************************************************************************************/
 | |
| 
 | |
| #pragma precomp SRP_PreCompiler
 | |
| 
 | |
| $insert APP_INSERTS
 | |
| $insert HTTP_SERVICE_SETUP
 | |
| $insert HTTP_INSERTS
 | |
| 
 | |
| // In the comments related to URL examples, words surrounded by "{" and "}" represent the names of values that
 | |
| // will appear in the actual URL. Words surrounded by "<" and ">" represent variables that contain values relevant to
 | |
| // the actual URL. See the Notes above for a list of the most important variables.
 | |
| //
 | |
| // For instance, <APIURL>/contacts/{KeyID} could look like https://api.mysite.com/v1/contacts/1000, assuming <APIURL>
 | |
| // resolves to "https://api.mysite.com/v1" and {KeyID} resolves to "1000".
 | |
| //
 | |
| // The type of request being made needs to be determined based on the URL content. There are only a few possibilities
 | |
| // that this API will support:
 | |
| //
 | |
| //   All Resources              = <APIURL>/contacts
 | |
| //   Specific Resource          = <APIURL>/contacts/{KeyID}
 | |
| //   Specific Resource Property = <APIURL>/contacts/{KeyID}/{property}
 | |
| //
 | |
| // Also, any URL can end with query parameters like this:
 | |
| //
 | |
| //   Resource Query             = <APIURL>/contacts?{property}={value}
 | |
| //
 | |
| // The request will go to the same handler as if the query parameters were missing but that handler itself will
 | |
| // determine if the query parameters will be used or ignored. 
 | |
| 
 | |
| // Assume the current HTTP method is valid until proven otherwise.
 | |
| ValidMethod     = True$
 | |
| // Assume the current web service is valid until provent otherwise.
 | |
| ValidService    = True$
 | |
| // Assume no HTTP methods are valid until proven otherwise.
 | |
| AllowedMethods  = ''
 | |
| // A list of all services able to be called from this URL.
 | |
| AllowedServices = 'picture'
 | |
| 
 | |
| // Handle the HTTP request as needed.
 | |
| Begin Case
 | |
|     Case RemainingURL _EQC ''
 | |
|         // This means the URL ends with /contacts, which means this is the end point. The HTTP methods roughly function
 | |
|         // as follows:
 | |
|         //
 | |
|         //  POST    - Creates a new resource. Assumes the server will generate the Key ID, which will be returned in the
 | |
|         //            HTTP response.
 | |
|         //  GET     - The client is requesting a collection of all contacts.
 | |
|         AllowedMethods  = 'POST,GET,OPTIONS'
 | |
|         Locate HTTPMethod in AllowedMethods using ',' setting MethodPos then
 | |
|             On MethodPos GoSub Post, Get, Options
 | |
|         end else
 | |
|             ValidMethod = False$
 | |
|         end
 | |
| 
 | |
|     Case Count(RemainingURL, '/') EQ 0
 | |
|         // This means the URL ends with /contacts/{KeyID}. {KeyID} is also known as the resource ID. When a resource is
 | |
|         // closely mapped to a database row (as is the case with this Contacts API), this is where the basic CRUD
 | |
|         // functionality will be added. The HTTP methods roughly function as follows:
 | |
|         //
 | |
|         //  PUT     - Creates* a resource using the Key ID contained in the URL. This is equivalent to the Write
 | |
|         //            statement in BASIC+. 
 | |
|         //  GET     - Reads the resource referenced by the Key ID contained in the URL. This is equivalent to the Read
 | |
|         //            statement in BASIC+.
 | |
|         //  PUT     - Updates* the resource referenced by the Key ID contained in the URL. This is the exact same
 | |
|         //            feature defined above. This should make sense since the Write statement in BASIC+ is used to
 | |
|         //            create and update database rows. Note, the PUT method assumes the entire resource is within the
 | |
|         //            request body, not just the changes. See the PATCH method below.
 | |
|         //  DELETE  - Deletes the source referenced by the Key ID contained in the URL. This is equivalent to the Delete
 | |
|         //            statement in BASIC+.
 | |
|         //
 | |
|         //  * Many people use the POST method for creating (and updating) a resource. However, per the HTTP
 | |
|         //    specification, POST is to be used when creating a new resource that does not yet have a resource ID
 | |
|         //    (i.e., Key ID). The server determines the Key ID and this is returned to the client for future use.
 | |
|         //
 | |
|         // PATCH    - Updates specific properties (e.g., data columns) of the resource referenced by the Key ID
 | |
|         //            contained in the URL. This is similar in concept to the WriteV statement in BASIC+, although
 | |
|         //            multiple changes in the resource can be updated with one PATCH method.
 | |
|         AllowedMethods  = 'PUT,GET,DELETE,PATCH,OPTIONS'
 | |
|         Locate HTTPMethod in AllowedMethods using ',' setting MethodPos then
 | |
|             On MethodPos GoSub PutItem, GetItem, DeleteItem, PatchItem, OptionsItem
 | |
|         end else
 | |
|             ValidMethod = False$
 | |
|         end
 | |
| 
 | |
|     Case Count(RemainingURL, '/') GE 1
 | |
|         // This means the URL ends with /contacts/{KeyID}/{property}. A property can be any specific data that is
 | |
|         // associated with the resource. It could be a column value, an image, a PDF document, etc. In this case, the
 | |
|         // only property supported by this web API is the contact's "picture". The developer can put add code in this
 | |
|         // service to update the picture or the developer can create another HTTP service to handle this. Since a
 | |
|         // "picture" service might be useful as a property for other types of resources, a call to a dedicated "picture"
 | |
|         // HTTP service will be made "as is" so it can handle the request. Calling another HTTP service is similar to
 | |
|         // the way one MFS calls another MFS by modifying the FS list. In this case, the NextSegment and RemainingURL
 | |
|         // variables will need to be modified.
 | |
|         Property    = FullEndPointURL[-1, 'B/']
 | |
|         Locate Property in AllowedServices using ',' setting ServicePos then
 | |
|             NextSegment     = Property  ; // This allows the RunHTTPService to call HTTP_PICTURE_SERVICES.
 | |
|             RemainingURL    = ''        ; // This variable won't be used in the HTTP_PICTURE_SERVICES code, but to keep the
 | |
|                                         ; // variables well formed, this should be cleared.
 | |
|             HTTP_Services('RunHTTPService', NextSegment, RemainingURL)
 | |
|         end else
 | |
|             ValidService    = False$
 | |
|         end
 | |
| 
 | |
|     Case Otherwise$
 | |
|         ValidService    = False$
 | |
| End Case
 | |
| 
 | |
| // Resolve any invalid conditions with the HTTP request.
 | |
| Begin Case
 | |
|     Case Not(ValidService)
 | |
|         HTTP_Services('SetResponseStatus', 404, NextSegment : ' is not a valid service request within the ' : CurrentServiceHandler : ' module.')
 | |
| 
 | |
|     Case Not(ValidMethod)
 | |
|         HTTP_Services('SetResponseStatus', 405, HTTPMethod : ' is not valid for this service.')
 | |
| 
 | |
|         GoSub SetAllowedMethods
 | |
| End Case
 | |
| 
 | |
| Return Response OR ''
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| // Service Parameter Options
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| Options BOOLEAN                     = True$, False$
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| // Web Services
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // Post
 | |
| //
 | |
| // Attempts to create a new resource. Creating a new which is a database row follows these guidelines:
 | |
| //
 | |
| //   - Any unexpected system errors will return a 500 status code (Internal Server Error).
 | |
| //   - If no errors occur then a 201 (Created) status code is returned. The Content-Location response header will be
 | |
| //     set to the value of the URL that will allow the client to GET the newly created resource.
 | |
| //   - If there is an error locking the resource then a 423 status code (Locked) is returned.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| Post:
 | |
| 
 | |
|     HTTP_Resource_Services('PostDatabaseItem', 'CONTACTS', SelfURL)
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // Get
 | |
| //
 | |
| // Returns a collection of resources.
 | |
| //
 | |
| // The easiest way to return a list of resources that are mapped to a database table is to use the GetDatabaseItems
 | |
| // service. This is being done in the code below. This URL also supports the passing in of query parameters, which in
 | |
| // this case will be used to return those items that match the property/value query.
 | |
| //
 | |
| // A property can be any specific data that is associated with the resource. It could be a column value, an image, a PDF
 | |
| // document, etc. In this case, only properties that match the name of database columns in the CONTACTS table will be
 | |
| // supported by this web API. Note, developers can limit the properties (aka columns) to those that are indexed in order
 | |
| // to avoid having a request take too long.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| Get:
 | |
| 
 | |
|     HAL = ''    ; // Initialize the response.
 | |
| 
 | |
|     If HTTP_Services('GetHTTPGetString') NE '' then
 | |
|         // This means the URL ends with /contacts?{property}={value}. The client is searching for one or more contacts
 | |
|         // that match the query parameters. This is equivalent to doing a filtered RLIST search.
 | |
| 
 | |
|         // Get the query string passed into the URL.
 | |
|         GetString   = HTTP_Services('GetHTTPGetString')
 | |
|         // Get the name of the property being queried.
 | |
|         Property    = GetString[1, 'F=']
 | |
|         // Get the value being searched for.
 | |
|         Value       = HTTP_Services('GetQueryField', Property)
 | |
|         // Get the database columns for the table.
 | |
|         ColumnNames = HTTP_Resource_Services('GetColumnNames', 'CONTACTS')
 | |
|         ColumnName  = Property
 | |
|         Convert @Lower_Case to @Upper_Case in ColumnName
 | |
|         // Verify the property matches a valid column in the table.
 | |
|         Locate ColumnName in ColumnNames using @FM setting fPos then
 | |
|             // Use the GetDatabaseItems service to perform the search and prepare the HAL+JSON response. If a more complex
 | |
|             // or optimized solution is needed, then replace the following with custom code.
 | |
|             Filter      = 'SELECT CONTACTS WITH ' : ColumnName : ' CONTAINING ' : Quote(Value)
 | |
|             // The GetDatabaseItems service will return all database column values unless otherwise specified. Since a query
 | |
|             // search might generated several results, it is sometimes best to pass in just those columns that are important
 | |
|             // for the query result.
 | |
|             ColumnNames = 'first_name' : @FM : 'last_name' : @FM : 'email'
 | |
|             Locate ColumnName in ColumnNames using @FM setting fPos else
 | |
|                 // Make sure the property being searched is included in the columns being returned.
 | |
|                 ColumnNames    := @FM : Property
 | |
|             end
 | |
|             HAL         = HTTP_Resource_Services('GetDatabaseItems', Filter, 'CONTACTS', SelfURL, ColumnNames)        
 | |
|         end else
 | |
|             // This is not a valid property, which means the URL does not resolve. Set a 404 error. Add a description if
 | |
|             // desired.
 | |
|             HTTP_Services('SetResponseStatus', 404)
 | |
|         end
 | |
| 
 | |
|     end else
 | |
|         // This means the URL ends with /contacts. The client is requesting all resources available at this URL.
 | |
|         // This is equivalent to performing an unfiltered SELECT statement. The ColumnNames argument for the
 | |
|         // GetDatabaseItems service specifies which values should be represented in the JSON response.
 | |
| 
 | |
|         Filter      = ''
 | |
|         ColumnNames = 'first_name' : @FM : 'last_name' : @FM : 'email'
 | |
|         HAL         = HTTP_Resource_Services('GetDatabaseItems', Filter, 'CONTACTS', SelfURL, ColumnNames)
 | |
| 
 | |
|     end
 | |
| 
 | |
|     Response    = HAL
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // Options
 | |
| //
 | |
| // Sets the appropriate response header fields for an OPTIONS request.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| Options:
 | |
| 
 | |
|     GoSub SetCommonOptionResponseHeaders
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // PutItem
 | |
| //
 | |
| // Attempts to update the resource. If the resource does not already exist then a new one will be created. Updating a
 | |
| // resource which is a database row follows these guidelines:
 | |
| //
 | |
| //   - Any unexpected system errors will return a 500 status code (Internal Server Error).
 | |
| //   - If no errors occur then a 200 (OK) status code is returned if the resource previously existed. Otherwise,
 | |
| //     a 201 (Created) status code is returned and the Content-Location response header will be set to the value of the
 | |
| //     URL that will allow the client to GET a newly created resource.
 | |
| //   - If there is an error locking the resource then a 423 status code (Locked) is returned.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| PutItem:
 | |
| 
 | |
|     KeyID   = NextSegment
 | |
| 
 | |
|     HTTP_Resource_Services('PutDatabaseItem', 'CONTACTS', SelfURL : '/' : KeyID, KeyID)
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // GetItem
 | |
| //
 | |
| // Returns the specific resource.
 | |
| //
 | |
| // The easiest way to return a resource that is mapped to a database row is to use the GetDatabaseItem service. This
 | |
| // is being done in the code below. However, to demonstrate how then basic functionality can be extended, there is
 | |
| // additional code below that will show how to add the Contact resource's image URL to the JSON response.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| GetItem:
 | |
| 
 | |
|     KeyID   = NextSegment
 | |
| 
 | |
|     // Calling this service alone would be sufficient to return a HAL+JSON representation of the specified contact.
 | |
|     HAL     = HTTP_Resource_Services('GetDatabaseItem', 'CONTACTS', SelfURL : '/' : KeyID, KeyID)
 | |
| 
 | |
|     // Since the Contact resource can also have an image, the following code will generate a valid URL for this image
 | |
|     // in case the client wants to retrieve it. The URL will then be added to the HAL+JSON response so this comes
 | |
|     // back as a single representation of the resource.
 | |
|     If HAL NE '' then
 | |
|         // Make the JSON content an object so the SRP_JSON API can work with it.
 | |
|         ParseResponse   = SRP_JSON(HALRootObj, 'PARSE', HAL)
 | |
|         If ParseResponse EQ '' then
 | |
|             // The CONTACTS table has a PICTURE data column. This stores the physical path to the image, but this is
 | |
|             // not useful to the HTTP client. Create a URL that will allow the client to retrieve the image.
 | |
|             PictureValue    = SRP_JSON(HALRootObj, 'GETVALUE', 'picture', '')
 | |
|             If PictureValue NE '' then
 | |
|                 If SRP_JSON(PictureObj, 'NEW', 'OBJECT') then
 | |
|                     // Create the URL and add it to the JSON object.
 | |
|                     ImageURL    = SelfURL : '/' : KeyID : '/picture'
 | |
|                     SRP_JSON(PictureObj, 'SETVALUE', 'href', ImageURL)
 | |
|                     SRP_JSON(PictureObj, 'SETVALUE', 'name', 'picture-' : KeyID)
 | |
|                     SRP_JSON(HALRootObj, 'SET', 'picture', PictureObj)
 | |
|                     HAL         = SRP_JSON(HALRootObj, 'STRINGIFY', 'STYLED')
 | |
|                     // Set the HTTP response body with the final HAL+JSON results.
 | |
|                     HTTP_Services('SetResponseBody', HAL, False$, 'application/hal+json')
 | |
|                     SRP_JSON(PictureObj, 'RELEASE')
 | |
|                 end
 | |
|             end
 | |
|             SRP_JSON(HALRootObj, 'RELEASE')
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     Response    = HAL
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // DeleteItem
 | |
| //
 | |
| // Attempts to delete the resource. Deleting a resource which is a database row follows these guidelines:
 | |
| //
 | |
| //   - Any unexpected system errors will return a 500 status code (Internal Server Error).
 | |
| //   - If no errors occur then a 204 (No Content) status code is returned.
 | |
| //   - If the resource was already deleted then a 204 (No Content) status code is returned.
 | |
| //   - If there is an error locking the resource then a 423 status code (Locked) is returned.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| DeleteItem:
 | |
| 
 | |
|     KeyID   = NextSegment
 | |
| 
 | |
|     HTTP_Resource_Services('DeleteDatabaseItem', 'CONTACTS', KeyID)
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // PatchItem
 | |
| //
 | |
| // Attempts to update the resource. Updating a resource which is a database row follows these guidelines:
 | |
| //
 | |
| //   - Any unexpected system errors will return a 500 status code (Internal Server Error).
 | |
| //   - If no errors occur then a 200 (OK) status code is returned.
 | |
| //   - If the resource is new then a 404 (Not Found) status code is returned. PATCH only works with existing resources.
 | |
| //   - Only those properties (columns) which are passed in will get updated.
 | |
| //   - If there is an error locking the resource then a 423 status code (Locked) is returned.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| PatchItem:
 | |
| 
 | |
|     KeyID   = NextSegment
 | |
| 
 | |
|     HTTP_Resource_Services('PatchDatabaseItem', 'CONTACTS', SelfURL : '/' : KeyID, KeyID)
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // OptionsItem
 | |
| //
 | |
| // Sets the appropriate response header fields for an OPTIONS request.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| OptionsItem:
 | |
| 
 | |
|     GoSub SetCommonOptionResponseHeaders
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // PutItemProperty
 | |
| //
 | |
| // Attempts to update the property of a specific resource.
 | |
| //
 | |
| // A property can be any specific data that is associated with the resource. It could be a column value, an image, a PDF
 | |
| // document, etc. In this case, the only property supported by this web API is the contact's "picture". The developer
 | |
| // can put add code here to update the picture or the developer can create another HTTP service to handle this. Since a
 | |
| // "picture" service might be useful as a property for other types of resources, a call to a dedicated "picture" HTTP
 | |
| // service will be made.
 | |
| //
 | |
| // Calling another HTTP service is similar to the way one MFS calls another MFS by modifying the FS list. In this case,
 | |
| // the NextSegment and RemainingURL variables will need to be modified. At this point in the stack the following
 | |
| // API variables look like this:
 | |
| //
 | |
| //    HTTPMethod        : PUT
 | |
| //    SelfURL           : <APIURL>/contacts
 | |
| //    NextSegment       : {KeyID}
 | |
| //    FullEndPointURL   : <APIURL>/contacts/{KeyID}/{property}
 | |
| //
 | |
| // The code will need to determine if a supported property has been passed in. If so, then the next HTTP service will
 | |
| // need to be called with the appropriate modifications to the variables.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| PutItemProperty:
 | |
| 
 | |
|     // Get the name of the property by looking at the last segment in the FullEndPointURL variable. An assumption is
 | |
|     // being made that there are no other segments in the URL that follow the property name.
 | |
|     Property    = FullEndPointURL[-1, 'B/']
 | |
|     Locate Property in AllowedServices using ',' setting ServicePos then
 | |
|         // A supported property has been passed in the URL. Modify the NextSegment and RemainingURL variables so the
 | |
|         // next HTTP service can be called correctly.
 | |
|         NextSegment     = Property  ; // This allows the RunHTTPService to call HTTP_PICTURE_SERVICES.
 | |
|         RemainingURL    = ''        ; // This variable won't be used in the HTTP_PICTURE_SERVICES code, but to keep the
 | |
|                                     ; // variables well formed, this should be cleared.
 | |
|         HTTP_Services('RunHTTPService', NextSegment, RemainingURL)
 | |
|     end else
 | |
|         // The URL contains an unsupported property. Return a 404 error.
 | |
|         HTTP_Services('SetResponseStatus', 404, Property : ' is not a valid service request within the ' : CurrentServiceHandler : ' module.')
 | |
|     end
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // GetItemProperty
 | |
| //
 | |
| // Returns the property of a specific resource.
 | |
| //
 | |
| // A property can be any specific data that is associated with the resource. It could be a column value, an image, a PDF
 | |
| // document, etc. In this case, the only property supported by this web API is the contact's "picture". The developer
 | |
| // can put add code here to return the picture or the developer can create another HTTP service to handle this. Since a
 | |
| // "picture" service might be useful as a property for other types of resources, a call to a dedicated "picture" HTTP
 | |
| // service will be made.
 | |
| //
 | |
| // Calling another HTTP service is similar to the way one MFS calls another MFS by modifying the FS list. In this case,
 | |
| // the NextSegment and RemainingURL variables will need to be modified. At this point in the stack the following
 | |
| // API variables look like this:
 | |
| //
 | |
| //    HTTPMethod        : GET
 | |
| //    SelfURL           : <APIURL>/contacts
 | |
| //    NextSegment       : {KeyID}
 | |
| //    FullEndPointURL   : <APIURL>/contacts/{KeyID}/{property}
 | |
| //
 | |
| // The code will need to determine if a supported property has been passed in. If so, then the next HTTP service will
 | |
| // need to be called with the appropriate modifications to the variables.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| GetItemProperty:
 | |
| 
 | |
|     // Get the name of the property by looking at the last segment in the FullEndPointURL variable. An assumption is
 | |
|     // being made that there are no other segments in the URL that follow the property name.
 | |
|     Property    = FullEndPointURL[-1, 'B/']
 | |
|     If Property _EQC 'picture' then
 | |
|         // A supported property has been passed in the URL. Modify the NextSegment and RemainingURL variables so the
 | |
|         // next HTTP service can be called correctly.
 | |
|         NextSegment     = Property  ; // This allows the RunHTTPService to call HTTP_PICTURE_SERVICES.
 | |
|         RemainingURL    = ''        ; // This variable won't be used in the HTTP_PICTURE_SERVICES code, but to keep the
 | |
|                                     ; // variables well formed, this should be cleared.
 | |
|         HTTP_Services('RunHTTPService', NextSegment, RemainingURL)
 | |
|     end else
 | |
|         // The URL contains an unsupported property. Return a 404 error.
 | |
|         HTTP_Services('SetResponseStatus', 404, Property : ' is not a valid service request within the ' : CurrentServiceHandler : ' module.')
 | |
|     end
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // OptionsItemProperty
 | |
| //
 | |
| // Sets the appropriate response header fields for an OPTIONS request.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| OptionsItemProperty:
 | |
| 
 | |
|     GoSub SetCommonOptionResponseHeaders
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| // Internal GoSubs
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // SetCommonOptionResponseHeaders
 | |
| //
 | |
| // Sets the response headers that will be common for all OPTIONS methods.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| SetCommonOptionResponseHeaders:
 | |
| 
 | |
|     HTTP_Services('SetResponseHeaderField', 'Access-Control-Allow-Headers', 'authorization', True$)
 | |
|     HTTP_Services('SetResponseHeaderField', 'Access-Control-Allow-Headers', 'x-authorization', True$)
 | |
|     HTTP_Services('SetResponseHeaderField', 'Access-Control-Max-Age', 1728000)
 | |
| 
 | |
|     GoSub SetAllowedMethods
 | |
| 
 | |
| return
 | |
| 
 | |
| 
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| // SetAllowedMethods
 | |
| //
 | |
| // Sets the Allow response header field as appropriate for the requested URL.
 | |
| //----------------------------------------------------------------------------------------------------------------------
 | |
| SetAllowedMethods:
 | |
| 
 | |
|     If AllowedMethods NE '' then
 | |
|         For Each Method in AllowedMethods using ','
 | |
|             HTTP_Services('SetResponseHeaderField', 'Allow', Method, True$)
 | |
|         Next Method
 | |
|     end
 | |
| 
 | |
| return
 |