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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5f04fcd  Add werkzeug support to sw_http_server plugin  (#20)
5f04fcd is described below

commit 5f04fcd3101522c1d88f9d27882fe7cb41b780c1
Author: Lei <leix....@qq.com>
AuthorDate: Mon Jun 22 23:26:05 2020 +0800

    Add werkzeug support to sw_http_server plugin  (#20)
---
 Makefile                                           |  6 +-
 setup.py                                           |  1 +
 skywalking/plugins/sw_http_server/__init__.py      | 79 +++++++++++++-------
 tests/plugin/docker/Dockerfile.agent               |  2 +-
 .../Dockerfile.agent => sw_http_wsgi/__init__.py}  | 11 +--
 tests/plugin/sw_http_wsgi/docker-compose.yml       | 78 ++++++++++++++++++++
 tests/plugin/sw_http_wsgi/expected.data.yml        | 84 ++++++++++++++++++++++
 .../services/__init__.py}                          | 12 +---
 tests/plugin/sw_http_wsgi/services/consumer.py     | 48 +++++++++++++
 .../services/provider.py}                          | 25 +++++--
 .../test_http_wsgi.py}                             | 32 +++++++--
 11 files changed, 322 insertions(+), 56 deletions(-)

diff --git a/Makefile b/Makefile
index 3714710..92f13c8 100644
--- a/Makefile
+++ b/Makefile
@@ -20,6 +20,9 @@ setup:
        python3 -m pip install --upgrade pip
        python3 -m pip install grpcio --ignore-installed
 
+setup-test: setup
+       pip3 install -e .[test]
+
 gen:
        python3 -m grpc_tools.protoc --version || python3 -m pip install 
grpcio-tools
        python3 -m grpc_tools.protoc -I protocol --python_out=. 
--grpc_python_out=. protocol/**/*.proto
@@ -38,8 +41,7 @@ lint: clean
 license: clean
        python3 tools/check-license-header.py skywalking tests tools
 
-test: gen
-       pip3 install -e .[test]
+test: gen setup-test
        python3 -m unittest  -v
 
 install: gen
diff --git a/setup.py b/setup.py
index effb262..720c5b0 100644
--- a/setup.py
+++ b/setup.py
@@ -42,6 +42,7 @@ setup(
     extras_require={
         "test": [
             "testcontainers",
+            "Werkzeug"
         ],
     },
     classifiers=[
diff --git a/skywalking/plugins/sw_http_server/__init__.py 
b/skywalking/plugins/sw_http_server/__init__.py
index d790975..04b58ca 100644
--- a/skywalking/plugins/sw_http_server/__init__.py
+++ b/skywalking/plugins/sw_http_server/__init__.py
@@ -34,32 +34,63 @@ def install():
 
         _handle = BaseHTTPRequestHandler.handle
 
-        def _sw_handle(this: BaseHTTPRequestHandler):
-            http_methods = ('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 
'OPTIONS', 'TRACE', 'PATCH')
-            for method in http_methods:
-                _wrap_do_method(this, method)
-            _handle(this)
-
-        def _wrap_do_method(this, method):
-            if hasattr(this, 'do_' + method) and 
inspect.ismethod(getattr(this, 'do_' + method)):
-                _do_method = getattr(this, 'do_' + method)
-
-                def _sw_do_method():
-                    context = get_context()
-                    carrier = Carrier()
-                    for item in carrier:
-                        item.val = this.headers[item.key.capitalize()]
-                    with context.new_entry_span(op=this.path, carrier=carrier) 
as span:
-                        span.layer = Layer.Http
-                        span.component = Component.General
-                        span.peer = '%s:%s' % this.client_address
-                        span.tag(Tag(key=tags.HttpMethod, val=method))
-
-                        _do_method()
-
-                setattr(this, 'do_' + method, _sw_do_method)
+        def _sw_handle(handler: BaseHTTPRequestHandler):
+            clazz = handler.__class__
+            if 'werkzeug.serving.WSGIRequestHandler' == 
".".join([clazz.__module__, clazz.__name__]):
+                wrap_werkzeug_request_handler(handler)
+            else:
+                wrap_default_request_handler(handler)
+            _handle(handler)
 
         BaseHTTPRequestHandler.handle = _sw_handle
+
     except Exception:
         logger.warning('failed to install plugin %s', __name__)
         traceback.print_exc()
+
+
+def wrap_werkzeug_request_handler(handler):
+    """
+    Wrap run_wsgi of werkzeug.serving.WSGIRequestHandler to add skywalking 
instrument code.
+    """
+    _run_wsgi = handler.run_wsgi
+
+    def _wrap_run_wsgi():
+        context = get_context()
+        carrier = Carrier()
+        for item in carrier:
+            item.val = handler.headers[item.key.capitalize()]
+        with context.new_entry_span(op=handler.path, carrier=carrier) as span:
+            span.layer = Layer.Http
+            span.component = Component.General
+            span.peer = '%s:%s' % handler.client_address
+            span.tag(Tag(key=tags.HttpMethod, val=handler.command))
+            return _run_wsgi()
+
+    handler.run_wsgi = _wrap_run_wsgi
+
+
+def wrap_default_request_handler(handler):
+    http_methods = ('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 
'OPTIONS', 'TRACE', 'PATCH')
+    for method in http_methods:
+        _wrap_do_method(handler, method)
+
+
+def _wrap_do_method(handler, method):
+    if hasattr(handler, 'do_' + method) and inspect.ismethod(getattr(handler, 
'do_' + method)):
+        _do_method = getattr(handler, 'do_' + method)
+
+        def _sw_do_method():
+            context = get_context()
+            carrier = Carrier()
+            for item in carrier:
+                item.val = handler.headers[item.key.capitalize()]
+            with context.new_entry_span(op=handler.path, carrier=carrier) as 
span:
+                span.layer = Layer.Http
+                span.component = Component.General
+                span.peer = '%s:%s' % handler.client_address
+                span.tag(Tag(key=tags.HttpMethod, val=method))
+
+                _do_method()
+
+        setattr(handler, 'do_' + method, _sw_do_method)
diff --git a/tests/plugin/docker/Dockerfile.agent 
b/tests/plugin/docker/Dockerfile.agent
index 7672618..81ea394 100644
--- a/tests/plugin/docker/Dockerfile.agent
+++ b/tests/plugin/docker/Dockerfile.agent
@@ -21,4 +21,4 @@ WORKDIR /agent
 
 ADD $ROOT /agent
 
-RUN make setup install
+RUN make setup-test install
diff --git a/tests/plugin/docker/Dockerfile.agent 
b/tests/plugin/sw_http_wsgi/__init__.py
similarity index 90%
copy from tests/plugin/docker/Dockerfile.agent
copy to tests/plugin/sw_http_wsgi/__init__.py
index 7672618..6222972 100644
--- a/tests/plugin/docker/Dockerfile.agent
+++ b/tests/plugin/sw_http_wsgi/__init__.py
@@ -1,3 +1,4 @@
+#
 # 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.
@@ -12,13 +13,5 @@
 # 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.
+#
 
-FROM python:3.7
-
-ARG ROOT=.
-
-WORKDIR /agent
-
-ADD $ROOT /agent
-
-RUN make setup install
diff --git a/tests/plugin/sw_http_wsgi/docker-compose.yml 
b/tests/plugin/sw_http_wsgi/docker-compose.yml
new file mode 100644
index 0000000..3f0d305
--- /dev/null
+++ b/tests/plugin/sw_http_wsgi/docker-compose.yml
@@ -0,0 +1,78 @@
+#
+# 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.
+#
+
+version: '2.1'
+
+services:
+  collector:
+    build:
+      context: ../docker
+      dockerfile: Dockerfile.tool
+    ports:
+      - 19876:19876
+      - 12800:12800
+    networks:
+      - beyond
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/12800"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+
+  provider:
+    build:
+      context: ../../../
+      dockerfile: tests/plugin/docker/Dockerfile.agent
+    networks:
+      - beyond
+    ports:
+      - 9091:9091
+    volumes:
+      - ./services/provider.py:/app/provider.py
+    environment:
+      SW_AGENT_COLLECTOR_BACKEND_SERVICES: collector:19876
+    command: ['python3', '/app/provider.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+
+  consumer:
+    build:
+      context: ../../../
+      dockerfile: tests/plugin/docker/Dockerfile.agent
+    networks:
+      - beyond
+    ports:
+      - 9090:9090
+    volumes:
+      - ./services/consumer.py:/app/consumer.py
+    environment:
+      SW_AGENT_COLLECTOR_BACKEND_SERVICES: collector:19876
+    command: ['python3', '/app/consumer.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+      provider:
+        condition: service_healthy
+
+networks:
+  beyond:
diff --git a/tests/plugin/sw_http_wsgi/expected.data.yml 
b/tests/plugin/sw_http_wsgi/expected.data.yml
new file mode 100644
index 0000000..509127e
--- /dev/null
+++ b/tests/plugin/sw_http_wsgi/expected.data.yml
@@ -0,0 +1,84 @@
+#
+# 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.
+#
+
+segmentItems:
+  - serviceName: provider
+    segmentSize: 1
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /users
+            operationId: 0
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: Http
+            tags:
+              - key: http.method
+                value: POST
+            refs:
+              - parentEndpoint: /users
+                networkAddress: provider:9091
+                refType: CrossProcess
+                parentSpanId: 1
+                parentTraceSegmentId: not null
+                parentServiceInstance: not null
+                parentService: consumer
+                traceId: not null
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7000
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+  - serviceName: consumer
+    segmentSize: 1
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /users
+            operationId: 0
+            parentSpanId: 0
+            spanId: 1
+            spanLayer: Http
+            tags:
+              - key: http.method
+                value: POST
+              - key: url
+                value: http://provider:9091/users
+              - key: status.code
+                value: '200'
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7000
+            spanType: Exit
+            peer: provider:9091
+            skipAnalysis: false
+          - operationName: /
+            operationId: 0
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: Http
+            tags:
+              - key: http.method
+                value: POST
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7000
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+
diff --git a/tests/plugin/docker/Dockerfile.agent 
b/tests/plugin/sw_http_wsgi/services/__init__.py
similarity index 89%
copy from tests/plugin/docker/Dockerfile.agent
copy to tests/plugin/sw_http_wsgi/services/__init__.py
index 7672618..b1312a0 100644
--- a/tests/plugin/docker/Dockerfile.agent
+++ b/tests/plugin/sw_http_wsgi/services/__init__.py
@@ -1,3 +1,4 @@
+#
 # 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.
@@ -12,13 +13,4 @@
 # 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.
-
-FROM python:3.7
-
-ARG ROOT=.
-
-WORKDIR /agent
-
-ADD $ROOT /agent
-
-RUN make setup install
+#
diff --git a/tests/plugin/sw_http_wsgi/services/consumer.py 
b/tests/plugin/sw_http_wsgi/services/consumer.py
new file mode 100644
index 0000000..43f60bd
--- /dev/null
+++ b/tests/plugin/sw_http_wsgi/services/consumer.py
@@ -0,0 +1,48 @@
+#
+# 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.
+#
+
+from urllib import request
+
+from skywalking import agent, config
+
+if __name__ == '__main__':
+    config.service_name = 'consumer'
+    config.logging_level = 'DEBUG'
+    agent.start()
+
+    import socketserver
+    from http.server import BaseHTTPRequestHandler
+
+    class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
+        def do_POST(self):
+            self.send_response(200)
+            self.send_header('Content-Type', 'application/json; charset=utf-8')
+            self.end_headers()
+
+            data = '{"name": "whatever"}'.encode('utf8')
+            req = request.Request('http://provider:9091/users')
+            req.add_header('Content-Type', 'application/json; charset=utf-8')
+            req.add_header('Content-Length', str(len(data)))
+            with request.urlopen(req, data):
+                self.wfile.write(data)
+
+    PORT = 9090
+    Handler = SimpleHTTPRequestHandler
+
+    with socketserver.TCPServer(("", PORT), Handler) as httpd:
+        print("serving at port", PORT)
+        httpd.serve_forever()
diff --git a/tests/plugin/docker/Dockerfile.agent 
b/tests/plugin/sw_http_wsgi/services/provider.py
similarity index 62%
copy from tests/plugin/docker/Dockerfile.agent
copy to tests/plugin/sw_http_wsgi/services/provider.py
index 7672618..e15f33b 100644
--- a/tests/plugin/docker/Dockerfile.agent
+++ b/tests/plugin/sw_http_wsgi/services/provider.py
@@ -1,3 +1,4 @@
+#
 # 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.
@@ -12,13 +13,27 @@
 # 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.
+#
+
+import time
+
+from skywalking import agent, config
+
+if __name__ == '__main__':
+    config.service_name = 'provider'
+    config.logging_level = 'DEBUG'
+    agent.start()
+
+    from werkzeug import Request, Response
 
-FROM python:3.7
 
-ARG ROOT=.
+    @Request.application
+    def application(request):
+        time.sleep(0.5)
+        return Response('{"song": "Despacito", "artist": "Luis Fonsi"}')
 
-WORKDIR /agent
 
-ADD $ROOT /agent
+    from werkzeug.serving import run_simple
 
-RUN make setup install
+    PORT = 9091
+    run_simple("", PORT, application)
diff --git a/tests/plugin/docker/Dockerfile.agent 
b/tests/plugin/sw_http_wsgi/test_http_wsgi.py
similarity index 52%
copy from tests/plugin/docker/Dockerfile.agent
copy to tests/plugin/sw_http_wsgi/test_http_wsgi.py
index 7672618..eacaa98 100644
--- a/tests/plugin/docker/Dockerfile.agent
+++ b/tests/plugin/sw_http_wsgi/test_http_wsgi.py
@@ -1,3 +1,4 @@
+#
 # 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.
@@ -12,13 +13,34 @@
 # 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.
+#
+
+import os
+import time
+import unittest
+from os.path import abspath, dirname
+
+import requests
+from testcontainers.compose import DockerCompose
+
+from tests.plugin import BasePluginTest
+
+
+class TestRequestPlugin(BasePluginTest):
+    @classmethod
+    def setUpClass(cls):
+        cls.compose = DockerCompose(filepath=dirname(abspath(__file__)))
+        cls.compose.start()
+
+        cls.compose.wait_for(cls.url(cls.collector_address()))
 
-FROM python:3.7
+    def test_request_plugin(self):
+        print('traffic: ', requests.post(url=self.url(('consumer', '9090'))))
 
-ARG ROOT=.
+        time.sleep(3)
 
-WORKDIR /agent
+        
self.validate(expected_file_name=os.path.join(dirname(abspath(__file__)), 
'expected.data.yml'))
 
-ADD $ROOT /agent
 
-RUN make setup install
+if __name__ == '__main__':
+    unittest.main()

Reply via email to