We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
REST args in GitLab push notification do not work
This is fundamentally the same problem we had at #3506 (@rodrigc). In Python3, some strings are now byte strings and are not identified properly by Buildbot.

In this case, I have a local gitlab instance with the webhook for push events: `https://mybuildbot/change_hook/gitlab?project=lthmb`.

However, when I test this the Buildbot logs show no `project` parameter set:
```
2017-10-27 18:15:14+0200 [-] injected change Change(revision='2afc1ab3ab28980db47d64714852aa0d586be7f9', who='Paulo Matos <pmatos@linki.tools>', branch='master', comments="Merge branch 'foobar'\n", when=1506599131, category='push', project='', repository='git@linki:pmatos/lthmb.git', codebase='')
```
I went ahead and printed the request received from gitlab in `www/hooks/gitlab.py` and noticed that `request.args` in `getChanges` are byte strings: `2017-10-27 18:15:14+0200 [-] {b'project': [b'lthmb']}`

I think in Buildbot nine with Py3 we might need string conversion here.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/www/hooks/gitlab.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 from __future__ import absolute_import
17 from __future__ import print_function
18 
19 import json
20 import re
21 
22 from dateutil.parser import parse as dateparse
23 
24 from twisted.python import log
25 
26 from buildbot.util import bytes2unicode
27 from buildbot.www.hooks.base import BaseHookHandler
28 
29 _HEADER_EVENT = b'X-Gitlab-Event'
30 _HEADER_GITLAB_TOKEN = b'X-Gitlab-Token'
31 
32 
33 class GitLabHandler(BaseHookHandler):
34 
35     def _process_change(self, payload, user, repo, repo_url, project, event,
36                         codebase=None):
37         """
38         Consumes the JSON as a python object and actually starts the build.
39 
40         :arguments:
41             payload
42                 Python Object that represents the JSON sent by GitLab Service
43                 Hook.
44         """
45         changes = []
46         refname = payload['ref']
47 
48         # We only care about regular heads or tags
49         match = re.match(r"^refs/(heads|tags)/(.+)$", refname)
50         if not match:
51             log.msg("Ignoring refname `%s': Not a branch" % refname)
52             return changes
53 
54         branch = match.group(2)
55         if payload.get('deleted'):
56             log.msg("Branch `%s' deleted, ignoring" % branch)
57             return changes
58 
59         for commit in payload['commits']:
60             if not commit.get('distinct', True):
61                 log.msg('Commit `%s` is a non-distinct commit, ignoring...' %
62                         (commit['id'],))
63                 continue
64 
65             files = []
66             for kind in ('added', 'modified', 'removed'):
67                 files.extend(commit.get(kind, []))
68 
69             when_timestamp = dateparse(commit['timestamp'])
70 
71             log.msg("New revision: %s" % commit['id'][:8])
72 
73             change = {
74                 'author': '%s <%s>' % (commit['author']['name'],
75                                        commit['author']['email']),
76                 'files': files,
77                 'comments': commit['message'],
78                 'revision': commit['id'],
79                 'when_timestamp': when_timestamp,
80                 'branch': branch,
81                 'revlink': commit['url'],
82                 'repository': repo_url,
83                 'project': project,
84                 'category': event,
85                 'properties': {
86                     'event': event,
87                 },
88             }
89 
90             if codebase is not None:
91                 change['codebase'] = codebase
92 
93             changes.append(change)
94 
95         return changes
96 
97     def _process_merge_request_change(self, payload, project, event, codebase=None):
98         """
99         Consumes the merge_request JSON as a python object and turn it into a buildbot change.
100 
101         :arguments:
102             payload
103                 Python Object that represents the JSON sent by GitLab Service
104                 Hook.
105         """
106         attrs = payload['object_attributes']
107         commit = attrs['last_commit']
108         when_timestamp = dateparse(commit['timestamp'])
109         # @todo provide and document a way to choose between http and ssh url
110         repo_url = attrs['source']['git_http_url']
111         changes = [{
112             'author': '%s <%s>' % (commit['author']['name'],
113                                    commit['author']['email']),
114             'files': [],  # @todo use rest API
115             'comments': "MR#{}: {}\n\n{}".format(attrs['iid'], attrs['title'], attrs['description']),
116             'revision': commit['id'],
117             'when_timestamp': when_timestamp,
118             'branch': attrs['source_branch'],
119             'repository': repo_url,
120             'project': project,
121             'category': event,
122             'revlink': attrs['url'],
123             'properties': {
124                 'target_branch': attrs['target_branch'],
125                 'target_repository': attrs['target']['git_http_url'],
126                 'event': event,
127             },
128         }]
129         if codebase is not None:
130             changes[0]['codebase'] = codebase
131         return changes
132 
133     def getChanges(self, request):
134         """
135         Reponds only to POST events and starts the build process
136 
137         :arguments:
138             request
139                 the http request object
140         """
141         expected_secret = isinstance(self.options, dict) and self.options.get('secret')
142         if expected_secret:
143             received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)
144             received_secret = bytes2unicode(received_secret)
145             if received_secret != expected_secret:
146                 raise ValueError("Invalid secret")
147         try:
148             content = request.content.read()
149             payload = json.loads(bytes2unicode(content))
150         except Exception as e:
151             raise ValueError("Error loading JSON: " + str(e))
152         event_type = request.getHeader(_HEADER_EVENT)
153         event_type = bytes2unicode(event_type)
154         # newer version of gitlab have a object_kind parameter,
155         # which allows not to use the http header
156         event_type = payload.get('object_kind', event_type)
157         project = request.args.get('project', [''])[0]
158         codebase = request.args.get('codebase', [None])[0]
159         if event_type in ("push", "tag_push", "Push Hook"):
160             user = payload['user_name']
161             repo = payload['repository']['name']
162             repo_url = payload['repository']['url']
163             changes = self._process_change(
164                 payload, user, repo, repo_url, project, event_type, codebase=codebase)
165         elif event_type == 'merge_request':
166             changes = self._process_merge_request_change(
167                 payload, project, event_type, codebase=codebase)
168         else:
169             changes = []
170         if changes:
171             log.msg("Received {} changes from {} gitlab event".format(
172                 len(changes), event_type))
173         return (changes, 'git')
174 
175 
176 gitlab = GitLabHandler
177
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/www/hooks/gitlab.py b/master/buildbot/www/hooks/gitlab.py
--- a/master/buildbot/www/hooks/gitlab.py
+++ b/master/buildbot/www/hooks/gitlab.py
@@ -154,8 +154,10 @@
         # newer version of gitlab have a object_kind parameter,
         # which allows not to use the http header
         event_type = payload.get('object_kind', event_type)
-        project = request.args.get('project', [''])[0]
-        codebase = request.args.get('codebase', [None])[0]
+        project = request.args.get(b'project', [''])[0]
+        project = bytes2unicode(project)
+        codebase = request.args.get(b'codebase', [None])[0]
+        codebase = bytes2unicode(codebase)
         if event_type in ("push", "tag_push", "Push Hook"):
             user = payload['user_name']
             repo = payload['repository']['name']

{"golden_diff": "diff --git a/master/buildbot/www/hooks/gitlab.py b/master/buildbot/www/hooks/gitlab.py\n--- a/master/buildbot/www/hooks/gitlab.py\n+++ b/master/buildbot/www/hooks/gitlab.py\n@@ -154,8 +154,10 @@\n         # newer version of gitlab have a object_kind parameter,\n         # which allows not to use the http header\n         event_type = payload.get('object_kind', event_type)\n-        project = request.args.get('project', [''])[0]\n-        codebase = request.args.get('codebase', [None])[0]\n+        project = request.args.get(b'project', [''])[0]\n+        project = bytes2unicode(project)\n+        codebase = request.args.get(b'codebase', [None])[0]\n+        codebase = bytes2unicode(codebase)\n         if event_type in (\"push\", \"tag_push\", \"Push Hook\"):\n             user = payload['user_name']\n             repo = payload['repository']['name']\n", "issue": "REST args in GitLab push notification do not work\nThis is fundamentally the same problem we had at #3506 (@rodrigc). In Python3, some strings are now byte strings and are not identified properly by Buildbot.\r\n\r\nIn this case, I have a local gitlab instance with the webhook for push events: `https://mybuildbot/change_hook/gitlab?project=lthmb`.\r\n\r\nHowever, when I test this the Buildbot logs show no `project` parameter set:\r\n```\r\n2017-10-27 18:15:14+0200 [-] injected change Change(revision='2afc1ab3ab28980db47d64714852aa0d586be7f9', who='Paulo Matos <pmatos@linki.tools>', branch='master', comments=\"Merge branch 'foobar'\\n\", when=1506599131, category='push', project='', repository='git@linki:pmatos/lthmb.git', codebase='')\r\n```\r\nI went ahead and printed the request received from gitlab in `www/hooks/gitlab.py` and noticed that `request.args` in `getChanges` are byte strings: `2017-10-27 18:15:14+0200 [-] {b'project': [b'lthmb']}`\r\n\r\nI think in Buildbot nine with Py3 we might need string conversion here.\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom __future__ import absolute_import\nfrom __future__ import print_function\n\nimport json\nimport re\n\nfrom dateutil.parser import parse as dateparse\n\nfrom twisted.python import log\n\nfrom buildbot.util import bytes2unicode\nfrom buildbot.www.hooks.base import BaseHookHandler\n\n_HEADER_EVENT = b'X-Gitlab-Event'\n_HEADER_GITLAB_TOKEN = b'X-Gitlab-Token'\n\n\nclass GitLabHandler(BaseHookHandler):\n\n    def _process_change(self, payload, user, repo, repo_url, project, event,\n                        codebase=None):\n        \"\"\"\n        Consumes the JSON as a python object and actually starts the build.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        changes = []\n        refname = payload['ref']\n\n        # We only care about regular heads or tags\n        match = re.match(r\"^refs/(heads|tags)/(.+)$\", refname)\n        if not match:\n            log.msg(\"Ignoring refname `%s': Not a branch\" % refname)\n            return changes\n\n        branch = match.group(2)\n        if payload.get('deleted'):\n            log.msg(\"Branch `%s' deleted, ignoring\" % branch)\n            return changes\n\n        for commit in payload['commits']:\n            if not commit.get('distinct', True):\n                log.msg('Commit `%s` is a non-distinct commit, ignoring...' %\n                        (commit['id'],))\n                continue\n\n            files = []\n            for kind in ('added', 'modified', 'removed'):\n                files.extend(commit.get(kind, []))\n\n            when_timestamp = dateparse(commit['timestamp'])\n\n            log.msg(\"New revision: %s\" % commit['id'][:8])\n\n            change = {\n                'author': '%s <%s>' % (commit['author']['name'],\n                                       commit['author']['email']),\n                'files': files,\n                'comments': commit['message'],\n                'revision': commit['id'],\n                'when_timestamp': when_timestamp,\n                'branch': branch,\n                'revlink': commit['url'],\n                'repository': repo_url,\n                'project': project,\n                'category': event,\n                'properties': {\n                    'event': event,\n                },\n            }\n\n            if codebase is not None:\n                change['codebase'] = codebase\n\n            changes.append(change)\n\n        return changes\n\n    def _process_merge_request_change(self, payload, project, event, codebase=None):\n        \"\"\"\n        Consumes the merge_request JSON as a python object and turn it into a buildbot change.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        attrs = payload['object_attributes']\n        commit = attrs['last_commit']\n        when_timestamp = dateparse(commit['timestamp'])\n        # @todo provide and document a way to choose between http and ssh url\n        repo_url = attrs['source']['git_http_url']\n        changes = [{\n            'author': '%s <%s>' % (commit['author']['name'],\n                                   commit['author']['email']),\n            'files': [],  # @todo use rest API\n            'comments': \"MR#{}: {}\\n\\n{}\".format(attrs['iid'], attrs['title'], attrs['description']),\n            'revision': commit['id'],\n            'when_timestamp': when_timestamp,\n            'branch': attrs['source_branch'],\n            'repository': repo_url,\n            'project': project,\n            'category': event,\n            'revlink': attrs['url'],\n            'properties': {\n                'target_branch': attrs['target_branch'],\n                'target_repository': attrs['target']['git_http_url'],\n                'event': event,\n            },\n        }]\n        if codebase is not None:\n            changes[0]['codebase'] = codebase\n        return changes\n\n    def getChanges(self, request):\n        \"\"\"\n        Reponds only to POST events and starts the build process\n\n        :arguments:\n            request\n                the http request object\n        \"\"\"\n        expected_secret = isinstance(self.options, dict) and self.options.get('secret')\n        if expected_secret:\n            received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)\n            received_secret = bytes2unicode(received_secret)\n            if received_secret != expected_secret:\n                raise ValueError(\"Invalid secret\")\n        try:\n            content = request.content.read()\n            payload = json.loads(bytes2unicode(content))\n        except Exception as e:\n            raise ValueError(\"Error loading JSON: \" + str(e))\n        event_type = request.getHeader(_HEADER_EVENT)\n        event_type = bytes2unicode(event_type)\n        # newer version of gitlab have a object_kind parameter,\n        # which allows not to use the http header\n        event_type = payload.get('object_kind', event_type)\n        project = request.args.get('project', [''])[0]\n        codebase = request.args.get('codebase', [None])[0]\n        if event_type in (\"push\", \"tag_push\", \"Push Hook\"):\n            user = payload['user_name']\n            repo = payload['repository']['name']\n            repo_url = payload['repository']['url']\n            changes = self._process_change(\n                payload, user, repo, repo_url, project, event_type, codebase=codebase)\n        elif event_type == 'merge_request':\n            changes = self._process_merge_request_change(\n                payload, project, event_type, codebase=codebase)\n        else:\n            changes = []\n        if changes:\n            log.msg(\"Received {} changes from {} gitlab event\".format(\n                len(changes), event_type))\n        return (changes, 'git')\n\n\ngitlab = GitLabHandler\n", "path": "master/buildbot/www/hooks/gitlab.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom __future__ import absolute_import\nfrom __future__ import print_function\n\nimport json\nimport re\n\nfrom dateutil.parser import parse as dateparse\n\nfrom twisted.python import log\n\nfrom buildbot.util import bytes2unicode\nfrom buildbot.www.hooks.base import BaseHookHandler\n\n_HEADER_EVENT = b'X-Gitlab-Event'\n_HEADER_GITLAB_TOKEN = b'X-Gitlab-Token'\n\n\nclass GitLabHandler(BaseHookHandler):\n\n    def _process_change(self, payload, user, repo, repo_url, project, event,\n                        codebase=None):\n        \"\"\"\n        Consumes the JSON as a python object and actually starts the build.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        changes = []\n        refname = payload['ref']\n\n        # We only care about regular heads or tags\n        match = re.match(r\"^refs/(heads|tags)/(.+)$\", refname)\n        if not match:\n            log.msg(\"Ignoring refname `%s': Not a branch\" % refname)\n            return changes\n\n        branch = match.group(2)\n        if payload.get('deleted'):\n            log.msg(\"Branch `%s' deleted, ignoring\" % branch)\n            return changes\n\n        for commit in payload['commits']:\n            if not commit.get('distinct', True):\n                log.msg('Commit `%s` is a non-distinct commit, ignoring...' %\n                        (commit['id'],))\n                continue\n\n            files = []\n            for kind in ('added', 'modified', 'removed'):\n                files.extend(commit.get(kind, []))\n\n            when_timestamp = dateparse(commit['timestamp'])\n\n            log.msg(\"New revision: %s\" % commit['id'][:8])\n\n            change = {\n                'author': '%s <%s>' % (commit['author']['name'],\n                                       commit['author']['email']),\n                'files': files,\n                'comments': commit['message'],\n                'revision': commit['id'],\n                'when_timestamp': when_timestamp,\n                'branch': branch,\n                'revlink': commit['url'],\n                'repository': repo_url,\n                'project': project,\n                'category': event,\n                'properties': {\n                    'event': event,\n                },\n            }\n\n            if codebase is not None:\n                change['codebase'] = codebase\n\n            changes.append(change)\n\n        return changes\n\n    def _process_merge_request_change(self, payload, project, event, codebase=None):\n        \"\"\"\n        Consumes the merge_request JSON as a python object and turn it into a buildbot change.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        attrs = payload['object_attributes']\n        commit = attrs['last_commit']\n        when_timestamp = dateparse(commit['timestamp'])\n        # @todo provide and document a way to choose between http and ssh url\n        repo_url = attrs['source']['git_http_url']\n        changes = [{\n            'author': '%s <%s>' % (commit['author']['name'],\n                                   commit['author']['email']),\n            'files': [],  # @todo use rest API\n            'comments': \"MR#{}: {}\\n\\n{}\".format(attrs['iid'], attrs['title'], attrs['description']),\n            'revision': commit['id'],\n            'when_timestamp': when_timestamp,\n            'branch': attrs['source_branch'],\n            'repository': repo_url,\n            'project': project,\n            'category': event,\n            'revlink': attrs['url'],\n            'properties': {\n                'target_branch': attrs['target_branch'],\n                'target_repository': attrs['target']['git_http_url'],\n                'event': event,\n            },\n        }]\n        if codebase is not None:\n            changes[0]['codebase'] = codebase\n        return changes\n\n    def getChanges(self, request):\n        \"\"\"\n        Reponds only to POST events and starts the build process\n\n        :arguments:\n            request\n                the http request object\n        \"\"\"\n        expected_secret = isinstance(self.options, dict) and self.options.get('secret')\n        if expected_secret:\n            received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)\n            received_secret = bytes2unicode(received_secret)\n            if received_secret != expected_secret:\n                raise ValueError(\"Invalid secret\")\n        try:\n            content = request.content.read()\n            payload = json.loads(bytes2unicode(content))\n        except Exception as e:\n            raise ValueError(\"Error loading JSON: \" + str(e))\n        event_type = request.getHeader(_HEADER_EVENT)\n        event_type = bytes2unicode(event_type)\n        # newer version of gitlab have a object_kind parameter,\n        # which allows not to use the http header\n        event_type = payload.get('object_kind', event_type)\n        project = request.args.get(b'project', [''])[0]\n        project = bytes2unicode(project)\n        codebase = request.args.get(b'codebase', [None])[0]\n        codebase = bytes2unicode(codebase)\n        if event_type in (\"push\", \"tag_push\", \"Push Hook\"):\n            user = payload['user_name']\n            repo = payload['repository']['name']\n            repo_url = payload['repository']['url']\n            changes = self._process_change(\n                payload, user, repo, repo_url, project, event_type, codebase=codebase)\n        elif event_type == 'merge_request':\n            changes = self._process_merge_request_change(\n                payload, project, event_type, codebase=codebase)\n        else:\n            changes = []\n        if changes:\n            log.msg(\"Received {} changes from {} gitlab event\".format(\n                len(changes), event_type))\n        return (changes, 'git')\n\n\ngitlab = GitLabHandler\n", "path": "master/buildbot/www/hooks/gitlab.py"}]}