I have written a script library to hold a bunch of functions that GET data from an inventory management system API.
All has been working for years until a curveball recently threw my way.
There was a URL parameter value that had a space in it.
Seems like such things are not supported and need to be URL encoded.
ie: “My Warehouse” must be encoded to “My+Warehouse” or “My%20Warehouse” to work in the API URL.
I’ve been trying to use the encoder recommended elsewhere in the forums:
from java.net import URLEncoder
from java.nio.charset import StandardCharsets
Apparently only the VALUE part of the parameter key/value pair should be encoded.
So something like this:
params={'pageSize': 200, 'WarehouseCode': 'G1.2 Prod'}
Is converted to a URL string like this, which will be appended to the end of the BASE URL that is sent to the API call.
result=pageSize=200&WarehouseCode=G1.2+Prod
However now I’m getting authorization errors (403) because (I presume) the ‘api-auth-signature’ generated does not match.
I think i’m going in circles a bit trying to figure out what needs encoding and where.
Does anyone have any good examples of:
-
Generating the URL parameter string from a dictionary.
-
Generating the appropriate header signature required
If you use system.net.httpClient it'll do all this for you.
Huh looks like I had some old testing functions that were trying to do just that.
Looks like I can get it something to work without putting the parameters into the URL myself (as below), but I believe the URL Headers are still giving me problems, because I need to generate that myself to create the api-auth-signature. And the missmatch between the original WarehouseCode value and the encoded one causes signature missmatch right?
def generateSignature(urlQueryParams):
""" This function returns an encoded signature based on url query parameters
and the unique API signature key
Args:
urlQueryParams (str): string of url parameters to encode into signature
"""
import hashlib
import base64
import hmac
scriptName = "shared.api.generateSignature"
message = urlQueryParams.encode('utf-8')
apiScriptLogger.debug("%s - urlQueryParams=%s, utf-8 encoded message=%s" % (scriptName, urlQueryParams, message))
secret = API_KEY.encode('utf-8')
hmacDigest = hmac.new(secret,message, digestmod=hashlib.sha256).digest()
return base64.b64encode(hmacDigest)
#end def
def generateHeaders(urlQueryParams):
return {
"Accept": "application/json",
"api-auth-id": API_ID,
"api-auth-signature": shared.api.generateSignature(urlQueryParams),
"Content-Type": "application/json",
"client-type": "Ignition"
}
#end def
url = BASE_URL + endpoint # NO url params - will be passed into getAsync and handled for us
# Custom function to take parameters and generate headers with signature
urlHeaders = shared.api.generateHeaders(shared.api.urlParamEncode(paramsDict))
try:
promise = httpClient.getAsync(url=url, headers=urlHeaders, params=paramsDict, timeout = 30000) # Timeout = read timeout.
LOGGER.info("%s - waiting for Async promise to complete" % (scriptName))
response = promise.get()
if response.isGood():
shared.log.debug("%s - GET request to: '%s' returned StatusCode=%0d" % (scriptName, url, response.getStatusCode()))
else:
shared.log.error("%s - GET request to: '%s' returned StatusCode=%0d, response: %s" % (scriptName, url, response.getStatusCode(), response.getJson()))
# end if
except:
response = None
finally:
endTime = shared.util.getGatewayDateTime()
secondsElapsed = system.date.secondsBetween(startTime, endTime)
###LOGGER.info("%s - statusCode=%s, duration=%0ds" % (scriptName, response.getStatusCode(), secondsElapsed))
# Record request attempt in SQL for future reference
recordHttpAttempt('GET', endpoint, paramsDict, url, response, secondsElapsed)
return response
# end try