Repository: pinpoint-apm/pinpoint
Issue: jdk http client can not profiling https request
Labels: enhancement
module:plugin

#### What version of pinpoint are you using?
master/v2.2.2

#### Describe the bug
the plugin jdk-http can only profiling/sampling http request, https request is not supported.


#### Expected behavior
request traced.

A PR would be made to fix it. 

Update: see PR #8024.

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/agent-testweb/jdk-http-plugin-testweb/README.md b/agent-testweb/jdk-http-plugin-testweb/README.md
new file mode 100644
index 000000000000..a09b57f7e64e
--- /dev/null
+++ b/agent-testweb/jdk-http-plugin-testweb/README.md
@@ -0,0 +1,16 @@
+
+## Install
+```
+$ mvnw -P pinpoint-jdk-http-plugin-testweb install -Dmaven.test.skip=true
+```
+
+## Run
+```
+$ mvnw -P pinpoint-jdk-http-plugin-testweb spring-boot:start
+```
+You can then access here: https://localhost:18443/
+
+## Stop
+```
+$ mvnw -P pinpoint-jdk-http-plugin-testweb spring-boot:stop
+```
diff --git a/agent-testweb/jdk-http-plugin-testweb/pom.xml b/agent-testweb/jdk-http-plugin-testweb/pom.xml
new file mode 100644
index 000000000000..5a805af40f2d
--- /dev/null
+++ b/agent-testweb/jdk-http-plugin-testweb/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.navercorp.pinpoint</groupId>
+        <artifactId>pinpoint-agent-testweb</artifactId>
+        <version>2.3.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>pinpoint-jdk-http-plugin-testweb</artifactId>
+
+    <packaging>jar</packaging>
+
+    <properties>
+        <pinpoint.agent.jvmargument>
+            ${pinpoint.agent.default.jvmargument}
+        </pinpoint.agent.jvmargument>
+
+        <pinpoint.server.port>18443</pinpoint.server.port>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/agent-testweb/jdk-http-plugin-testweb/src/main/resources/application.yml b/agent-testweb/jdk-http-plugin-testweb/src/main/resources/application.yml
new file mode 100644
index 000000000000..d26aac35ece6
--- /dev/null
+++ b/agent-testweb/jdk-http-plugin-testweb/src/main/resources/application.yml
@@ -0,0 +1,21 @@
+# Defined in commandlineArgument of agent-test pom.xml
+
+server:
+  port: 18443
+  http:
+    port: 18080
+  http2:
+    enabled: true
+  ssl:
+    key-alias: pinpoint-keystore
+    key-store: classpath:pinpoint-keystore.jks
+    key-store-type: JKS
+    key-store-password: 123456
+
+logging:
+  level:
+    root: info
+
+springdoc:
+  swagger-ui:
+    path: /
diff --git a/agent-testweb/pom.xml b/agent-testweb/pom.xml
index ffb8351e5823..3f0a602515ae 100644
--- a/agent-testweb/pom.xml
+++ b/agent-testweb/pom.xml
@@ -48,6 +48,7 @@
             -Dprofiler.sampling.rate=${pinpoint.agent.sampling-rate}
         </pinpoint.agent.default.jvmargument>
         <pinpoint.agent.jvmargument>${pinpoint.agent.default.jvmargument}</pinpoint.agent.jvmargument>
+        <pinpoint.server.port>18080</pinpoint.server.port>
     </properties>
 
     <modules>
@@ -59,6 +60,7 @@
         <module>dubbo-plugin-testweb</module>
 <!--        <module>spring-cloud-gateway-plugin-testweb</module>-->
 <!--        <module>spring-webflux-plugin-testweb</module>-->
+        <module>jdk-http-plugin-testweb</module>
     </modules>
 
 
@@ -131,7 +133,7 @@
                     <jvmArguments>${pinpoint.agent.jvmargument}</jvmArguments>
                     <agents>${maven.multiModuleProjectDirectory}/agent/target/pinpoint-agent-${project.version}/pinpoint-bootstrap.jar</agents>
                     <commandlineArguments>
-                        --server.port=18080
+                        --server.port=${pinpoint.server.port}
                         --logging.level.root=info
                         --springdoc.swagger-ui.path=/
                     </commandlineArguments>
diff --git a/agent/src/main/resources/profiles/local/pinpoint.config b/agent/src/main/resources/profiles/local/pinpoint.config
index aca4895e8dfd..10e94326c987 100644
--- a/agent/src/main/resources/profiles/local/pinpoint.config
+++ b/agent/src/main/resources/profiles/local/pinpoint.config
@@ -787,6 +787,7 @@ profiler.apache.httpclient4.io=true
 # JDK HTTPURLConnection                                   #
 ###########################################################
 # Profile parameter.
+profiler.jdk.http=true
 profiler.jdk.http.param=true
 
 ###########################################################
diff --git a/agent/src/main/resources/profiles/release/pinpoint.config b/agent/src/main/resources/profiles/release/pinpoint.config
index 97c8f9e1b160..a4c1b36b5690 100644
--- a/agent/src/main/resources/profiles/release/pinpoint.config
+++ b/agent/src/main/resources/profiles/release/pinpoint.config
@@ -783,6 +783,7 @@ profiler.apache.httpclient4.io=true
 # JDK HTTPURLConnection                                   #
 ###########################################################
 # Profile parameter.
+profiler.jdk.http=true
 profiler.jdk.http.param=true
 
 ###########################################################
diff --git a/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPlugin.java b/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPlugin.java
index d01307aa8bd1..fc27056e280e 100644
--- a/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPlugin.java
+++ b/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPlugin.java
@@ -24,6 +24,9 @@
 import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformCallback;
 import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplate;
 import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplateAware;
+import com.navercorp.pinpoint.bootstrap.interceptor.scope.ExecutionPolicy;
+import com.navercorp.pinpoint.bootstrap.logging.PLogger;
+import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
 import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;
 import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext;
 import com.navercorp.pinpoint.bootstrap.plugin.util.InstrumentUtils;
@@ -32,15 +35,27 @@
 /**
  * 
  * @author Jongho Moon
+ * @author yjqg6666
  *
  */
 public class JdkHttpPlugin implements ProfilerPlugin, TransformTemplateAware {
 
     private TransformTemplate transformTemplate;
 
+    private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
+
+    public static final String INTERCEPT_HTTPS_CLASS_NAME = "sun.net.www.protocol.https.HttpsURLConnectionImpl";
+
+
     @Override
     public void setup(ProfilerPluginSetupContext context) {
+        JdkHttpPluginConfig config = new JdkHttpPluginConfig(context.getConfig());
+        if (!config.isEnable()) {
+            logger.info("{} disabled", this.getClass().getSimpleName());
+            return;
+        }
         transformTemplate.transform("sun.net.www.protocol.http.HttpURLConnection", HttpURLConnectionTransform.class);
+        transformTemplate.transform(INTERCEPT_HTTPS_CLASS_NAME, HttpURLConnectionTransform.class);
     }
 
     public static class HttpURLConnectionTransform implements TransformCallback {
@@ -56,13 +71,13 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
             }
 
             final InstrumentMethod connectMethod = InstrumentUtils.findMethod(target, "connect");
-            connectMethod.addScopedInterceptor(HttpURLConnectionInterceptor.class, "HttpURLConnection");
+            connectMethod.addScopedInterceptor(HttpURLConnectionInterceptor.class, "HttpURLConnectionConnect", ExecutionPolicy.ALWAYS);
 
             final InstrumentMethod getInputStreamMethod = InstrumentUtils.findMethod(target, "getInputStream");
-            getInputStreamMethod.addScopedInterceptor(HttpURLConnectionInterceptor.class, "HttpURLConnection");
+            getInputStreamMethod.addScopedInterceptor(HttpURLConnectionInterceptor.class, "HttpURLConnectionInput", ExecutionPolicy.BOUNDARY);
 
             final InstrumentMethod getOutputStreamMethod = InstrumentUtils.findMethod(target, "getOutputStream");
-            getOutputStreamMethod.addScopedInterceptor(HttpURLConnectionInterceptor.class, "HttpURLConnection");
+            getOutputStreamMethod.addScopedInterceptor(HttpURLConnectionInterceptor.class, "HttpURLConnectionOutput", ExecutionPolicy.BOUNDARY);
 
             return target.toBytecode();
         }
diff --git a/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPluginConfig.java b/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPluginConfig.java
index 0b695c00a151..f93a78c727c4 100644
--- a/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPluginConfig.java
+++ b/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/JdkHttpPluginConfig.java
@@ -9,13 +9,19 @@
 public class JdkHttpPluginConfig {
 
     private final boolean param;
+    private final boolean enable;
     private final HttpDumpConfig httpDumpConfig;
 
     public JdkHttpPluginConfig(ProfilerConfig src) {
+        this.enable = src.readBoolean("profiler.jdk.http", true);
         this.param = src.readBoolean("profiler.jdk.http.param", true);
         this.httpDumpConfig = HttpDumpConfig.getDefault();
     }
 
+    public boolean isEnable() {
+        return enable;
+    }
+
     public boolean isParam() {
         return param;
     }
@@ -27,7 +33,8 @@ public HttpDumpConfig getHttpDumpConfig() {
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("JdkHttpPluginConfig{");
-        sb.append("param=").append(param);
+        sb.append("enable=").append(enable);
+        sb.append(", param=").append(param);
         sb.append(", httpDumpConfig=").append(httpDumpConfig);
         sb.append('}');
         return sb.toString();
diff --git a/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/interceptor/HttpURLConnectionInterceptor.java b/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/interceptor/HttpURLConnectionInterceptor.java
index 27775dfebf45..246bcbdb06e6 100644
--- a/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/interceptor/HttpURLConnectionInterceptor.java
+++ b/plugins/jdk-http/src/main/java/com/navercorp/pinpoint/plugin/jdk/http/interceptor/HttpURLConnectionInterceptor.java
@@ -19,6 +19,7 @@
 import com.navercorp.pinpoint.bootstrap.context.*;
 import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor;
 import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScope;
+import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScopeInvocation;
 import com.navercorp.pinpoint.bootstrap.logging.PLogger;
 import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
 import com.navercorp.pinpoint.bootstrap.plugin.request.*;
@@ -30,6 +31,7 @@
 /**
  * @author netspider
  * @author emeroad
+ * @author yjqg6666
  */
 public class HttpURLConnectionInterceptor implements AroundInterceptor {
     private static final Object TRACE_BLOCK_BEGIN_MARKER = new Object();
@@ -62,12 +64,16 @@ public void before(Object target, Object[] args) {
         if (isDebug) {
             logger.beforeInterceptor(target, args);
         }
+
+        if (target == null) {
+            return;
+        }
+
         Trace trace = traceContext.currentRawTraceObject();
         if (trace == null) {
             return;
         }
 
-        final HttpURLConnection request = (HttpURLConnection) target;
         boolean connected = false;
         if (target instanceof ConnectedGetter) {
             connected = ((ConnectedGetter) target)._$PINPOINT$_isConnected();
@@ -77,71 +83,80 @@ public void before(Object target, Object[] args) {
             connecting = ((ConnectingGetter) target)._$PINPOINT$_isConnecting();
         }
 
-        if (connected || connecting) {
-            return;
+        boolean addRequestHeader = !connected && !connecting;
+        if (isInterceptingHttps()) {
+            addRequestHeader = addRequestHeader && isInterceptingConnect();
         }
 
-        final boolean sampling = trace.canSampled();
-        if (!sampling) {
-            if (request != null) {
+        final HttpURLConnection request = (HttpURLConnection) target;
+        final boolean canSample = trace.canSampled();
+        if (canSample) {
+            scope.getCurrentInvocation().setAttachment(TRACE_BLOCK_BEGIN_MARKER);
+            final SpanEventRecorder recorder = trace.traceBlockBegin();
+            recorder.recordServiceType(JdkHttpConstants.SERVICE_TYPE);
+            if (addRequestHeader) {
+                final TraceId nextId = trace.getTraceId().getNextTraceId();
+                recorder.recordNextSpanId(nextId.getSpanId());
+                String host = getHost(request);
+                this.requestTraceWriter.write(request, nextId, host);
+            }
+        } else {
+            if (addRequestHeader) {
                 this.requestTraceWriter.write(request);
             }
-            return;
-        }
-
-        scope.getCurrentInvocation().setAttachment(TRACE_BLOCK_BEGIN_MARKER);
-
-        final SpanEventRecorder recorder = trace.traceBlockBegin();
-        recorder.recordServiceType(JdkHttpConstants.SERVICE_TYPE);
-        final TraceId nextId = trace.getTraceId().getNextTraceId();
-        recorder.recordNextSpanId(nextId.getSpanId());
-
-        if (request != null) {
-            String host = getHost(request);
-            this.requestTraceWriter.write(request, nextId, host);
         }
     }
 
-
-    private String getHost(HttpURLConnection httpURLConnection) {
-        final URL url = httpURLConnection.getURL();
-        if (url != null) {
-            final String host = url.getHost();
-            final int port = url.getPort();
-            if (host != null) {
-                return JdkHttpClientRequestAdaptor.getEndpoint(host, port);
-            }
-        }
-        return null;
-    }
-
     @Override
     public void after(Object target, Object[] args, Object result, Throwable throwable) {
         if (isDebug) {
             // do not log result
             logger.afterInterceptor(target, args);
         }
+        if (target == null) {
+            return;
+        }
 
         Trace trace = traceContext.currentTraceObject();
-        if (trace == null) {
+        if (trace == null || !trace.canSampled()) {
             return;
         }
 
-        Object marker = scope.getCurrentInvocation().getAttachment();
-        if (marker != TRACE_BLOCK_BEGIN_MARKER) {
+        final InterceptorScopeInvocation currentInvocation = scope.getCurrentInvocation();
+        if (TRACE_BLOCK_BEGIN_MARKER != currentInvocation.getAttachment()) {
             return;
         }
 
         try {
+            final HttpURLConnection request = (HttpURLConnection) target;
             final SpanEventRecorder recorder = trace.currentSpanEventRecorder();
             recorder.recordApi(descriptor);
             recorder.recordException(throwable);
-            final HttpURLConnection request = (HttpURLConnection) target;
-            if (request != null) {
-                this.clientRequestRecorder.record(recorder, request, throwable);
-            }
+            this.clientRequestRecorder.record(recorder, request, throwable);
         } finally {
+            currentInvocation.removeAttachment();
             trace.traceBlockEnd();
         }
     }
+
+    private String getHost(HttpURLConnection httpURLConnection) {
+        final URL url = httpURLConnection.getURL();
+        if (url != null) {
+            final String host = url.getHost();
+            final int port = url.getPort();
+            if (host != null) {
+                return JdkHttpClientRequestAdaptor.getEndpoint(host, port);
+            }
+        }
+        return null;
+    }
+
+    private boolean isInterceptingConnect() {
+        return "connect".contentEquals(this.descriptor.getMethodName());
+    }
+
+    private boolean isInterceptingHttps() {
+        return JdkHttpPlugin.INTERCEPT_HTTPS_CLASS_NAME.contentEquals(this.descriptor.getClassName());
+    }
+
 }


Test patch:
diff --git a/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/CertService.java b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/CertService.java
new file mode 100644
index 000000000000..d460e6b38baf
--- /dev/null
+++ b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/CertService.java
@@ -0,0 +1,100 @@
+package com.pinpointest.plugin;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.InputStream;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.util.Enumeration;
+import java.util.Objects;
+
+@Component
+public class CertService {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final Resource keyStorePath;
+    private final String password;
+    private final String keyStoreType;
+
+    private SSLContext sslContext;
+
+    public CertService(@Value("${server.ssl.key-store-type}") String keyStoreType,
+                       @Value("${server.ssl.key-store}") Resource keyStorePath,
+                       @Value("${server.ssl.key-store-password}") String password) {
+        this.keyStoreType = Objects.requireNonNull(keyStoreType, "keyStoreType");
+        this.keyStorePath = Objects.requireNonNull(keyStorePath, "keyStorePath");
+        this.password = Objects.requireNonNull(password, "password");
+    }
+
+    @PostConstruct
+    public void importCertForLocalCall() throws Exception {
+        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+        logger.info("KeyStore type:{}", keyStore.getType());
+        try (InputStream inputStream = keyStorePath.getInputStream()) {
+            keyStore.load(inputStream, password.toCharArray());
+        }
+
+
+        Enumeration<String> aliases = keyStore.aliases();
+        while (aliases.hasMoreElements()) {
+            String alias = aliases.nextElement();
+            logger.info("KeyStore aliases:{}", alias);
+            Certificate certificate = keyStore.getCertificate(alias);
+            logger.debug("Certificate:{}", certificate);
+        }
+
+        KeyManagerFactory kmf = newKeyManagerFactory(keyStore);
+        TrustManagerFactory tmf = newTrustManagerFactory(keyStore);
+
+
+        logger.info("KeyManagerFactory Algorithm:{} Provider:{}", kmf.getAlgorithm(), kmf.getProvider());
+        logger.info("TrustManagerFactory Algorithm:{} Provider:{}", tmf.getAlgorithm(), tmf.getProvider());
+
+        this.sslContext = newSSLContext(kmf, tmf);
+    }
+
+    private SSLContext newSSLContext(KeyManagerFactory kmf, TrustManagerFactory tmf) throws NoSuchAlgorithmException, KeyManagementException {
+        SSLContext sslContext = SSLContext.getInstance("SSL");
+
+        KeyManager[] keyManagers = kmf.getKeyManagers();
+        TrustManager[] trustManagers = tmf.getTrustManagers();
+        sslContext.init(keyManagers, trustManagers, new SecureRandom());
+
+        return sslContext;
+    }
+
+    private TrustManagerFactory newTrustManagerFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException {
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        tmf.init(keyStore);
+        return tmf;
+    }
+
+    private KeyManagerFactory newKeyManagerFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        kmf.init(keyStore, password.toCharArray());
+        return kmf;
+    }
+
+    @Bean
+    public SSLContext getSslContext() {
+        return sslContext;
+    }
+
+}
diff --git a/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/JdkHttpPluginTestStarter.java b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/JdkHttpPluginTestStarter.java
new file mode 100644
index 000000000000..ba004484b464
--- /dev/null
+++ b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/JdkHttpPluginTestStarter.java
@@ -0,0 +1,32 @@
+package com.pinpointest.plugin;
+
+import org.apache.catalina.connector.Connector;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
+import org.springframework.context.annotation.Bean;
+
+@SpringBootApplication
+public class JdkHttpPluginTestStarter {
+
+    public static void main(String[] args) {
+        SpringApplication.run(JdkHttpPluginTestStarter.class, args);
+    }
+
+    @Bean
+    public ServletWebServerFactory serverFactory(@Value("${server.http.port}") int httpPort) {
+        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
+        tomcat.addAdditionalTomcatConnectors(createHttpConnector(httpPort));
+        tomcat.setDisableMBeanRegistry(false);
+        return tomcat;
+    }
+
+    private Connector createHttpConnector(int httpPort) {
+        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
+        connector.setPort(httpPort);
+        return connector;
+    }
+
+}
diff --git a/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/controller/HelloWorldController.java b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/controller/HelloWorldController.java
new file mode 100644
index 000000000000..48e2cd1e50ec
--- /dev/null
+++ b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/controller/HelloWorldController.java
@@ -0,0 +1,13 @@
+package com.pinpointest.plugin.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HelloWorldController {
+
+    @RequestMapping("/helloworld")
+    public String helloWorld() {
+        return "Hello-World";
+    }
+}
diff --git a/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/controller/JdkHttpPluginController.java b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/controller/JdkHttpPluginController.java
new file mode 100644
index 000000000000..e6893c63ca72
--- /dev/null
+++ b/agent-testweb/jdk-http-plugin-testweb/src/main/java/com/pinpointest/plugin/controller/JdkHttpPluginController.java
@@ -0,0 +1,132 @@
+package com.pinpointest.plugin.controller;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Objects;
+
+@RestController
+public class JdkHttpPluginController {
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    public static final String HTTP_TARGET = "%s://localhost:%s/helloworld";
+
+
+    private final SSLContext sslContext;
+    private final int httpsPort;
+    private final int httpPort;
+
+    public JdkHttpPluginController(@Value("${server.port}") int httpsPort,
+                                   @Value("${server.http.port}") int httpPort,
+                                   SSLContext sslContext) {
+        this.httpsPort = httpsPort;
+        this.httpPort = httpPort;
+        this.sslContext = Objects.requireNonNull(sslContext, "sslContext");
+    }
+
+    private String getHttpUrl(String schema) {
+        if (schema.equalsIgnoreCase("https")) {
+            return String.format(HTTP_TARGET, schema, httpsPort);
+        } else {
+            return String.format(HTTP_TARGET, schema, httpPort);
+        }
+    }
+
+
+    @GetMapping(value = "/http-get")
+    public String httpGet() {
+        return httpCall("http", "GET");
+    }
+
+
+    @GetMapping(value = "/https-get")
+    public String httpsGet() {
+        return httpCall("https", "GET");
+    }
+
+
+    @GetMapping(value = "/http-post")
+    public String httpPost() {
+        return httpCall("http", "POST");
+    }
+
+    @GetMapping(value = "/https-post")
+    public String httpsPost() {
+        return httpCall("https", "GET");
+    }
+
+
+    @GetMapping(value = "/error")
+    public String error() throws IOException {
+        try {
+            final URL url = new URL("http://localhost:39000");
+            HttpURLConnection connection = openConnection(url);
+            connection.connect();
+        } catch (IOException e) {
+            return e.getMessage();
+        }
+        return "success";
+    }
+
+    private String httpCall(String schema, String method) {
+        final URL url = newURL(getHttpUrl(schema));
+        try {
+            HttpURLConnection connection = openConnection(url);
+            connection.setRequestMethod(method);
+            connection.connect();
+
+            final String contents = readStream(connection);
+            final URL targetURL = connection.getURL();
+
+            String log = String.format("url:%s contents:%s", targetURL, contents);
+            logger.info(log);
+            return log;
+        } catch (Exception e) {
+            logger.warn("{} open error", url, e);
+            return "fail";
+        }
+    }
+
+    private HttpURLConnection openConnection(URL url) throws IOException {
+        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
+        if (httpURLConnection instanceof HttpsURLConnection) {
+            HttpsURLConnection httpsURLConnection = (HttpsURLConnection) httpURLConnection;
+            httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
+            httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
+                @Override
+                public boolean verify(String hostname, SSLSession session) {
+                    return "localhost".equalsIgnoreCase(hostname);
+                }
+            });
+        }
+        return httpURLConnection;
+    }
+
+    private String readStream(HttpURLConnection connection) throws IOException {
+        try (InputStream inputStream = connection.getInputStream()) {
+            return IOUtils.toString(inputStream, connection.getContentEncoding());
+        }
+    }
+
+    private URL newURL(String spec) {
+        try {
+            return new URL(spec);
+        } catch (MalformedURLException exception) {
+            throw new IllegalArgumentException("invalid url" + spec, exception);
+        }
+    }
+
+}