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

[skywalking] branch master updated: Support HTTP api for browser recevier (#5429)

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

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 9143f13  Support HTTP api for browser recevier (#5429)
9143f13 is described below

commit 9143f1342d5cbdab1876d39006e7d8d5743d56a6
Author: zhang-wei <pk...@outlook.com>
AuthorDate: Thu Sep 3 17:12:07 2020 +0800

    Support HTTP api for browser recevier (#5429)
    
    * support http api
---
 docs/en/protocols/Browser-HTTP-API-Protocol.md     | 108 +++++++++++
 docs/en/protocols/Browser-Protocol.md              |   3 +-
 .../browser/provider/BrowserModuleProvider.java    |  22 ++-
 .../{ => grpc}/BrowserPerfServiceHandler.java      |   2 +-
 .../BrowserErrorLogReportBaseServletHandler.java   |  96 ++++++++++
 .../BrowserErrorLogReportListServletHandler.java   |  70 ++++++++
 .../BrowserErrorLogReportSingleServletHandler.java |  56 ++++++
 .../rest/BrowserPerfDataReportServletHandler.java  | 112 ++++++++++++
 .../rest/BrowserReportServletHandlerTest.java      | 197 +++++++++++++++++++++
 9 files changed, 662 insertions(+), 4 deletions(-)

diff --git a/docs/en/protocols/Browser-HTTP-API-Protocol.md b/docs/en/protocols/Browser-HTTP-API-Protocol.md
new file mode 100644
index 0000000..25d2e16
--- /dev/null
+++ b/docs/en/protocols/Browser-HTTP-API-Protocol.md
@@ -0,0 +1,108 @@
+# HTTP API Protocol
+
+HTTP API Protocol defines the API data format, including api request and response data format.
+They use the HTTP1.1 wrapper of the official [SkyWalking Browser Protocol](Browser-Protocol.md). Read it for more details.
+
+## Performance Data Report
+
+Detail information about data format can be found in [BrowserPerf.proto](https://github.com/apache/skywalking-data-collect-protocol/blob/master/browser/BrowserPerf.proto).
+
+### POST http://localhost:12800/browser/perfData
+
+Send a performance data object with JSON format.
+
+Input:
+
+```json
+{
+  "service": "web",
+  "serviceVersion": "v0.0.1",
+  "pagePath": "/index.html",
+  "redirectTime": 10,
+  "dnsTime": 10,
+  "ttfbTime": 10,
+  "tcpTime": 10,
+  "transTime": 10,
+  "domAnalysisTime": 10,
+  "fptTime": 10,
+  "domReadyTime": 10,
+  "loadPageTime": 10,
+  "resTime": 10,
+  "sslTime": 10,
+  "ttlTime": 10,
+  "firstPackTime": 10,
+  "fmpTime": 10
+}
+```
+
+OutPut:
+
+```json
+
+```
+
+## Error Log Report
+
+Detail information about data format can be found in [BrowserPerf.proto](https://github.com/apache/skywalking-data-collect-protocol/blob/master/Browser/BrowserPerf.proto).
+
+### POST http://localhost:12800/browser/errorLogs
+
+Send an error log object list with JSON format.
+
+Input:
+
+```json
+[
+    {
+        "uniqueId": "55ec6178-3fb7-43ef-899c-a26944407b01",
+        "service": "web",
+        "serviceVersion": "v0.0.1",
+        "pagePath": "/index.html",
+        "category": "ajax",
+        "message": "error",
+        "line": 1,
+        "col": 1,
+        "stack": "error",
+        "errorUrl": "/index.html"
+    },
+    {
+        "uniqueId": "55ec6178-3fb7-43ef-899c-a26944407b02",
+        "service": "web",
+        "serviceVersion": "v0.0.1",
+        "pagePath": "/index.html",
+        "category": "ajax",
+        "message": "error",
+        "line": 1,
+        "col": 1,
+        "stack": "error",
+        "errorUrl": "/index.html"
+    }
+]
+```
+
+### POST http://localhost:12800/browser/errorLog
+
+Send a single error log object with JSON format.
+
+Input:
+
+```json
+{
+  "uniqueId": "55ec6178-3fb7-43ef-899c-a26944407b01",
+  "service": "web",
+  "serviceVersion": "v0.0.1",
+  "pagePath": "/index.html",
+  "category": "ajax",    
+  "message": "error",
+  "line": 1,
+  "col": 1,
+  "stack": "error",
+  "errorUrl": "/index.html"
+}
+```
+
+OutPut:
+
+```json
+
+```
diff --git a/docs/en/protocols/Browser-Protocol.md b/docs/en/protocols/Browser-Protocol.md
index 679ecaf..a88a77a 100644
--- a/docs/en/protocols/Browser-Protocol.md
+++ b/docs/en/protocols/Browser-Protocol.md
@@ -4,7 +4,8 @@ Browser protocol describes the data format between [skywalking-client-js](https:
 
 ## Overview
 
-Browser protocol is defined and provided in [gRPC format](https://github.com/apache/skywalking-data-collect-protocol/blob/master/browser/BrowserPerf.proto).
+Browser protocol is defined and provided in [gRPC format](https://github.com/apache/skywalking-data-collect-protocol/blob/master/browser/BrowserPerf.proto),
+also implemented in [HTTP 1.1](Browser-HTTP-API-Protocol.md)
 
 ### Send performance data and error log
 
diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java
index bc58caf..aa13728 100644
--- a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java
+++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java
@@ -21,13 +21,17 @@ import org.apache.skywalking.oap.server.configuration.api.ConfigurationModule;
 import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.oal.rt.OALEngineLoaderService;
 import org.apache.skywalking.oap.server.core.server.GRPCHandlerRegister;
+import org.apache.skywalking.oap.server.core.server.JettyHandlerRegister;
 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;
 import org.apache.skywalking.oap.server.library.module.ModuleStartException;
 import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
 import org.apache.skywalking.oap.server.receiver.browser.module.BrowserModule;
-import org.apache.skywalking.oap.server.receiver.browser.provider.handler.BrowserPerfServiceHandler;
+import org.apache.skywalking.oap.server.receiver.browser.provider.handler.grpc.BrowserPerfServiceHandler;
+import org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest.BrowserErrorLogReportListServletHandler;
+import org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest.BrowserErrorLogReportSingleServletHandler;
+import org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest.BrowserPerfDataReportServletHandler;
 import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager;
 import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.listener.ErrorLogRecordListener;
 import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.listener.MultiScopesErrorLogAnalysisListener;
@@ -69,10 +73,24 @@ public class BrowserModuleProvider extends ModuleProvider {
 
         GRPCHandlerRegister grpcHandlerRegister = getManager().find(SharingServerModule.NAME)
                                                               .provider().getService(GRPCHandlerRegister.class);
-
+        // grpc
         grpcHandlerRegister.addHandler(
             new BrowserPerfServiceHandler(
                 getManager(), moduleConfig, perfDataListenerManager(), errorLogListenerManager()));
+
+        // rest
+        JettyHandlerRegister jettyHandlerRegister = getManager().find(SharingServerModule.NAME)
+                                                                .provider()
+                                                                .getService(JettyHandlerRegister.class);
+        // performance
+        jettyHandlerRegister.addHandler(
+            new BrowserPerfDataReportServletHandler(getManager(), moduleConfig, perfDataListenerManager()));
+        // error log
+        ErrorLogParserListenerManager errorLogParserListenerManager = errorLogListenerManager();
+        jettyHandlerRegister.addHandler(
+            new BrowserErrorLogReportSingleServletHandler(getManager(), moduleConfig, errorLogParserListenerManager));
+        jettyHandlerRegister.addHandler(
+            new BrowserErrorLogReportListServletHandler(getManager(), moduleConfig, errorLogParserListenerManager));
     }
 
     @Override
diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/BrowserPerfServiceHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/grpc/BrowserPerfServiceHandler.java
similarity index 99%
rename from oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/BrowserPerfServiceHandler.java
rename to oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/grpc/BrowserPerfServiceHandler.java
index 3b1f633..7be6438 100644
--- a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/BrowserPerfServiceHandler.java
+++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/grpc/BrowserPerfServiceHandler.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.skywalking.oap.server.receiver.browser.provider.handler;
+package org.apache.skywalking.oap.server.receiver.browser.provider.handler.grpc;
 
 import io.grpc.stub.StreamObserver;
 import lombok.extern.slf4j.Slf4j;
diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportBaseServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportBaseServletHandler.java
new file mode 100644
index 0000000..ccf7c80
--- /dev/null
+++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportBaseServletHandler.java
@@ -0,0 +1,96 @@
+/*
+ * 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.receiver.browser.provider.handler.rest;
+
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.server.jetty.JettyHandler;
+import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig;
+import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogAnalyzer;
+import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager;
+import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
+import org.apache.skywalking.oap.server.telemetry.api.CounterMetrics;
+import org.apache.skywalking.oap.server.telemetry.api.HistogramMetrics;
+import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
+import org.apache.skywalking.oap.server.telemetry.api.MetricsTag;
+
+@Slf4j
+public abstract class BrowserErrorLogReportBaseServletHandler extends JettyHandler {
+    private final ModuleManager moduleManager;
+    private final BrowserServiceModuleConfig config;
+    private final ErrorLogParserListenerManager errorLogListenerManager;
+
+    private final HistogramMetrics errorLogHistogram;
+    private final CounterMetrics logErrorCounter;
+
+    public BrowserErrorLogReportBaseServletHandler(ModuleManager moduleManager,
+                                                   BrowserServiceModuleConfig config,
+                                                   ErrorLogParserListenerManager errorLogListenerManager) {
+        this.moduleManager = moduleManager;
+        this.config = config;
+        this.errorLogListenerManager = errorLogListenerManager;
+
+        MetricsCreator metricsCreator = moduleManager.find(TelemetryModule.NAME)
+                                                     .provider()
+                                                     .getService(MetricsCreator.class);
+
+        errorLogHistogram = metricsCreator.createHistogramMetric(
+            "browser_error_log_in_latency", "The process latency of browser error log", new MetricsTag.Keys("protocol"),
+            new MetricsTag.Values("grpc")
+        );
+        logErrorCounter = metricsCreator.createCounter(
+            "browser_error_log_analysis_error_count", "The error number of browser error log analysis",
+            new MetricsTag.Keys("protocol"), new MetricsTag.Values("http")
+        );
+    }
+
+    @Override
+    protected void doGet(final HttpServletRequest req,
+                         final HttpServletResponse resp) throws ServletException, IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void doPost(final HttpServletRequest req,
+                          final HttpServletResponse resp) throws ServletException, IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("receive browser error log");
+        }
+
+        HistogramMetrics.Timer timer = errorLogHistogram.createTimer();
+        try {
+            for (BrowserErrorLog browserErrorLog : parseBrowserErrorLog(req)) {
+                ErrorLogAnalyzer analyzer = new ErrorLogAnalyzer(moduleManager, errorLogListenerManager, config);
+                analyzer.doAnalysis(browserErrorLog);
+            }
+        } catch (Throwable e) {
+            log.error(e.getMessage(), e);
+            logErrorCounter.inc();
+        } finally {
+            timer.finish();
+        }
+    }
+
+    protected abstract List<BrowserErrorLog> parseBrowserErrorLog(HttpServletRequest request) throws IOException;
+}
diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportListServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportListServletHandler.java
new file mode 100644
index 0000000..b1aae85
--- /dev/null
+++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportListServletHandler.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.receiver.browser.provider.handler.rest;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils;
+import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig;
+import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager;
+
+public class BrowserErrorLogReportListServletHandler extends BrowserErrorLogReportBaseServletHandler {
+    private final Gson gson = new Gson();
+
+    public BrowserErrorLogReportListServletHandler(final ModuleManager moduleManager,
+                                                   final BrowserServiceModuleConfig config,
+                                                   final ErrorLogParserListenerManager errorLogListenerManager) {
+        super(moduleManager, config, errorLogListenerManager);
+    }
+
+    @Override
+    protected List<BrowserErrorLog> parseBrowserErrorLog(final HttpServletRequest request) throws IOException {
+        BufferedReader reader = request.getReader();
+        String line;
+        StringBuilder stringBuilder = new StringBuilder();
+        while ((line = reader.readLine()) != null) {
+            stringBuilder.append(line);
+        }
+        final JsonArray array = gson.fromJson(stringBuilder.toString(), JsonArray.class);
+        if (array.size() == 0) {
+            return Collections.emptyList();
+        }
+
+        final ArrayList<BrowserErrorLog> errorLogs = new ArrayList<>(array.size());
+        for (JsonElement element : array) {
+            BrowserErrorLog.Builder builder = BrowserErrorLog.newBuilder();
+            ProtoBufJsonUtils.fromJSON(element.toString(), builder);
+            errorLogs.add(builder.build());
+        }
+        return errorLogs;
+    }
+
+    @Override
+    public String pathSpec() {
+        return "/browser/errorLogs";
+    }
+}
diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportSingleServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportSingleServletHandler.java
new file mode 100644
index 0000000..1054486
--- /dev/null
+++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportSingleServletHandler.java
@@ -0,0 +1,56 @@
+/*
+ * 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.receiver.browser.provider.handler.rest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils;
+import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig;
+import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager;
+
+public class BrowserErrorLogReportSingleServletHandler extends BrowserErrorLogReportBaseServletHandler {
+    public BrowserErrorLogReportSingleServletHandler(final ModuleManager moduleManager,
+                                                     final BrowserServiceModuleConfig config,
+                                                     final ErrorLogParserListenerManager errorLogListenerManager) {
+        super(moduleManager, config, errorLogListenerManager);
+    }
+
+    @Override
+    protected List<BrowserErrorLog> parseBrowserErrorLog(final HttpServletRequest request) throws IOException {
+        BufferedReader reader = request.getReader();
+        String line;
+        StringBuilder stringBuilder = new StringBuilder();
+        while ((line = reader.readLine()) != null) {
+            stringBuilder.append(line);
+        }
+
+        BrowserErrorLog.Builder builder = BrowserErrorLog.newBuilder();
+        ProtoBufJsonUtils.fromJSON(stringBuilder.toString(), builder);
+        return Collections.singletonList(builder.build());
+    }
+
+    @Override
+    public String pathSpec() {
+        return "/browser/errorLog";
+    }
+}
diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserPerfDataReportServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserPerfDataReportServletHandler.java
new file mode 100644
index 0000000..e0bec01
--- /dev/null
+++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserPerfDataReportServletHandler.java
@@ -0,0 +1,112 @@
+/*
+ * 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.receiver.browser.provider.handler.rest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.apm.network.language.agent.v3.BrowserPerfData;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.server.jetty.JettyHandler;
+import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils;
+import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig;
+import org.apache.skywalking.oap.server.receiver.browser.provider.parser.performance.PerfDataAnalyzer;
+import org.apache.skywalking.oap.server.receiver.browser.provider.parser.performance.PerfDataParserListenerManager;
+import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
+import org.apache.skywalking.oap.server.telemetry.api.CounterMetrics;
+import org.apache.skywalking.oap.server.telemetry.api.HistogramMetrics;
+import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
+import org.apache.skywalking.oap.server.telemetry.api.MetricsTag;
+
+@Slf4j
+public class BrowserPerfDataReportServletHandler extends JettyHandler {
+    private final ModuleManager moduleManager;
+    private final BrowserServiceModuleConfig config;
+    private final PerfDataParserListenerManager perfDataListenerManager;
+
+    private final HistogramMetrics perfHistogram;
+    private final CounterMetrics perfErrorCounter;
+
+    public BrowserPerfDataReportServletHandler(ModuleManager moduleManager,
+                                               BrowserServiceModuleConfig config,
+                                               PerfDataParserListenerManager perfDataListenerManager) {
+        this.moduleManager = moduleManager;
+        this.config = config;
+        this.perfDataListenerManager = perfDataListenerManager;
+
+        MetricsCreator metricsCreator = moduleManager.find(TelemetryModule.NAME)
+                                                     .provider()
+                                                     .getService(MetricsCreator.class);
+
+        perfHistogram = metricsCreator.createHistogramMetric(
+            "browser_perf_data_in_latency", "The process latency of browser performance data",
+            new MetricsTag.Keys("protocol"), new MetricsTag.Values("http")
+        );
+        perfErrorCounter = metricsCreator.createCounter(
+            "browser_perf_data_analysis_error_count", "The error number of browser performance data analysis",
+            new MetricsTag.Keys("protocol"), new MetricsTag.Values("http")
+        );
+    }
+
+    @Override
+    protected void doGet(final HttpServletRequest req,
+                         final HttpServletResponse resp) throws ServletException, IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void doPost(final HttpServletRequest req,
+                          final HttpServletResponse resp) throws ServletException, IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("receive browser performance data");
+        }
+
+        HistogramMetrics.Timer timer = perfHistogram.createTimer();
+        try {
+            BrowserPerfData browserPerfData = parseBrowserPerfData(req);
+            PerfDataAnalyzer analyzer = new PerfDataAnalyzer(moduleManager, perfDataListenerManager, config);
+            analyzer.doAnalysis(browserPerfData);
+        } catch (Throwable e) {
+            log.error(e.getMessage(), e);
+            perfErrorCounter.inc();
+        } finally {
+            timer.finish();
+        }
+    }
+
+    protected BrowserPerfData parseBrowserPerfData(HttpServletRequest request) throws IOException {
+        BufferedReader reader = request.getReader();
+        String line;
+        StringBuilder stringBuilder = new StringBuilder();
+        while ((line = reader.readLine()) != null) {
+            stringBuilder.append(line);
+        }
+
+        BrowserPerfData.Builder builder = BrowserPerfData.newBuilder();
+        ProtoBufJsonUtils.fromJSON(stringBuilder.toString(), builder);
+        return builder.build();
+    }
+
+    @Override
+    public String pathSpec() {
+        return "/browser/perfData";
+    }
+}
diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserReportServletHandlerTest.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserReportServletHandlerTest.java
new file mode 100644
index 0000000..b3dd333
--- /dev/null
+++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserReportServletHandlerTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.receiver.browser.provider.handler.rest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog;
+import org.apache.skywalking.apm.network.language.agent.v3.BrowserPerfData;
+import org.apache.skywalking.apm.network.language.agent.v3.ErrorCategory;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
+import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
+import org.apache.skywalking.oap.server.telemetry.none.MetricsCreatorNoop;
+import org.apache.skywalking.oap.server.telemetry.none.NoneTelemetryProvider;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.Whitebox;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.mockito.Mockito.when;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockIgnore({"javax.management.*"})
+public class BrowserReportServletHandlerTest {
+    @Mock
+    private HttpServletRequest request;
+    @Mock
+    private ModuleManager moduleManager;
+    @Mock
+    private NoneTelemetryProvider telemetryProvider;
+
+    @Before
+    public void init() {
+        Mockito.when(telemetryProvider.getService(MetricsCreator.class))
+               .thenReturn(new MetricsCreatorNoop());
+
+        TelemetryModule telemetryModule = Mockito.spy(TelemetryModule.class);
+        Whitebox.setInternalState(telemetryModule, "loadedProvider", telemetryProvider);
+        Mockito.when(moduleManager.find(TelemetryModule.NAME)).thenReturn(telemetryModule);
+    }
+
+    @Test
+    public void testPerfData() throws IOException {
+        final String json = "{\n" +
+            "  \"service\": \"test\",\n" +
+            "  \"serviceVersion\": \"v0.0.1\",\n" +
+            "  \"pagePath\": \"/e2e-browser\",\n" +
+            "  \"redirectTime\": 1,\n" +
+            "  \"dnsTime\": 2,\n" +
+            "  \"ttfbTime\": 3,\n" +
+            "  \"tcpTime\": 4,\n" +
+            "  \"transTime\": 5,\n" +
+            "  \"domAnalysisTime\": 6,\n" +
+            "  \"fptTime\": 7,\n" +
+            "  \"domReadyTime\": 8,\n" +
+            "  \"loadPageTime\": 9,\n" +
+            "  \"resTime\": 10,\n" +
+            "  \"sslTime\": 11,\n" +
+            "  \"ttlTime\": 12,\n" +
+            "  \"firstPackTime\": 13,\n" +
+            "  \"fmpTime\": 14\n" +
+            "}";
+        final BrowserPerfDataReportServletHandler reportServletHandler = new BrowserPerfDataReportServletHandler(
+            moduleManager, null, null);
+
+        when(request.getReader()).thenReturn(
+            new BufferedReader(new StringReader(json)));
+        final BrowserPerfData result = reportServletHandler.parseBrowserPerfData(request);
+        Assert.assertEquals("test", result.getService());
+        Assert.assertEquals("v0.0.1", result.getServiceVersion());
+        Assert.assertEquals("/e2e-browser", result.getPagePath());
+        Assert.assertEquals(1, result.getRedirectTime());
+        Assert.assertEquals(2, result.getDnsTime());
+        Assert.assertEquals(3, result.getTtfbTime());
+        Assert.assertEquals(4, result.getTcpTime());
+        Assert.assertEquals(5, result.getTransTime());
+        Assert.assertEquals(6, result.getDomAnalysisTime());
+        Assert.assertEquals(7, result.getFptTime());
+        Assert.assertEquals(8, result.getDomReadyTime());
+        Assert.assertEquals(9, result.getLoadPageTime());
+        Assert.assertEquals(10, result.getResTime());
+        Assert.assertEquals(11, result.getSslTime());
+        Assert.assertEquals(12, result.getTtlTime());
+        Assert.assertEquals(13, result.getFirstPackTime());
+        Assert.assertEquals(14, result.getFmpTime());
+    }
+
+    @Test
+    public void testErrorLogSingle() throws IOException {
+        final String singleJson = "{\n" +
+            "  \"uniqueId\": \"55ec6178-3fb7-43ef-899c-a26944407b0e\",\n" +
+            "  \"service\": \"test\",\n" +
+            "  \"serviceVersion\": \"v0.0.1\",\n" +
+            "  \"pagePath\": \"/e2e-browser\",\n" +
+            "  \"category\": \"ajax\",\n" +
+            "  \"message\": \"test\",\n" +
+            "  \"line\": 1,\n" +
+            "  \"col\": 1,\n" +
+            "  \"stack\": \"e2e\",\n" +
+            "  \"errorUrl\": \"/e2e-browser\"\n" +
+            "}";
+
+        final BrowserErrorLogReportSingleServletHandler singleServletHandler = new BrowserErrorLogReportSingleServletHandler(
+            moduleManager, null, null);
+
+        when(request.getReader()).thenReturn(
+            new BufferedReader(new StringReader(singleJson)));
+        final List<BrowserErrorLog> browserErrorLogs = singleServletHandler.parseBrowserErrorLog(request);
+        Assert.assertEquals(1, browserErrorLogs.size());
+        BrowserErrorLog errorLog = browserErrorLogs.get(0);
+        Assert.assertEquals("55ec6178-3fb7-43ef-899c-a26944407b0e", errorLog.getUniqueId());
+        Assert.assertEquals("test", errorLog.getService());
+        Assert.assertEquals("v0.0.1", errorLog.getServiceVersion());
+        Assert.assertEquals("/e2e-browser", errorLog.getPagePath());
+        Assert.assertEquals(ErrorCategory.ajax, errorLog.getCategory());
+        Assert.assertEquals("test", errorLog.getMessage());
+        Assert.assertEquals(1, errorLog.getLine());
+        Assert.assertEquals(1, errorLog.getCol());
+        Assert.assertEquals("e2e", errorLog.getStack());
+        Assert.assertEquals("/e2e-browser", errorLog.getErrorUrl());
+    }
+
+    @Test
+    public void testErrorLogList() throws IOException {
+        final String listJson = "[\n" +
+            "    {\n" +
+            "        \"uniqueId\": \"55ec6178-3fb7-43ef-899c-a26944407b01\",\n" +
+            "        \"service\": \"test\",\n" +
+            "        \"serviceVersion\": \"v0.0.1\",\n" +
+            "        \"pagePath\": \"/e2e-browser\",\n" +
+            "        \"category\": \"ajax\",\n" +
+            "        \"message\": \"test\",\n" +
+            "        \"line\": 1,\n" +
+            "        \"col\": 1,\n" +
+            "        \"stack\": \"e2e\",\n" +
+            "        \"errorUrl\": \"/e2e-browser\"\n" +
+            "    },\n" +
+            "    {\n" +
+            "        \"uniqueId\": \"55ec6178-3fb7-43ef-899c-a26944407b02\",\n" +
+            "        \"service\": \"test\",\n" +
+            "        \"serviceVersion\": \"v0.0.1\",\n" +
+            "        \"pagePath\": \"/e2e-browser\",\n" +
+            "        \"category\": \"ajax\",\n" +
+            "        \"message\": \"test\",\n" +
+            "        \"line\": 1,\n" +
+            "        \"col\": 1,\n" +
+            "        \"stack\": \"e2e\",\n" +
+            "        \"errorUrl\": \"/e2e-browser\"\n" +
+            "    }\n" +
+            "]";
+
+        final BrowserErrorLogReportListServletHandler listServletHandler = new BrowserErrorLogReportListServletHandler(
+            moduleManager, null, null);
+
+        when(request.getReader()).thenReturn(
+            new BufferedReader(new StringReader(listJson)));
+
+        final List<BrowserErrorLog> browserErrorLogs = listServletHandler.parseBrowserErrorLog(request);
+        Assert.assertEquals(2, browserErrorLogs.size());
+        BrowserErrorLog errorLog1 = browserErrorLogs.get(0);
+        Assert.assertEquals("55ec6178-3fb7-43ef-899c-a26944407b01", errorLog1.getUniqueId());
+        Assert.assertEquals("test", errorLog1.getService());
+        Assert.assertEquals("v0.0.1", errorLog1.getServiceVersion());
+        Assert.assertEquals("/e2e-browser", errorLog1.getPagePath());
+        Assert.assertEquals(ErrorCategory.ajax, errorLog1.getCategory());
+        Assert.assertEquals("test", errorLog1.getMessage());
+        Assert.assertEquals(1, errorLog1.getLine());
+        Assert.assertEquals(1, errorLog1.getCol());
+        Assert.assertEquals("e2e", errorLog1.getStack());
+        Assert.assertEquals("/e2e-browser", errorLog1.getErrorUrl());
+        BrowserErrorLog errorLog2 = browserErrorLogs.get(1);
+        Assert.assertEquals("55ec6178-3fb7-43ef-899c-a26944407b02", errorLog2.getUniqueId());
+    }
+}