AWS Developer Tools Blog

Using Custom Request Handlers

Requests are the core of the Go SDK. They handle the network request to a specific service. When you call a service method in the AWS SDK for Go, the SDK creates a request object that contains the input parameters and request lifecycle handlers. The logic involved in each step of a request’s lifecycle, such as Build, Validate, or Send, is handled by its corresponding list of handlers. For example, when the request’s Send method is called, it will iterate through and call every function in the request’s Send handler list. For the full list of handlers, see this API reference page. These handlers  shape and mold  what your request does on the way to AWS and what the response does on the way out.

Customizability is an important aspect of request handlers. By customizing its handlers, you can modify a request’s lifecycle for various use cases. You can do this for an individual request object returned by DoFooRequest client methods or, more interestingly, for an entire service client object, which will affect every request processed by the client.

An example use case would be gathering additional information on retried requests. In this case, a simple addition to the Retry handler would provide information required for further debugging. Logging is another use case. For example, a logger function can be added to log every request, or a benchmark function can be added to profile requests. Let’s see how you can add a custom logger to different handler lists.


func customLogger(req *request.Request) {
	 log.Printf(Op: %s\nParams: %v\nTime:      %s\nRetryCount: %d\n\n",
	 req.Operation.Name, req.Params, req.Time, req.RetryCount
	)
}


svc.Handlers.Send.PushBack(customLogger)
svc.Handlers.Unmarshal.PushBack(func(req *request.Request) {
log.Println(“Unmarshal handler”)
customLogger(req)
})

We added a customLogger request handler to the Sign and Send handlers and a closure function in the Unmarshal handler. The only requirement for this function is that it takes a request.Request pointer. The customLogger, which was added to the Send handler, will log the operation’s name, the parameters passed, time executed, and the retry count. The anonymous function added to Unmarshal will log when the Unmarshal handler was executed and then call the customLogger.

These functions are being added to the end of the handler list. During the request’s Send step, it will iterate through the list and execute the functions. Having custom loggers can be useful when developing and debugging applications because they provide more visibility into each API operation’s request/response layers.

In addition to logging, sometimes benchmarking or profiling requests are important. This will give you an idea of what to expect when hitting your production environments.


dt := time.Time{}
svc.Handlers.Send.PushFront(func(req *request.Request) {
	 dt = time.Now()
})
svc.Handlers.Send.PushBack(func(req *request.Request) {
	 fmt.Println("SENDING", time.Now().Sub(dt))
})
// Output:
// SENDING 201.007364ms

Here we push one function to the beginning of the Send’s list and then compute the delta time in the handler added to the end of the Send’s list to measure how long the API request took to send and receive a response.

As we have shown in these code samples, request handlers allow you to easily customize the SDK’s behavior and add new functionality. If you have used handlers to augment the SDK’s request/response functionality, please let us know in the comments!