Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sun.net.www.protocol.http.HttpURLConnection.getOutputStream should tr… #6053

Merged
merged 11 commits into from
May 25, 2022

Conversation

jeanbisutti
Copy link
Member

The purpose of this PR is to fix #4597

@jeanbisutti jeanbisutti requested a review from a team as a code owner May 17, 2022 12:32
public String method(HttpURLConnection connection) {
String requestMethod = connection.getRequestMethod();

if ("GET".equals(requestMethod)) {
Copy link
Contributor

@laurit laurit May 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider doing this inside HttpUrlHttpAttributesGetter?
If you did it this way because there isn't an obvious way how to tell that getOutputStream was called in HttpUrlHttpAttributesGetter then here's a couple of ideas for you. You could try using HttpUrlState, add a boolean field to it that would signify that it really is a POST. Another way would be to use addContextCustomizer method from InstrumenterBuilder. The idea is to use addContextCustomizer to add a holder object into context where you can store info like is this really a POST. For example see https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/grizzly-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyErrorHolder.java. As you don't have access to context in HttpUrlHttpAttributesGetter.method you'll need to add a custom AttributesExtractor to your Instrumenter. Just leave HttpUrlHttpAttributesGetter as is and overwrite the request method attribute in your AttributesExtractor.onEnd. If you set the SemanticAttributes.HTTP_METHOD attribute in onEnd then it will work even when the span wasn't started from getOutputStream.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea would be to change the request type from HttpURLConnection to a value class that'd contain both HttpURLConnection and the String methodName

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your suggestions @laurit and @mateuszrzeszutek. In my last commit, I created a new implementation to not depend on the execution order of the connection's methods.

public String method(HttpURLConnection connection) {
String requestMethod = connection.getRequestMethod();

if ("GET".equals(requestMethod)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea would be to change the request type from HttpURLConnection to a value class that'd contain both HttpURLConnection and the String methodName


def connectionClassName = connection.getClass().getCanonicalName()

assert "sun.net.www.protocol.http.HttpURLConnection".equals(connectionClassName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will that work on non-OpenJDK JVMs? Can you add some assumption that will skip this test if this class is not present?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests seem to succeed with the OpenJ9 JVM.

I you want, I could add the following kind of code in case where the sun.net.www.protocol.http.HttpURLConnection class disappears one day from the JDK:

      if (!"sun.net.www.protocol.http.HttpURLConnection".equals(connectionClassName)) {
        return;
      }

I preferred to add only this kind of code if the problem will appear one day, to try to keep things simple.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nowadays everybody uses class library from openjdk so I wouldn't worry about this class being missing. Though I wouldn't probably build the instrumentation around sun.net.www.protocol.http.HttpURLConnection but rather instrument getOutpuStream in all subclasses of java.net.HttpURLConnection because for this use case, as far as I can tell, it isn't really important on which class getOutpuStream is called. Only the fact that it was called matters. Handling this in other classes besides sun.net.www.protocol.http.HttpURLConnection could come handy on weblogic where there is a custom url protocol handler (system property java.protocol.handler.pkgs can be used to change url protocol handlers).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I wouldn't probably build the instrumentation around sun.net.www.protocol.http.HttpURLConnection but rather instrument getOutpuStream in all subclasses of java.net.HttpURLConnection because for this use case, as far as I can tell, it isn't really important on which class getOutpuStream is called.

As far as I understand, the #4597 issue was created for the sun.net.www.protocol.http.HttpURLConnection class. This issue aims at reflecting the getOutputStream() behavior of the sun.net.www.protocol.http.HttpURLConnection class, extending java.net.HttpURLConnection abstract class. Could we be sure that all classes extending java.net.HttpURLConnection will implement a getOutputStream() method that will transform the GET to POST, as in the sun.net.www.protocol.http.HttpURLConnection class?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, the #4597 issue was created for the sun.net.www.protocol.http.HttpURLConnection class. This issue aims at reflecting the getOutputStream() behavior of the sun.net.www.protocol.http.HttpURLConnection class, extending java.net.HttpURLConnection abstract class. Could we be sure that all classes extending java.net.HttpURLConnection will implement a getOutputStream() method that will transform the GET to POST, as in the sun.net.www.protocol.http.HttpURLConnection class?

This is a good question. I believe we can check the value of getRequestMethod after calling getOutputStream, if it returns POST then it was a POST request even if it returned GET before calling getOutputStream.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good question. I believe we can check the value of getRequestMethod after calling getOutputStream, if it returns POST then it was a POST request even if it returned GET before calling getOutputStream.

I am not sure to have been rather explicit. If I was misunderstood., my question was about the expected behavior, not about implementation.

we can check the value of getRequestMethod after calling getOutputStream, if it returns POST then it was a POST request even if it returned GET before calling getOutputStream.

The getOutputStream() method of sun... class updates the method field. So, the getRequestMethod() should return POST even if the HTTP call was done with a GET method.

In term of expected behavior, do we want all classes extending java.net.HttpURLConnection to have an instrumentation behavior for the getOutputStream() method similar to this of sun.net.www.protocol.http.HttpURLConnection? We can't be sure that all classes extending java.net.HttpURLConnection implement the getOutputStream() method the same way as the sun.net.www.protocol.http.HttpURLConnection does (that is to say, changing GET into POST). As in #4597, it may be disturbing not to have instrumentation aligned with the code behavior?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments:

  1. I committed a new implementation to not depend on the execution order of the connection's methods. Could you please review it?

In term of expected behavior, do we want all classes extending java.net.HttpURLConnection to have an instrumentation behavior for the getOutputStream() method similar to this of sun.net.www.protocol.http.HttpURLConnection? We can't be sure that all classes extending java.net.HttpURLConnection implement the getOutputStream() method the same way as the sun.net.www.protocol.http.HttpURLConnection does (that is to say, changing GET into POST). As in https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/4597, it may be disturbing not to have instrumentation aligned with the code behavior?

The new implementation could be adjusted once the expected behavior is determined.

The new implementation updates the HTTP method attribute but not the span name. Does span name also need to be updated? If yes, please advise for the implementation. The HttpSpanNameExtractor has no access to Context. I have noticed that the Instrumenter class
span name extractor calls the span name extractor
and has access to the context. So, a possible implementation would be to add Context parameter to the existing method of the SpanNameExtractor interface. It may require updating the code in many places. Another option would be to allow the possibility to define a SpanNameExtractor and call it around here in Instrumenter class. If something needs to be done with the span name, perhaps it could be another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. If you believe it is best to keep the checks for sun.net.www.protocol.http.HttpURLConnection then that is ok.
  2. Nice catch about the span name. I think we should change it as it too. You could use
Span span = Span.fromContext(context);
span.udateName("HTTP " + requestMethod);

to update the span name too. You could update the span name at the same place where you change the value for HTTP_METHOD attribute.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code updated to set the span name, thank you for the trick

jeanbisutti and others added 2 commits May 18, 2022 14:58
Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>
@@ -116,6 +116,11 @@ public static void methodExit(
callDepth.getAndIncrement();
try {
scope.close();
Class<? extends HttpURLConnection> connectionClass = connection.getClass();

GetOutputStreamContext getOutputStreamContext =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use the value of connection.getRequestMethod here. If getOutputStream got an exception (maybe setDoOutput(true)) wasn't called then the request method isn't changed and we could still report it as GET.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point! I haved added a condition on the request method with a comment. I propose to leave the condition on the method name of HttpUrlConnection to try to make the code explicit.

return context.get(KEY);
}

public void set(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other places that use the same pattern we typically have static methods for setting and getting values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code changed

Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx @jeanbisutti!

I tend to agree with @laurit thought about implementing this behavior for all subclasses of HttpURLConnection. I would hazzard to guess that weblogic's HttpURLConnection implementation probably had to conform to the sun behavior just to avoid breaking applications which "worked on tomcat" 😄.

But I'm also ok with handling only the sun implementation.

Comment on lines +119 to +123
Class<? extends HttpURLConnection> connectionClass = connection.getClass();

String requestMethod = connection.getRequestMethod();
GetOutputStreamContext.set(
httpUrlState.context, connectionClass, methodName, requestMethod);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inlining connectionClass might be nice to get rid of the generic reference above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hazzard to guess that weblogic's HttpURLConnection implementation probably had to conform to the sun behavior

Maybe , maybe not. We may discuss this point and create a PR outside this issue? This PR was to fix #4597.

jeanbisutti and others added 5 commits May 24, 2022 10:21
…HttpUrlConnectionTest.groovy

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
…HttpUrlConnectionTest.groovy

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
…/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpMethodAttributeExtractor.java

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
@trask trask merged commit e8ee277 into open-telemetry:main May 25, 2022
@trask
Copy link
Member

trask commented May 25, 2022

thx @jeanbisutti!

@jeanbisutti
Copy link
Member Author

jeanbisutti commented May 25, 2022

Thank you very much @laurit, @mateuszrzeszutek and @trask for your help on this first issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

sun.net.www.protocol.http.HttpURLConnection.getOutputStream0() will change httpUrlConnection's method type
4 participants