Engineering BlogRSS Feed

Nate Harris

HTTP Hooks Available in Client Libraries

by Nate Harris

“Knowing yourself is the beginning of all wisdom,” Aristotle once said.

In software development, as in life, sometimes you need to look inward to understand the world around you. Introspection, as it’s called, can be invaluable in times of crisis and uncertainty; it’s a reliable methodology for assessing a problem and exploring a solution.

When it comes to our client libraries at EasyPost, we have designed them to be as simple and straightforward to use as possible. On occasion, however, we run into situations where our “black box” approach actually makes troubleshooting more difficult. Our objective, to build easy-to-use shipping solutions, would be incomplete if we did not make it just as easy to debug our systems as it is to use them normally.

With that in mind, we would like to announce that we have taken another step toward improving debuggability (is that a word? It is now) with the introduction of HTTP request and response hooks.

Developers using our client libraries will be able to set up callback functionsopens in new tab that can be triggered both before and after every single HTTP request to the EasyPost API.

In our Python library:

def custom_request_function(**kwargs):
	"""This function will be triggered before each request."""
	pass

def custom_response_function(**kwargs):
	"""This function will be triggered after each request."""
	pass

# Create a normal client
client = easypost.EasyPostClient("my_api_key")

# Subscribe the client to the callback functions on each request
client.subscribe_to_request_hook(custom_request_function)
client.subscribe_to_response_hook(custom_response_function)

In our Java library:

public static Object requestHookFunction(RequestHookResponses request) {
	// This function will be triggered before each request.
	return true;
}

public static Object responseHookFunction(ResponseHookResponses response) {
	// This function will be triggered after each request.
	return true;
}

// Create a normal client
EasyPostClient client = new EasyPostClient("my_api_key");

// Subscribe the client to the callback functions on each request
client.subscribeToRequestHook(requestHookFunction);
client.subscribeToResponseHook(responseHookFunction);

Important elements from the preflight request or post-flight response will be passed into the respective callback functions for developers to utilize as they see fit (e.g. logging, analysis). These elements will be passed in a format expected based on the language (as a **kwargs dictionary in Python, an object in Java, individual arguments in Ruby, a set of EventArgs in .NET, etc.)

The following items about the request that is about to be executed will be provided to the request callback functions:

  • The HTTP method of the request
  • The full URL path of the request
  • The full set of headers of the request
  • The prepared body of the request, if any
  • A timestamp of the request (useful for calculating execution time)
  • A unique ID (to match request-response pairs across callback functions)

The following items about the response that was just received will be provided to the response callback functions:

  • The HTTP status code of the response
  • The full set of headers of the response
  • The HTTP method of the corresponding request
  • The full URL path of the corresponding request
  • The raw body of the response
  • A timestamp of the response
  • The timestamp of the corresponding request (calculate the difference between the two timestamps to determine total in-flight time)
  • The unique ID of the corresponding request (to match request-response pairs across callback functions)

Armed with these callbacks, developers can now introspect the details of any request to or response from the EasyPost API. This can be useful for ensuring that request payloads are properly formatted, calculating response times for large API calls, logging request details, or anything else that a developer might need.

def demo_hooks():
	"""Create a parcel, confirming that the request-response IDs and methods match"""
	current_request_id = None
	current_request_method = None

	def request_hook(**kwargs):
		"""Save the ID and method of the request preflight."""
		nonlocal current_request_id, current_request_method
		current_request_id = kwargs.get("request_uuid")
		current_request_method = kwargs.get("method")

	def response_hook(**kwargs):
		"""Confirm the ID and method of the response matches the stored request."""
		response_request_id = kwargs.get("request_uuid")
		response_request_method = kwargs.get("method")
		assert current_request_id == response_request_id
		assert current_request_method == response_request_method

	# Create a normal client
	client = easypost.EasyPostClient("my_api_key")

	# Subscribe the client to the callback functions on each request
	client.subscribe_to_request_hook(request_hook)
	client.subscribe_to_response_hook(response_hook)

	# Call a "create parcel" API call
	parcel = client.parcel.create(**{})

	# Unsubscribe from the callbacks for future API calls
	client.unsubscribe_from_request_hook(request_hook)
	client.unsubscribe_from_response_hook(response_hook)

If developers would like to execute multiple operations on each API call, multiple request and response callbacks are allowed. And if a callback function is no longer needed, developers can unsubscribe from a callback in much the same process as subscribing.

HTTP hooks have been added to all seven of our client libraries and are now available in the most recent release of each. Details about the exact usage of hooks are available in the README of each respective GitHub repositoryopens in new tab. If you run into any issues, please don’t hesitate to open an issue on our GitHub page or contact our support teamopens in new tab.