Skip to content

Commit

Permalink
add java11 httpclient transmission
Browse files Browse the repository at this point in the history
  • Loading branch information
hexiaofeng committed May 6, 2024
1 parent 6c93275 commit 6a62e1f
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright © ${year} ${owner} (${email})
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jd.live.agent.plugin.transmission.jdkhttp.definition;

import com.jd.live.agent.core.bytekit.matcher.MatcherBuilder;
import com.jd.live.agent.core.extension.annotation.ConditionalOnClass;
import com.jd.live.agent.core.extension.annotation.ConditionalOnProperty;
import com.jd.live.agent.core.extension.annotation.Extension;
import com.jd.live.agent.core.plugin.definition.InterceptorDefinitionAdapter;
import com.jd.live.agent.core.plugin.definition.PluginDefinition;
import com.jd.live.agent.core.plugin.definition.PluginDefinitionAdapter;
import com.jd.live.agent.governance.config.GovernanceConfig;
import com.jd.live.agent.plugin.transmission.jdkhttp.interceptor.JavaHttpClientInterceptor;

/**
* Defines the instrumentation for the Java HTTP Client's HttpRequestBuilderImpl class.
* This class specifies the conditions under which the {@link JavaHttpClientInterceptor}
* is applied to modify or monitor HTTP requests during their construction.
*
* <p>The interceptor is conditionally applied based on the presence of the HttpRequestBuilderImpl
* class and the configuration specified by {@link GovernanceConfig#CONFIG_TRANSMISSION_ENABLED}.
* This allows for dynamic enabling or disabling of this instrumentation based on runtime configuration.</p>
*
* <p>Annotations used:</p>
* <ul>
* <li>{@link Extension} - Marks this class as an extension with a specific purpose within the framework,
* allowing it to be automatically discovered and applied.</li>
* <li>{@link ConditionalOnProperty} - Controls whether this plugin is active based on the
* {@code CONFIG_TRANSMISSION_ENABLED} configuration property.</li>
* <li>{@link ConditionalOnClass} - Ensures that this plugin is only loaded if HttpRequestBuilderImpl
* class is available in the runtime environment, preventing class loading issues in environments
* with different Java versions or configurations.</li>
* </ul>
*
* @see PluginDefinitionAdapter
* @see JavaHttpClientInterceptor
*/
@Extension(value = "JavaHttpClientDefinition", order = PluginDefinition.ORDER_TRANSMISSION)
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_TRANSMISSION_ENABLED, matchIfMissing = true)
@ConditionalOnClass(JavaHttpClientDefinition.TYPE_HTTP_REQUEST_BUILDER_IMPL)
public class JavaHttpClientDefinition extends PluginDefinitionAdapter {

protected static final String TYPE_HTTP_REQUEST_BUILDER_IMPL = "jdk.internal.net.http.HttpRequestBuilderImpl";

private static final String METHOD_BUILD = "build";

private static final String METHOD_BUILD_FOR_WEBSOCKET = "buildForWebSocket";

public JavaHttpClientDefinition() {
super(MatcherBuilder.named(TYPE_HTTP_REQUEST_BUILDER_IMPL),
new InterceptorDefinitionAdapter(
MatcherBuilder.in(METHOD_BUILD, METHOD_BUILD_FOR_WEBSOCKET),
new JavaHttpClientInterceptor()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,37 @@
import com.jd.live.agent.core.plugin.definition.PluginDefinition;
import com.jd.live.agent.core.plugin.definition.PluginDefinitionAdapter;
import com.jd.live.agent.governance.config.GovernanceConfig;
import com.jd.live.agent.plugin.transmission.jdkhttp.interceptor.JdkHttpClientInterceptor;
import com.jd.live.agent.plugin.transmission.jdkhttp.interceptor.SunHttpClientInterceptor;

/**
* Defines the instrumentation for intercepting the {@code writeRequests} method
* of the {@code sun.net.www.http.HttpClient} class. This class configures the
* conditions under which the {@link SunHttpClientInterceptor} is applied, aiming
* to monitor or modify HTTP request writing behavior.
*
* <p>The interceptor is conditionally applied based on the presence of the HttpClient
* class and the configuration specified by {@link GovernanceConfig#CONFIG_TRANSMISSION_ENABLED}.
* This allows for dynamic enabling or disabling of this instrumentation based on runtime
* configuration settings.</p>
*
* <p>Annotations used:</p>
* <ul>
* <li>{@link Extension} - Marks this class as an extension with a defined purpose
* within the framework, allowing it to be automatically discovered and applied.</li>
* <li>{@link ConditionalOnProperty} - Ensures that this plugin is active based on the
* {@code CONFIG_TRANSMISSION_ENABLED} configuration property.</li>
* <li>{@link ConditionalOnClass} - Guarantees that this plugin is only loaded if the
* {@code HttpClient} class is available in the runtime environment, preventing class
* loading issues in environments with different Java versions or configurations.</li>
* </ul>
*
* @see PluginDefinitionAdapter
* @see SunHttpClientInterceptor
*/
@Extension(value = "JdkHttpClientDefinition", order = PluginDefinition.ORDER_TRANSMISSION)
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_TRANSMISSION_ENABLED, matchIfMissing = true)
@ConditionalOnClass(JdkHttpClientDefinition.TYPE_HTTP_CLIENT)
public class JdkHttpClientDefinition extends PluginDefinitionAdapter {
@ConditionalOnClass(SunHttpClientDefinition.TYPE_HTTP_CLIENT)
public class SunHttpClientDefinition extends PluginDefinitionAdapter {

public static final String TYPE_HTTP_CLIENT = "sun.net.www.http.HttpClient";

Expand All @@ -39,11 +64,11 @@ public class JdkHttpClientDefinition extends PluginDefinitionAdapter {
"sun.net.www.http.PosterOutputStream"
};

public JdkHttpClientDefinition() {
public SunHttpClientDefinition() {
super(MatcherBuilder.named(TYPE_HTTP_CLIENT),
new InterceptorDefinitionAdapter(
MatcherBuilder.named(METHOD_WRITE_REQUESTS).
and(MatcherBuilder.arguments(ARGUMENT_WRITE_REQUESTS)),
new JdkHttpClientInterceptor()));
new SunHttpClientInterceptor()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright © ${year} ${owner} (${email})
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jd.live.agent.plugin.transmission.jdkhttp.interceptor;

import com.jd.live.agent.bootstrap.bytekit.context.ExecutableContext;
import com.jd.live.agent.core.plugin.definition.InterceptorAdaptor;
import com.jd.live.agent.governance.context.RequestContext;

import java.lang.reflect.Method;
import java.util.List;

import static com.jd.live.agent.core.util.type.ClassUtils.describe;

/**
* Interceptor for the Java HTTP Client's request builder implementation.
* This class uses reflection to access and modify the headers of HTTP requests
* constructed by the internal {@code HttpRequestBuilderImpl} class.
*
* <p>Due to module encapsulation in Java 9 and above, direct access to the internal
* HTTP request builder is not possible. This interceptor bypasses these restrictions
* to allow header manipulation for outgoing HTTP requests.</p>
*
* <p>Note: This interceptor is designed to work with specific versions of the JDK
* where {@code HttpRequestBuilderImpl} and its method {@code setHeader} are present.</p>
*/
public class JavaHttpClientInterceptor extends InterceptorAdaptor {

private static final String TYPE_HTTP_REQUEST_BUILDER_IMPL = "jdk.internal.net.http.HttpRequestBuilderImpl";

private static final String METHOD_SET_HEADER = "setHeader";

private final Method method;

public JavaHttpClientInterceptor() {
// use reflect to avoid module error in java 17.
Method method = null;
try {
// Use reflection to get the setHeader method from HttpRequestBuilderImpl
List<Method> methods = describe(Class.forName(TYPE_HTTP_REQUEST_BUILDER_IMPL))
.getMethodList()
.getMethods(METHOD_SET_HEADER);
method = methods == null || methods.isEmpty() ? null : methods.get(0);
} catch (ClassNotFoundException ignore) {
}
this.method = method;
}

@Override
public void onEnter(ExecutableContext ctx) {
if (method != null) {
attachTag(ctx.getTarget());
}
}

/**
* Attaches tags to the HTTP header using the {@code setHeader} method.
*
* @param header The HTTP request header object to which the tags will be attached.
*/
private void attachTag(Object header) {
RequestContext.traverse((key, value) -> addHeader(header, key, value));
}

/**
* Adds a single header to the HTTP request.
*
* @param header The HTTP request header object.
* @param key The header key.
* @param value The header value.
*/
private void addHeader(Object header, String key, String value) {
try {
method.invoke(header, key, value);
} catch (Throwable ignore) {
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,49 @@

import com.jd.live.agent.bootstrap.bytekit.context.ExecutableContext;
import com.jd.live.agent.core.plugin.definition.InterceptorAdaptor;
import com.jd.live.agent.core.util.type.ClassUtils;
import com.jd.live.agent.governance.context.RequestContext;

import java.lang.reflect.Method;
import java.util.List;

public class JdkHttpClientInterceptor extends InterceptorAdaptor {
import static com.jd.live.agent.core.util.type.ClassUtils.describe;

/**
* Intercepts HTTP requests made by {@code sun.net.www.http.HttpClient} to
* add custom headers to the request. This is achieved by using reflection
* to invoke the {@code add} method on {@code sun.net.www.MessageHeader}.
*
* <p>The use of reflection allows this interceptor to modify the request headers
* without being hindered by the module system introduced in Java 9, which restricts
* access to JDK internal APIs.</p>
*
* <p>This class is part of an instrumentation plugin designed to monitor or modify
* the behavior of HTTP requests for observability or governance purposes.</p>
*/
public class SunHttpClientInterceptor extends InterceptorAdaptor {

private static final String TYPE_MESSAGE_HEADER = "sun.net.www.MessageHeader";

private static final String METHOD_ADD = "add";

/**
* Method reference for {@code sun.net.www.MessageHeader#add(String, String)}.
* This is obtained via reflection to bypass module system restrictions.
*/
private final Method method;

public JdkHttpClientInterceptor() {
/**
* Constructs a new {@code SunHttpClientInterceptor}.
* Attempts to obtain a reference to the {@code add} method of
* {@code sun.net.www.MessageHeader} via reflection.
*/
public SunHttpClientInterceptor() {
// use reflect to avoid module error in java 17.
Method method = null;
try {
List<Method> methods = ClassUtils.describe(Class.forName(TYPE_MESSAGE_HEADER)).getMethodList().getMethods(METHOD_ADD);
List<Method> methods = describe(Class.forName(TYPE_MESSAGE_HEADER))
.getMethodList()
.getMethods(METHOD_ADD);
method = methods == null || methods.isEmpty() ? null : methods.get(0);
} catch (ClassNotFoundException ignore) {
}
Expand All @@ -49,10 +73,22 @@ public void onEnter(ExecutableContext ctx) {
}
}

/**
* Attaches custom headers to the HTTP request.
*
* @param header The {@code sun.net.www.MessageHeader} instance to which headers are added.
*/
private void attachTag(Object header) {
RequestContext.traverse((key, value) -> addHeader(header, key, value));
}

/**
* Adds a single header to the HTTP request.
*
* @param header The {@code sun.net.www.MessageHeader} instance to which a header is added.
* @param key The name of the header.
* @param value The value of the header.
*/
private void addHeader(Object header, String key, String value) {
try {
method.invoke(header, key, value);
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
com.jd.live.agent.plugin.transmission.jdkhttp.definition.JdkHttpClientDefinition
com.jd.live.agent.plugin.transmission.jdkhttp.definition.SunHttpClientDefinition
com.jd.live.agent.plugin.transmission.jdkhttp.definition.JavaHttpClientDefinition

0 comments on commit 6a62e1f

Please sign in to comment.