azure-sdk-for-go
azure-sdk-for-go copied to clipboard
Poller[T] Should expose headers of response
Feature Request
Our legacy code would grab the "Azure-AsyncOperation" out of a response and store if for something else to poll later. Now there is just resume token but it would be nice backcompat to let me get the headers out of a poller so I don't have to change both places at once and can move to resume token at a leisurely pace rather than having to get all recievers to adjust first.
My other options i the mean while are to inject a percallpolicy on the client to extract the header but since those are set on constructor rather than each call it means I have to isolate my clients more or I run into race conditons.
client := Newclient(..., options)
client.Beginwhatever.(...
opId := captureHeaderPolicy.OperationId
Other solutions are even mroe abstraction breaking
func getOperationID[T any](poller *runtime.Poller[T]) (string, error) {
token, err := poller.ResumeToken()
if err != nil {
return "", err
}
raw, err := pollers.ExtractToken(token)
if err != nil {
return "", err
}
var asJSON asyncPoller
if err := json.Unmarshal(raw, &asJSON); err != nil {
return "", err
}
return parseOperationIDFromURL(asyncPoller.Op)
Maybe every response should provide a way to inspect the underlying response, like the policy.Request
, which exposes .Raw()
Could all response type be extended to expose a .Raw() func that returns the underlying *http.Reponse
?
Our current hypothesis is that the majority of users don't care about the raw HTTP response which is why we don't directly expose it in the response envelopes (we did early on but removed it). It also created some confusion in some user studies.
We have runtime.WithCaptureResponse
that allows capturing of the raw HTTP response on an as-needed basis. Can you try it?
Oh, wow... yes, I think this "solves" it, but it's really weird and not discoverable at all.
looking at the doc, I just noticed this :
WithHTTPHeader
.
I wrote multiple PerRequest policies injecting headers on the outgoing request in our code... it's completely hidden and unnatural to use the context as the input of a function, and I'm not looking in the runtime package to pass a header to a request.
Why do we have an option argument if not to pass options. why not put everything in the context then? how do I know where to look?
"users don't care about the raw HTTP response". obviously we do.
when we get an error, the AzureError has an operationID and correlationID, and we want to track that in our app. Not just have a string in the error struct to parse. Response can have meaningful headers for other reasons, that are inaccessible. ETags, LastModified, etc... all these are on the response object.
I'm confused at what is confusing with .Raw()
returning the *http.Response
behind the typed struct.
I'm all for minimal API, but don't amputate me and make it harder than it should be. otherwise we'll end up rolling our own again to not have bad workarounds.
For APIs that return a model, we, and a few of the other languages, had users start looking in the RawResponse
field for the model which is why we removed it. That's not to say we couldn't add it later, but we wanted more data (it's easier to add than to remove).
I agree that our current solution for retrieving the raw response (in the success case) is unintuitive (and needs better docs/examples so it's discoverable). With our hypothesis that this is an "advanced" scenario, we felt it was acceptable for the API to be a bit clunky.
For the error case, we should be returning an *azcore.ResponseError
type which does provide access to the raw response. If you're seeing otherwise, please open an issue so we can get it fixed.
Using the options type for optional HTTP headers is an interesting idea, and not one we had considered. I agree this would make it much more discoverable. I will bring this up with the team.
@serbrech we're evaluating the idea to add the optional HTTP headers to the options
parameter type. Can you please provide some details on how many headers you add and how often (i.e. every API call, selects calls, etc).