You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2020/09/03 03:06:14 UTC

[skywalking] 01/01: Support SSL for prometheus telemetry and prometheus fetcher

This is an automated email from the ASF dual-hosted git repository.

hanahmily pushed a commit to branch telemetry-ssl
in repository https://gitbox.apache.org/repos/asf/skywalking.git

commit 08107490dc45abe33bf2751b20f43042936203e8
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Thu Sep 3 11:05:06 2020 +0800

    Support SSL for prometheus telemetry and prometheus fetcher
    
    Signed-off-by: Gao Hongtao <ha...@gmail.com>
---
 .../src/main/resources/application.yml             |   3 +
 .../main/resources/fetcher-prom-rules/self.yaml    |   3 +-
 .../core/metric/promethues/rule/StaticConfig.java  |   2 +-
 .../rule/{StaticConfig.java => Target.java}        |   8 +-
 .../server/fetcher/prometheus/http/HttpClient.java |  88 +++++++++++++++++
 .../fetcher/prometheus/http/HttpClientHandler.java |  64 +++++++++++++
 .../prometheus/http/HttpClientInitializer.java     |  55 +++++++++++
 .../provider/PrometheusFetcherProvider.java        |  22 ++---
 .../resources/fetcher-prom-rules/localhost.yaml    |   2 +-
 .../library/server/grpc/ssl/DynamicSslContext.java |  84 +++--------------
 .../AbstractSslContext.java}                       |  55 +++--------
 .../library/server/ssl/HttpDynamicSslContext.java  |  64 +++++++++++++
 .../server/{grpc => }/ssl/PrivateKeyUtil.java      |   6 +-
 .../server-telemetry/telemetry-prometheus/pom.xml  |  12 ++-
 .../telemetry/prometheus/PrometheusConfig.java     |   3 +
 .../prometheus/PrometheusTelemetryProvider.java    |   7 +-
 .../prometheus/httpserver/HttpServer.java          |  70 ++++++++++++++
 .../prometheus/httpserver/HttpServerHandler.java   | 105 +++++++++++++++++++++
 .../httpserver/HttpServerInitializer.java          |  44 +++++++++
 19 files changed, 549 insertions(+), 148 deletions(-)

diff --git a/oap-server/server-bootstrap/src/main/resources/application.yml b/oap-server/server-bootstrap/src/main/resources/application.yml
index f026062..2dbf112 100755
--- a/oap-server/server-bootstrap/src/main/resources/application.yml
+++ b/oap-server/server-bootstrap/src/main/resources/application.yml
@@ -299,6 +299,9 @@ telemetry:
   prometheus:
     host: ${SW_TELEMETRY_PROMETHEUS_HOST:0.0.0.0}
     port: ${SW_TELEMETRY_PROMETHEUS_PORT:1234}
+    sslEnabled: ${SW_TELEMETRY_PROMETHEUS_SSL_ENABLED:false}
+    sslKeyPath: ${SW_TELEMETRY_PROMETHEUS_SSL_KEY_PATH:""}
+    sslCertChainPath: ${SW_TELEMETRY_PROMETHEUS_SSL_CERT_CHAIN_PATH:""}
 
 configuration:
   selector: ${SW_CONFIGURATION:none}
diff --git a/oap-server/server-bootstrap/src/main/resources/fetcher-prom-rules/self.yaml b/oap-server/server-bootstrap/src/main/resources/fetcher-prom-rules/self.yaml
index c6d1a53..19b03ad 100644
--- a/oap-server/server-bootstrap/src/main/resources/fetcher-prom-rules/self.yaml
+++ b/oap-server/server-bootstrap/src/main/resources/fetcher-prom-rules/self.yaml
@@ -34,7 +34,8 @@ metricsPath: /metrics
 staticConfig:
   # targets will be labeled as "instance"
   targets:
-    - localhost:1234
+    - url: http://localhost:1234
+      sslCaFilePath:
   labels:
     service: oap-server
 metricsRules:
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/StaticConfig.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/StaticConfig.java
index 03a1c93..f591bce 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/StaticConfig.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/StaticConfig.java
@@ -26,6 +26,6 @@ import lombok.NoArgsConstructor;
 @Data
 @NoArgsConstructor
 public class StaticConfig {
-    private List<String> targets;
+    private List<Target> targets;
     private Map<String, String> labels;
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/StaticConfig.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/Target.java
similarity index 86%
copy from oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/StaticConfig.java
copy to oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/Target.java
index 03a1c93..f2364a3 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/StaticConfig.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/metric/promethues/rule/Target.java
@@ -18,14 +18,12 @@
 
 package org.apache.skywalking.oap.server.core.metric.promethues.rule;
 
-import java.util.List;
-import java.util.Map;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
 @Data
 @NoArgsConstructor
-public class StaticConfig {
-    private List<String> targets;
-    private Map<String, String> labels;
+public class Target {
+    private String url;
+    private String sslCaFilePath;
 }
diff --git a/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClient.java b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClient.java
new file mode 100644
index 0000000..1f03084
--- /dev/null
+++ b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClient.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.fetcher.prometheus.http;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpVersion;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Objects;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+import lombok.Builder;
+import org.apache.skywalking.oap.server.library.server.ssl.HttpDynamicSslContext;
+
+@Builder
+public class HttpClient {
+
+    private final String url;
+
+    private final String caFilePath;
+
+    public String request() throws URISyntaxException, InterruptedException {
+        URI uri = new URI(url);
+        String scheme = uri.getScheme() == null ? "http" : uri.getScheme();
+        String host = uri.getHost() == null ? "127.0.0.1" : uri.getHost();
+        int port = uri.getPort();
+
+        // Configure SSL context if necessary.
+        final boolean ssl = "https".equalsIgnoreCase(scheme);
+        final HttpDynamicSslContext sslCtx = ssl ? HttpDynamicSslContext.forClient(caFilePath) : null;
+
+        // Configure the client.
+        EventLoopGroup group = new NioEventLoopGroup();
+        BlockingQueue<String> channel = new SynchronousQueue<>();
+        try {
+            Bootstrap b = new Bootstrap();
+            b.group(group)
+                .channel(NioSocketChannel.class)
+                .handler(new HttpClientInitializer(sslCtx, channel));
+
+            // Make the connection attempt.
+            Channel ch = b.connect(host, port).sync().channel();
+
+            // Prepare the HTTP request.
+            HttpRequest request = new DefaultFullHttpRequest(
+                HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath(), Unpooled.EMPTY_BUFFER);
+            request.headers().set(HttpHeaderNames.HOST, host);
+            request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
+            request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.TEXT_PLAIN);
+
+            // Send the HTTP request.
+            ch.writeAndFlush(request);
+
+            return Objects.requireNonNull(channel.poll(10, TimeUnit.SECONDS), "Request timeout");
+        } finally {
+            // Shut down executor threads to exit.
+            group.shutdownGracefully();
+        }
+    }
+
+}
diff --git a/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClientHandler.java b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClientHandler.java
new file mode 100644
index 0000000..c76373f
--- /dev/null
+++ b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClientHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.fetcher.prometheus.http;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpObject;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.util.CharsetUtil;
+import java.util.concurrent.BlockingQueue;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@RequiredArgsConstructor
+@Slf4j
+public class HttpClientHandler extends SimpleChannelInboundHandler<HttpObject> {
+
+    private final BlockingQueue<String> channel;
+
+    private final StringBuilder buf = new StringBuilder();
+
+    @Override
+    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
+        if (msg instanceof HttpResponse) {
+            buf.setLength(0);
+        }
+        if (msg instanceof HttpContent) {
+            HttpContent content = (HttpContent) msg;
+            buf.append(content.content().toString(CharsetUtil.UTF_8));
+            if (content instanceof LastHttpContent) {
+                try {
+                    channel.put(buf.toString());
+                } catch (InterruptedException e) {
+                    ctx.fireExceptionCaught(e);
+                }
+                ctx.close();
+            }
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        log.error("HTTP request error", cause);
+        ctx.close();
+    }
+}
diff --git a/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClientInitializer.java b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClientInitializer.java
new file mode 100644
index 0000000..4669c70
--- /dev/null
+++ b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/http/HttpClientInitializer.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.fetcher.prometheus.http;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpContentDecompressor;
+import io.netty.handler.ssl.SslContext;
+import java.util.concurrent.BlockingQueue;
+
+public class HttpClientInitializer extends ChannelInitializer<SocketChannel> {
+
+    private final SslContext sslCtx;
+    private final BlockingQueue<String> channel;
+
+    public HttpClientInitializer(SslContext sslCtx, BlockingQueue<String> channel) {
+        this.sslCtx = sslCtx;
+        this.channel = channel;
+    }
+
+    @Override
+    public void initChannel(SocketChannel ch) {
+        ChannelPipeline p = ch.pipeline();
+
+        // Enable HTTPS if necessary.
+        if (sslCtx != null) {
+            p.addLast(sslCtx.newHandler(ch.alloc()));
+        }
+
+        p.addLast(new HttpClientCodec());
+
+        // Remove the following line if you don't want automatic content decompression.
+        p.addLast(new HttpContentDecompressor());
+
+        p.addLast(new HttpClientHandler(channel));
+    }
+}
diff --git a/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/provider/PrometheusFetcherProvider.java b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/provider/PrometheusFetcherProvider.java
index ffd3c76..f3b796a 100644
--- a/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/provider/PrometheusFetcherProvider.java
+++ b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/fetcher/prometheus/provider/PrometheusFetcherProvider.java
@@ -20,6 +20,8 @@ package org.apache.skywalking.oap.server.fetcher.prometheus.provider;
 
 import com.google.common.collect.Maps;
 import io.vavr.CheckedFunction1;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import java.time.Duration;
 import java.util.Collection;
 import java.util.LinkedList;
@@ -29,15 +31,14 @@ import java.util.Objects;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
+import org.apache.commons.codec.Charsets;
 import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.analysis.meter.MeterSystem;
 import org.apache.skywalking.oap.server.core.metric.promethues.PrometheusMetricConverter;
 import org.apache.skywalking.oap.server.core.metric.promethues.rule.Rule;
 import org.apache.skywalking.oap.server.core.metric.promethues.rule.Rules;
 import org.apache.skywalking.oap.server.core.metric.promethues.rule.StaticConfig;
+import org.apache.skywalking.oap.server.fetcher.prometheus.http.HttpClient;
 import org.apache.skywalking.oap.server.fetcher.prometheus.module.PrometheusFetcherModule;
 import org.apache.skywalking.oap.server.library.module.ModuleConfig;
 import org.apache.skywalking.oap.server.library.module.ModuleDefine;
@@ -51,7 +52,6 @@ import org.apache.skywalking.oap.server.library.util.prometheus.metrics.MetricFa
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toList;
 
 public class PrometheusFetcherProvider extends ModuleProvider {
@@ -59,8 +59,6 @@ public class PrometheusFetcherProvider extends ModuleProvider {
 
     private final PrometheusFetcherConfig config;
 
-    private final OkHttpClient client = new OkHttpClient();
-
     private List<Rule> rules;
 
     private ScheduledExecutorService ses;
@@ -115,13 +113,11 @@ public class PrometheusFetcherProvider extends ModuleProvider {
                     StaticConfig sc = r.getStaticConfig();
                     long now = System.currentTimeMillis();
                     converter.toMeter(sc.getTargets().stream()
-                        .map(CheckedFunction1.liftTry(url -> {
-                            Request request = new Request.Builder()
-                                .url(String.format("http://%s%s", url, r.getMetricsPath().startsWith("/") ? r.getMetricsPath() : "/" + r.getMetricsPath()))
-                                .build();
+                        .map(CheckedFunction1.liftTry(target -> {
                             List<Metric> result = new LinkedList<>();
-                            try (Response response = client.newCall(request).execute()) {
-                                Parser p = Parsers.text(requireNonNull(response.body()).byteStream());
+                            String content = HttpClient.builder().url(target.getUrl()).caFilePath(target.getSslCaFilePath()).build().request();
+                            try (InputStream targetStream = new ByteArrayInputStream(content.getBytes(Charsets.UTF_8))) {
+                                Parser p = Parsers.text(targetStream);
                                 MetricFamily mf;
 
                                 while ((mf = p.parse(now)) != null) {
@@ -131,7 +127,7 @@ public class PrometheusFetcherProvider extends ModuleProvider {
                                                 return;
                                             }
                                             Map<String, String> extraLabels = Maps.newHashMap(sc.getLabels());
-                                            extraLabels.put("instance", url);
+                                            extraLabels.put("instance", target.getUrl());
                                             extraLabels.forEach((key, value) -> {
                                                 if (metric.getLabels().containsKey(key)) {
                                                     metric.getLabels().put("exported_" + key, metric.getLabels().get(key));
diff --git a/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/test/resources/fetcher-prom-rules/localhost.yaml b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/test/resources/fetcher-prom-rules/localhost.yaml
index 9c38e0f..89b5834 100644
--- a/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/test/resources/fetcher-prom-rules/localhost.yaml
+++ b/oap-server/server-fetcher-plugin/prometheus-fetcher-plugin/src/test/resources/fetcher-prom-rules/localhost.yaml
@@ -19,7 +19,7 @@ metricsPath: /metrics
 staticConfig:
   # targets will be labeled as "instance"
   targets:
-    - localhost:1234
+    - url: http://localhost:1234
   labels:
     app: test-oap
 metricsRules:
diff --git a/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/DynamicSslContext.java b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/DynamicSslContext.java
index 80bc79e..a98125c 100644
--- a/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/DynamicSslContext.java
+++ b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/DynamicSslContext.java
@@ -19,27 +19,20 @@
 package org.apache.skywalking.oap.server.library.server.grpc.ssl;
 
 import io.grpc.netty.GrpcSslContexts;
-import io.netty.buffer.ByteBufAllocator;
-import io.netty.handler.ssl.ApplicationProtocolNegotiator;
-import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.SslProvider;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.file.Paths;
 import java.security.GeneralSecurityException;
-import java.util.List;
-import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSessionContext;
-import org.apache.skywalking.oap.server.library.util.MultipleFilesChangeMonitor;
+import org.apache.skywalking.oap.server.library.server.ssl.AbstractSslContext;
+import org.apache.skywalking.oap.server.library.server.ssl.PrivateKeyUtil;
 
 /**
  * Load SslContext dynamically.
  */
-public class DynamicSslContext extends SslContext {
-    private final MultipleFilesChangeMonitor monitor;
-    private volatile SslContext ctx;
+public class DynamicSslContext extends AbstractSslContext {
 
     public static DynamicSslContext forServer(final String privateKeyFile, final String certChainFile) {
         return new DynamicSslContext(privateKeyFile, certChainFile);
@@ -49,86 +42,33 @@ public class DynamicSslContext extends SslContext {
         return new DynamicSslContext(caFile);
     }
 
-    private DynamicSslContext(final String privateKeyFile, final String certChainFile) {
-        updateContext(privateKeyFile, certChainFile);
-        monitor = new MultipleFilesChangeMonitor(
-            10,
-            readableContents -> updateContext(privateKeyFile, certChainFile),
-            certChainFile,
-            privateKeyFile);
+    protected DynamicSslContext(String privateKeyFile, String certChainFile) {
+        super(privateKeyFile, certChainFile);
     }
 
-    private DynamicSslContext(final String caFile) {
-        updateContext(caFile);
-        monitor = new MultipleFilesChangeMonitor(
-            10,
-            readableContents -> updateContext(caFile),
-            caFile);
+    protected DynamicSslContext(String caFile) {
+        super(caFile);
     }
 
-    private void updateContext(String caFile) {
+    protected void updateContext(String caFile) {
         try {
-            ctx = GrpcSslContexts.forClient().trustManager(Paths.get(caFile).toFile()).build();
+            setCtx(GrpcSslContexts.forClient().trustManager(Paths.get(caFile).toFile()).build());
         } catch (SSLException e) {
             throw new IllegalArgumentException(e);
         }
     }
 
-    private void updateContext(final String privateKeyFile, final String certChainFile) {
+    protected void updateContext(final String privateKeyFile, final String certChainFile) {
         try {
-            ctx = GrpcSslContexts
+            setCtx(GrpcSslContexts
                 .configure(SslContextBuilder
                     .forServer(
                         new FileInputStream(Paths.get(certChainFile).toFile()),
                         PrivateKeyUtil.loadDecryptionKey(privateKeyFile)),
                     SslProvider.OPENSSL)
-                .build();
+                .build());
         } catch (GeneralSecurityException | IOException e) {
             throw new IllegalArgumentException(e);
         }
     }
-
-    public void start() {
-       monitor.start();
-    }
-
-    @Override
-    public final boolean isClient() {
-        return ctx.isClient();
-    }
-
-    @Override
-    public final List<String> cipherSuites() {
-        return ctx.cipherSuites();
-    }
-
-    @Override
-    public final long sessionCacheSize() {
-        return ctx.sessionCacheSize();
-    }
-
-    @Override
-    public final long sessionTimeout() {
-        return ctx.sessionTimeout();
-    }
-
-    @Override
-    public final ApplicationProtocolNegotiator applicationProtocolNegotiator() {
-        return ctx.applicationProtocolNegotiator();
-    }
-
-    @Override
-    public final SSLEngine newEngine(ByteBufAllocator alloc) {
-        return ctx.newEngine(alloc);
-    }
-
-    @Override
-    public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
-        return ctx.newEngine(alloc, peerHost, peerPort);
-    }
-
-    @Override
-    public final SSLSessionContext sessionContext() {
-        return ctx.sessionContext();
-    }
 }
diff --git a/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/DynamicSslContext.java b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/AbstractSslContext.java
similarity index 60%
copy from oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/DynamicSslContext.java
copy to oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/AbstractSslContext.java
index 80bc79e..6ea8b3c 100644
--- a/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/DynamicSslContext.java
+++ b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/AbstractSslContext.java
@@ -16,40 +16,25 @@
  *
  */
 
-package org.apache.skywalking.oap.server.library.server.grpc.ssl;
+package org.apache.skywalking.oap.server.library.server.ssl;
 
-import io.grpc.netty.GrpcSslContexts;
 import io.netty.buffer.ByteBufAllocator;
 import io.netty.handler.ssl.ApplicationProtocolNegotiator;
 import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-import io.netty.handler.ssl.SslProvider;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.security.GeneralSecurityException;
 import java.util.List;
 import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSessionContext;
+import lombok.AccessLevel;
+import lombok.Setter;
 import org.apache.skywalking.oap.server.library.util.MultipleFilesChangeMonitor;
 
-/**
- * Load SslContext dynamically.
- */
-public class DynamicSslContext extends SslContext {
+public abstract class AbstractSslContext extends SslContext {
     private final MultipleFilesChangeMonitor monitor;
-    private volatile SslContext ctx;
-
-    public static DynamicSslContext forServer(final String privateKeyFile, final String certChainFile) {
-        return new DynamicSslContext(privateKeyFile, certChainFile);
-    }
 
-    public static DynamicSslContext forClient(final String caFile) {
-        return new DynamicSslContext(caFile);
-    }
+    @Setter(AccessLevel.PROTECTED)
+    private volatile SslContext ctx;
 
-    private DynamicSslContext(final String privateKeyFile, final String certChainFile) {
+    protected AbstractSslContext(final String privateKeyFile, final String certChainFile) {
         updateContext(privateKeyFile, certChainFile);
         monitor = new MultipleFilesChangeMonitor(
             10,
@@ -58,7 +43,7 @@ public class DynamicSslContext extends SslContext {
             privateKeyFile);
     }
 
-    private DynamicSslContext(final String caFile) {
+    protected AbstractSslContext(final String caFile) {
         updateContext(caFile);
         monitor = new MultipleFilesChangeMonitor(
             10,
@@ -66,30 +51,12 @@ public class DynamicSslContext extends SslContext {
             caFile);
     }
 
-    private void updateContext(String caFile) {
-        try {
-            ctx = GrpcSslContexts.forClient().trustManager(Paths.get(caFile).toFile()).build();
-        } catch (SSLException e) {
-            throw new IllegalArgumentException(e);
-        }
-    }
+    protected abstract void updateContext(String caFile);
 
-    private void updateContext(final String privateKeyFile, final String certChainFile) {
-        try {
-            ctx = GrpcSslContexts
-                .configure(SslContextBuilder
-                    .forServer(
-                        new FileInputStream(Paths.get(certChainFile).toFile()),
-                        PrivateKeyUtil.loadDecryptionKey(privateKeyFile)),
-                    SslProvider.OPENSSL)
-                .build();
-        } catch (GeneralSecurityException | IOException e) {
-            throw new IllegalArgumentException(e);
-        }
-    }
+    protected abstract void updateContext(final String privateKeyFile, final String certChainFile);
 
     public void start() {
-       monitor.start();
+        monitor.start();
     }
 
     @Override
diff --git a/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/HttpDynamicSslContext.java b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/HttpDynamicSslContext.java
new file mode 100644
index 0000000..198865c
--- /dev/null
+++ b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/HttpDynamicSslContext.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.library.server.ssl;
+
+import io.netty.handler.ssl.SslContextBuilder;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import javax.net.ssl.SSLException;
+
+public class HttpDynamicSslContext extends AbstractSslContext {
+
+    public static HttpDynamicSslContext forServer(String privateKeyFile, String certChainFile) {
+        return new HttpDynamicSslContext(privateKeyFile, certChainFile);
+    }
+
+    public static HttpDynamicSslContext forClient(String caFile) {
+        return new HttpDynamicSslContext(caFile);
+    }
+
+    protected HttpDynamicSslContext(String privateKeyFile, String certChainFile) {
+        super(privateKeyFile, certChainFile);
+    }
+
+    protected HttpDynamicSslContext(String caFile) {
+        super(caFile);
+    }
+
+    protected void updateContext(String caFile) {
+        try {
+            setCtx(SslContextBuilder.forClient().trustManager(Paths.get(caFile).toFile()).build());
+        } catch (SSLException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    protected void updateContext(final String privateKeyFile, final String certChainFile) {
+        try {
+            setCtx(SslContextBuilder
+                .forServer(
+                    new FileInputStream(Paths.get(certChainFile).toFile()),
+                    PrivateKeyUtil.loadDecryptionKey(privateKeyFile)).build());
+        } catch (GeneralSecurityException | IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/PrivateKeyUtil.java b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/PrivateKeyUtil.java
similarity index 94%
rename from oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/PrivateKeyUtil.java
rename to oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/PrivateKeyUtil.java
index 86f08ca..8c866de 100644
--- a/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/ssl/PrivateKeyUtil.java
+++ b/oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/ssl/PrivateKeyUtil.java
@@ -16,7 +16,7 @@
  *
  */
 
-package org.apache.skywalking.oap.server.library.server.grpc.ssl;
+package org.apache.skywalking.oap.server.library.server.ssl;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -30,7 +30,7 @@ import java.util.Base64;
 /**
  * Util intends to parse PKCS#1 and PKCS#8 at same time.
  */
-class PrivateKeyUtil {
+public class PrivateKeyUtil {
     private static final String PKCS_1_PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
     private static final String PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE KEY-----";
     private static final String PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----";
@@ -39,7 +39,7 @@ class PrivateKeyUtil {
     /**
      * Load a RSA decryption key from a file (PEM or DER).
      */
-    static InputStream loadDecryptionKey(String keyFilePath) throws GeneralSecurityException, IOException {
+    public static InputStream loadDecryptionKey(String keyFilePath) throws GeneralSecurityException, IOException {
         byte[] keyDataBytes = Files.readAllBytes(Paths.get(keyFilePath));
         String keyDataString = new String(keyDataBytes, StandardCharsets.UTF_8);
 
diff --git a/oap-server/server-telemetry/telemetry-prometheus/pom.xml b/oap-server/server-telemetry/telemetry-prometheus/pom.xml
index da74857..eb096c1 100644
--- a/oap-server/server-telemetry/telemetry-prometheus/pom.xml
+++ b/oap-server/server-telemetry/telemetry-prometheus/pom.xml
@@ -31,6 +31,11 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.skywalking</groupId>
+            <artifactId>library-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
             <artifactId>telemetry-api</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -40,16 +45,15 @@
             <artifactId>simpleclient</artifactId>
             <version>0.6.0</version>
         </dependency>
-        <!-- Hotspot JVM metrics-->
         <dependency>
             <groupId>io.prometheus</groupId>
-            <artifactId>simpleclient_hotspot</artifactId>
+            <artifactId>simpleclient_common</artifactId>
             <version>0.6.0</version>
         </dependency>
-        <!-- Exposition HTTPServer-->
+        <!-- Hotspot JVM metrics-->
         <dependency>
             <groupId>io.prometheus</groupId>
-            <artifactId>simpleclient_httpserver</artifactId>
+            <artifactId>simpleclient_hotspot</artifactId>
             <version>0.6.0</version>
         </dependency>
     </dependencies>
diff --git a/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusConfig.java b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusConfig.java
index 08c8756..47809d3 100644
--- a/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusConfig.java
+++ b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusConfig.java
@@ -30,4 +30,7 @@ import org.apache.skywalking.oap.server.library.module.ModuleConfig;
 public class PrometheusConfig extends ModuleConfig {
     private String host = "0.0.0.0";
     private int port = 1234;
+    private boolean sslEnabled = false;
+    private String sslKeyPath;
+    private String sslCertChainPath;
 }
diff --git a/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusTelemetryProvider.java b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusTelemetryProvider.java
index 4713568..ca6fa06 100644
--- a/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusTelemetryProvider.java
+++ b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/PrometheusTelemetryProvider.java
@@ -18,9 +18,7 @@
 
 package org.apache.skywalking.oap.server.telemetry.prometheus;
 
-import io.prometheus.client.exporter.HTTPServer;
 import io.prometheus.client.hotspot.DefaultExports;
-import java.io.IOException;
 import org.apache.skywalking.oap.server.library.module.ModuleConfig;
 import org.apache.skywalking.oap.server.library.module.ModuleDefine;
 import org.apache.skywalking.oap.server.library.module.ModuleProvider;
@@ -29,6 +27,7 @@ import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedExcepti
 import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
 import org.apache.skywalking.oap.server.telemetry.api.MetricsCollector;
 import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
+import org.apache.skywalking.oap.server.telemetry.prometheus.httpserver.HttpServer;
 
 /**
  * Start the Prometheus
@@ -60,8 +59,8 @@ public class PrometheusTelemetryProvider extends ModuleProvider {
         this.registerServiceImplementation(MetricsCreator.class, new PrometheusMetricsCreator());
         this.registerServiceImplementation(MetricsCollector.class, new PrometheusMetricsCollector());
         try {
-            new HTTPServer(config.getHost(), config.getPort());
-        } catch (IOException e) {
+            new HttpServer(config).start();
+        } catch (InterruptedException e) {
             throw new ModuleStartException(e.getMessage(), e);
         }
 
diff --git a/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServer.java b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServer.java
new file mode 100644
index 0000000..4dc180a
--- /dev/null
+++ b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServer.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.telemetry.prometheus.httpserver;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import java.util.Optional;
+import java.util.concurrent.ThreadFactory;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.library.server.ssl.HttpDynamicSslContext;
+import org.apache.skywalking.oap.server.telemetry.prometheus.PrometheusConfig;
+
+/**
+ * An HTTP server that sends back the content of the received HTTP request
+ * in a pretty plaintext form.
+ */
+@RequiredArgsConstructor
+@Slf4j
+public final class HttpServer {
+
+    private final PrometheusConfig config;
+
+    public void start() throws InterruptedException {
+        // Configure SSL.
+        final HttpDynamicSslContext sslCtx;
+        if (config.isSslEnabled()) {
+            sslCtx = HttpDynamicSslContext.forServer(config.getSslKeyPath(), config.getSslCertChainPath());
+        } else {
+            sslCtx = null;
+        }
+
+        // Configure the server.
+        ThreadFactory tf = new ThreadFactoryBuilder().setDaemon(true).build();
+        EventLoopGroup bossGroup = new NioEventLoopGroup(1, tf);
+        EventLoopGroup workerGroup = new NioEventLoopGroup(0, tf);
+        ServerBootstrap b = new ServerBootstrap();
+        b.group(bossGroup, workerGroup)
+            .channel(NioServerSocketChannel.class)
+            .handler(new LoggingHandler(LogLevel.INFO))
+            .childHandler(new HttpServerInitializer(sslCtx));
+
+        b.bind(config.getHost(), config.getPort()).sync();
+        Optional.ofNullable(sslCtx).ifPresent(HttpDynamicSslContext::start);
+
+        log.info("Prometheus exporter endpoint:" +
+            (config.isSslEnabled() ? "https" : "http") + "://" + config.getHost() + ":" + config.getPort() + '/');
+    }
+}
diff --git a/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServerHandler.java b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServerHandler.java
new file mode 100644
index 0000000..bed33c2
--- /dev/null
+++ b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServerHandler.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.telemetry.prometheus.httpserver;
+
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpObject;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.util.CharsetUtil;
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.exporter.common.TextFormat;
+import java.io.IOException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.core.util.StringBuilderWriter;
+
+import static io.netty.channel.ChannelFutureListener.CLOSE;
+import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
+import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
+import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
+import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
+import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
+import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
+import static io.netty.handler.codec.http.HttpResponseStatus.OK;
+
+@Slf4j
+public class HttpServerHandler  extends SimpleChannelInboundHandler<HttpObject> {
+
+    private final CollectorRegistry registry = CollectorRegistry.defaultRegistry;
+    private final StringBuilderWriter buf = new StringBuilderWriter();
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        ctx.flush();
+    }
+
+    @Override
+    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
+        if (msg instanceof HttpRequest) {
+            HttpRequest req = (HttpRequest) msg;
+
+            boolean keepAlive = HttpUtil.isKeepAlive(req);
+
+            buf.getBuilder().setLength(0);
+            try {
+                TextFormat.write004(buf, registry.metricFamilySamples());
+            } catch (IOException e) {
+                ctx.fireExceptionCaught(e);
+                return;
+            }
+            FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK,
+                Unpooled.copiedBuffer(buf.getBuilder().toString(), CharsetUtil.UTF_8));
+            response.headers()
+                .set(CONTENT_TYPE, TEXT_PLAIN)
+                .setInt(CONTENT_LENGTH, response.content().readableBytes());
+
+            if (keepAlive) {
+                if (!req.protocolVersion().isKeepAliveDefault()) {
+                    response.headers().set(CONNECTION, KEEP_ALIVE);
+                }
+            } else {
+                // Tell the client we're going to close the connection.
+                response.headers().set(CONNECTION, CLOSE);
+            }
+
+            ChannelFuture f = ctx.write(response);
+
+            if (!keepAlive) {
+                f.addListener(CLOSE);
+            }
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        log.error("Prometheus exporter error", cause);
+        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
+            INTERNAL_SERVER_ERROR,
+            Unpooled.wrappedBuffer(cause.getMessage().getBytes()));
+        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+        ctx.close();
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServerInitializer.java b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServerInitializer.java
new file mode 100644
index 0000000..dc9f675
--- /dev/null
+++ b/oap-server/server-telemetry/telemetry-prometheus/src/main/java/org/apache/skywalking/oap/server/telemetry/prometheus/httpserver/HttpServerInitializer.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.telemetry.prometheus.httpserver;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpServerExpectContinueHandler;
+import io.netty.handler.ssl.SslContext;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
+
+    private final SslContext sslCtx;
+
+    @Override
+    public void initChannel(SocketChannel ch) {
+        ChannelPipeline p = ch.pipeline();
+        if (sslCtx != null) {
+            p.addLast(sslCtx.newHandler(ch.alloc()));
+        }
+        p.addLast(new HttpServerCodec());
+        p.addLast(new HttpServerExpectContinueHandler());
+        p.addLast(new HttpServerHandler());
+    }
+}