Nike-Inc / riposte

Riposte is a Netty-based microservice framework for rapid development of production-ready HTTP APIs.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RequestBuilderWrapper's url can get out of sync with the wrapped AsyncHttpClient.BoundRequestBuilder's url

rabeyta opened this issue · comments

Summary of issue

If a user of RequestBuilderWrapper would change the underlining url inside the BoundRequestBuilder after they construct a RequestBuilderWrapper the two would get out of sync as the RequestBuilderWrapper's url is final but the BoundRequestBuilder allows the URL to be changed after initial creation.

This is an issue as the tracing started inside AsyncHttpClientHelper.executeAsyncHttpRequest reads the url it logs and passes around from the RequestBuilderWrapper due to not having a handle on the wrapped BoundRequestBuilder's values at the time before the request is made as they do not expose them.

Typical usage of the async client:

RequestBuilderWrapper builder = asyncHttpClient.getRequestBuilder(url, HttpMethod.GET);
builder.requestBuilder.setHeader(HEADER_NAME, HEADER_VALUE);
...

AsyncHttpClient.BoundRequestBuilder allows for the url to be set on it as such

builder.requestBuilder.setUrl("url");

This would bypass the RequestBuilderWrapper and the two values would no longer be in sync as the RequestBuilderWrapper's url is final and only set on creation of a RequestBuilderWrapper.

There is currently not an exposed way to update the URL of the RequestBuilderWrapper after it is created while maintaining the rest of the modified request.

Potential solution to this problem I thought about were:

  1. Add a 'setUrl' method on the RequestBuilderWrapper that updates the internal url and then updates the BoundRequestBuilder's url to enable someone a method that is clear and enable someone to stay in sync if they need to modify the url before before executing the request.
  2. Add a new method inside AsyncHttpClientHelper that takes in a BoundRequestBuilder + URL and then returns a new RequestBuilderWrapper based on the inputs.
  3. ?

I think I need to understand the use case. Are you trying to reuse a RequestBuilderWrapper but with a different URL?

I don't know that I ever tested whether the underlying BoundRequestBuilders were reusable. If you've tested it and it works, and this use case is desirable, then I guess I'd prefer exposing a new setUrl(...) method on RequestBuilderWrapper to keep them in sync (option 1), and put comments to make it clear that users should call the RequestBuilderWrapper method to adjust URL rather than calling the BoundRequestBuilder directly.

While you're at it, might as well do the same thing for method. Hmm, but what about the circuit breaker stuff? If you're changing URL/method, maybe you'd want to change the circuit breaker too?

The more I think about this the more I dislike the use case - I think I'd just prefer these be treated as single-use builders. I'm not sure the extra code and complexity is worth it here.

Eventually we'll be replacing all this stuff with a homerolled solution that supports any combination of streaming (chunked) or full request and response handling, and the clunkiness of RequestBuilderWrapper will go away. But that doesn't really help you right now. I'm leaning against wanting to do anything here but I'm open to the possibility that's it's necessary, so I think I might need you to convince me that the benefit of reusing these is worth the extra complexity and maintenance.

After looking at both of them and coding up solutions, I also prefer option 1.

The main use case I have is updating a URL after the RequestBuilderWrapper has been created. (request composer use case). I can take N number of inputs from a user and potentially replace bits in a url after I have created the RequestBuilderWrapper. I would ideally be updating a request and not creating a new one. In my case I could be replacing queryArgs or the path, depending on the inputs of the user in addition to setting headers or a body. The setUrl method of the BoundRequestBuilder handles both setting a url with query params or adding query params without issue as it would merge query params on the setUrl(String) in addition to the addQueryParam() methods as they were called on the builder.

To solve my use case without changing the current Riposte code...
I could refactor my logic and maintain the headers, body, query params, and signature calculator outside of the requestBuilder and only create the builder when I have a final state of a request...My only issue with this is that it feels against the nature of the request builder. The only issue with the overall design is the RequestBuilderWrapper is wrapping a mutable builder but maintaining immutable state of one of those fields. I understand why it was wrapped and done the way it is, and there is no perfect scenario in the current code base to not have a user mess themselves up if they tried.

If i call 'setUrl' on the BoundRequestBuilder I am able to update the Url and have everything work as expected, just the logging is not accurate and doesn't reflect reality of the actual calls.

On updating the method on the builder...You cannot update the Method after you have created a BoundRequestBuilder as the builder is created with a given Method, but it does allow setting pretty much everything else after creation. Once you call the 'build' method then its final and you cannot change it anymore (signature calculator has run, etc). Until build is called you can add / modify the request (headers, body, query param, etc) without issue.

Your circuit breaker point is a valid concern...but it is also an issue today since the two can get of sync (default circuit breaker based on the host inside the RequestBuildWrapper, which might not the value used to actually make the request). The circuit breaker comes into play when you actually go to execute the request, so providing a way to keep the Wrapper and BoundRequestBuilder in sync would prevent any issues with the circuit breaker.

I know this is a long reply, and I hope I covered the main issues and questions you had. Let me know if you need some clarification or more details on anything. Also which way you are leaning (I should solve my issue in another way, or add a setUrl to the Wrapper)

I guess I'm ok with doing this provided there's some relevant javadocs explaining what to do and what not to do. Seems like a reasonable stopgap until we provide our own client that doesn't require this builder-wrapper gunk.

On setting the HTTP method - you are correct that you have to create a BoundRequestBuilder by calling the method that corresponds to the HTTP method you want, but BoundRequestBuilder does have a public setMethod(String) method you can call afterward. Does it not work, or am I not following your point?

@nicmunroe It was my bad on reading the code. I was did not find it in the parent and didn't realize there was a setMethod on there. I knew the setUrl worked and was exposed from my testing and just was reading code around the setMethod.

I'll go through and add two methods: setMethod / setUrl and add some doc around what they do and when you would use them.

fixed in 0.10.0