Function HTTP_Scan_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_Scan_Services Description : Handler program for the HTTP Scan service module. Notes : 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) 07/31/18 dmb Original programmer. 09/04/18 dmb Revamp so Scan is its own resource rather than a parent to a sub-resource. Migrate the Tools API logic into this routine as a starting point. Retrofit several SCANS column name equates to match the changes made to the dictionary. ***********************************************************************************************************************/ #pragma precomp SRP_PreCompiler $insert APP_INSERTS $insert HTTP_SERVICE_SETUP $insert HTTP_INSERTS $insert SCANS_EQUATES Declare function Scan_Services, Database_Services, RTI_CreateGUID Declare subroutine Scan_Services, Database_Services // 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 = '' // Handle the HTTP request as needed. Begin Case Case RemainingURL _EQC '' // This means the URL ends with /scan, 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 scan ID, which will be returned in // the HTTP response. AllowedMethods = 'POST,OPTIONS' Locate HTTPMethod in AllowedMethods using ',' setting MethodPos then On MethodPos GoSub Post, Options end else ValidMethod = False$ end Case Count(RemainingURL, '/') EQ 0 // This means the URL ends with /scan/{scanID}. {scanID} 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 = 'POST,GET,PUT,PATCH,DELETE,OPTIONS' Locate HTTPMethod in AllowedMethods using ',' setting MethodPos then On MethodPos GoSub PostItem, GetItem, PutItem, PatchItem, DeleteItem, OptionsItem end else ValidMethod = 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 scan. Creating a new scan 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: ScanID = Scan_Services('CreateScansRow', 'Lot') If Error_Services('NoError') then ItemURL = SelfURL : '/' : ScanID StatusCode = 201 GoSub CreateHALItem end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 500, Message) end return //---------------------------------------------------------------------------------------------------------------------- // Options // // Sets the appropriate response header fields for an OPTIONS request. //---------------------------------------------------------------------------------------------------------------------- Options: GoSub SetCommonOptionResponseHeaders return //---------------------------------------------------------------------------------------------------------------------- // PostItem // // Attempts to add new scan data to the scan resource. //---------------------------------------------------------------------------------------------------------------------- PostItem: ScanID = NextSegment // The resource will have been put into the POST string. Body = HTTP_Services('GetHTTPPostString') If Body NE '' then // The POST string will have been encoded so use percent (URL) decoding. Body = HTTP_Services('DecodePercentString', Body) ParseResponse = SRP_JSON(hBody, 'PARSE', Body) If (ParseResponse EQ '') then ValidScan = False$ ; // Assume the scan is not valid for now. ScanData = SRP_JSON(hBody, 'GetValue', 'scanData') SRP_JSON(hBody, 'Release') Scan_Services('ProcessScanData', ScanID, ScanData) If Error_Services('NoError') then ItemURL = SelfURL : '/' : ScanID StatusCode = 200 ; // This service assumes the resource already exists. GoSub CreateHALItem end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 400, Message) end end else HTTP_Services('SetResponseStatus', 400, 'Unable to parse the scanData JSON.') end end else HTTP_Services('SetResponseStatus', 400, 'JSON object is missing from the request.') end 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: ScanID = NextSegment ItemURL = SelfURL : '/' : ScanID ScanRow = Scan_Services('GetScansRow', ScanID) If Error_Services('NoError') then StatusCode = 200 GoSub CreateHALItem end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 404, Message) end return //---------------------------------------------------------------------------------------------------------------------- // PutItem // // Attempts to replace the indicated scan resource with the incoming JSON object. //---------------------------------------------------------------------------------------------------------------------- PutItem: ScanID = NextSegment // The resource will have been put into the POST string. Body = HTTP_Services('GetHTTPPostString') If Body NE '' then // The POST string will have been encoded so use percent (URL) decoding. Body = HTTP_Services('DecodePercentString', Body) ParseResponse = SRP_JSON(hBody, 'PARSE', Body) If (ParseResponse EQ '') then ScanRow = Database_Services('ReadDataRow', 'SCANS', ScanID) If Error_Services('NoError') then // Existing scan resource is being replaced. StatusCode = 200 end else // Scan resource is being created for the first time. StatusCode = 201 end If ScanRow EQ '' then ScanRow = Date() If ScanRow EQ '' then ScanRow = Time() ScanRow = SRP_JSON(hBody, 'GetValue', 'employeeID', '') ScanRow = SRP_JSON(hBody, 'GetValue', 'toolID', '') ScanRow = SRP_JSON(hBody, 'GetValue', 'cassetteID', '') Accept = SRP_JSON(hBody, 'GetValue', 'accept', False$) If Accept EQ True$ then // Before allowing the accept field to be set to True, verify that all required data has been populated. Begin Case Case ScanRow EQ '' ; Accept = False$ Case ScanRow EQ '' ; Accept = False$ Case ScanRow EQ '' ; Accept = False$ End Case end ScanRow = Accept If ScanRow EQ True$ then ScanRow = Date() ScanRow = Time() end Database_Services('WriteDataRow', 'SCANS', ScanID, ScanRow, True$, False$, True$) If Error_Services('NoError') then ItemURL = SelfURL : '/' : ScanID GoSub CreateHALItem end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 500, Message) end SRP_JSON(hBody, 'Release') end else HTTP_Services('SetResponseStatus', 400, 'Unable to parse the scanData JSON.') end end else HTTP_Services('SetResponseStatus', 400, 'JSON object is missing from the request.') end return //---------------------------------------------------------------------------------------------------------------------- // PatchItem // // Attempts to update the indicated scan resource with the incoming JSON object. The only permissible field to be // updated is "accept". //---------------------------------------------------------------------------------------------------------------------- PatchItem: ScanID = NextSegment // First confirm that this is a valid Scan ID. jsonScan = Scan_Services('GetScansRow', ScanID, True$) If Error_Services('NoError') then // Confirm that all required data has been scanned before allowing the "accept" field to be updated. ParseResponse = SRP_JSON(objResource, 'Parse', jsonScan) If ParseResponse EQ '' then ScanAcceptable = SRP_JSON(objResource, 'GetValue', 'scan.acceptable', False$) If ScanAcceptable NE True$ then ScanNotAcceptableReason = SRP_JSON(objResource, 'GetValue', 'scan.notAcceptableReason') Error_Services('Add', ScanNotAcceptableReason) end SRP_JSON(objResource, 'Release') end else Error_Services('Add', 'Unable to parse the JSON scan resource.') end If Error_Services('NoError') then // The resource will have been put into the POST string. Body = HTTP_Services('GetHTTPPostString') If Body NE '' then // The POST string will have been encoded so use percent (URL) decoding. Body = HTTP_Services('DecodePercentString', Body) ParseResponse = SRP_JSON(hBody, 'PARSE', Body) If ParseResponse EQ '' then AcceptedStatus = SRP_JSON(hBody, 'GetValue', 'accepted.status') If AcceptedStatus NE '' then ScansRow = Scan_Services('GetScansRow', ScanID, False$) ScansRow = AcceptedStatus If AcceptedStatus EQ True$ then ScansRow = Date() ScansRow = Time() end Scan_Services('SetScansRow', ScanID, ScansRow) If Error_Services('NoError') then ItemURL = SelfURL : '/' : ScanID StatusCode = 200 GoSub CreateHALItem end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 500, Message) end end else HTTP_Services('SetResponseStatus', 400, 'accepted.status field is missing from the JSON object.') end SRP_JSON(hBody, 'Release') end else HTTP_Services('SetResponseStatus', 400, 'Unable to parse the scanData JSON.') end end else HTTP_Services('SetResponseStatus', 400, 'JSON object is missing from the request.') end end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 400, Message) end end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 404, Message) end 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: ScanID = NextSegment ScanRow = Database_Services('ReadDataRow', 'SCANS', ScanID) If Error_Services('NoError') then If ScanRow NE True$ then Database_Services('DeleteDataRow', 'SCANS', ScanID, True$, False$) If Error_Services('NoError') then HTTP_Services('SetResponseStatus', 200) end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 500, Message) end end else HTTP_Services('SetResponseStatus', 403, 'This scan is already accepted and cannot be deleted.') end end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 404, Message) end return //---------------------------------------------------------------------------------------------------------------------- // OptionsItem // // Sets the appropriate response header fields for an OPTIONS request. //---------------------------------------------------------------------------------------------------------------------- OptionsItem: 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 //---------------------------------------------------------------------------------------------------------------------- // CreateHALItem // // Creates a HAL+JSON object based on the OpenInsight data row representation of the scan. //---------------------------------------------------------------------------------------------------------------------- CreateHALItem: jsonScan = Scan_Services('ConvertMVScanToJSON', ScanID, '', itemURL) If Error_Services('NoError') then If SRP_JSON(objResource, 'Parse', jsonScan) EQ '' then lastModified = SRP_JSON(objResource, 'GetValue', 'lastModified') end else lastModified = '' end SRP_JSON(objResource, 'Release') HTTP_Services('SetResponseHeaderField', 'Content-Location', ItemURL) HTTP_Services('SetResponseHeaderField', 'Last-Modified', lastModified) HTTP_Services('SetResponseBody', jsonScan, False$, 'application/hal+json') HTTP_Services('SetResponseStatus', StatusCode) end else Message = Error_Services('GetMessage') HTTP_Services('SetResponseStatus', 500, 'Error in the ' : CurrentServiceHandler : ' service. Message: ': Message) end return