system.net.httpGet URL encoding of parametesr

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:

  1. Generating the URL parameter string from a dictionary.

  2. 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