We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Support CORS on JSON API
Say I want to show project stats like monthly downloads on the project page. Fetching this from client side JavaScript allows me to retain a completely static project page (can be hosted on a CDN).

However, the JSON API does not set `Access-Control-Allow-Origin` header - no CORS support.

Test:
https://github.com/oberstet/scratchbox/blob/master/js/badges/pypi.html

For comparison, this is what GitHub does with it's API:
https://github.com/oberstet/scratchbox/blob/master/js/badges/github.html

If above would work, that makes #330 void (for me) - if the API doesn't get rate limited or such ..


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `warehouse/legacy/api/json.py`
Content:
```
1 # Licensed under the Apache License, Version 2.0 (the "License");
2 # you may not use this file except in compliance with the License.
3 # You may obtain a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12 
13 from pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound
14 from pyramid.view import view_config
15 from sqlalchemy import func
16 from sqlalchemy.orm.exc import NoResultFound
17 
18 from warehouse.cache.http import cache_control
19 from warehouse.cache.origin import origin_cache
20 from warehouse.packaging.interfaces import IDownloadStatService
21 from warehouse.packaging.models import File, Release, JournalEntry
22 
23 
24 @view_config(
25     route_name="legacy.api.json.project",
26     renderer="json",
27     decorator=[
28         cache_control(
29             1 * 24 * 60 * 60,                         # 1 day
30             stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day
31             stale_if_error=1 * 24 * 60 * 60,          # 1 day
32         ),
33         origin_cache(7 * 24 * 60 * 60),   # 7 days
34     ],
35 )
36 def json_project(project, request):
37     if project.name != request.matchdict.get("name", project.name):
38         return HTTPMovedPermanently(
39             request.current_route_path(name=project.name),
40         )
41 
42     try:
43         release = project.releases.order_by(
44             Release._pypi_ordering.desc()
45         ).limit(1).one()
46     except NoResultFound:
47         return HTTPNotFound()
48 
49     return json_release(release, request)
50 
51 
52 @view_config(
53     route_name="legacy.api.json.release",
54     renderer="json",
55     decorator=[
56         cache_control(
57             7 * 24 * 60 * 60,                         # 7 days
58             stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day
59             stale_if_error=1 * 24 * 60 * 60,          # 1 day
60         ),
61         origin_cache(30 * 24 * 60 * 60),  # 30 days
62     ],
63 )
64 def json_release(release, request):
65     project = release.project
66 
67     if project.name != request.matchdict.get("name", project.name):
68         return HTTPMovedPermanently(
69             request.current_route_path(name=project.name),
70         )
71 
72     # We want to allow CORS here to enable anyone to fetch data from this API
73     request.response.headers["Access-Control-Allow-Origin"] = "*"
74 
75     # Get the latest serial number for this project.
76     serial = (
77         request.db.query(func.max(JournalEntry.id))
78                   .filter(JournalEntry.name == project.name)
79                   .scalar()
80     )
81     request.response.headers["X-PyPI-Last-Serial"] = serial or 0
82 
83     # Get all of the releases and files for this project.
84     release_files = (
85         request.db.query(Release, File)
86                .outerjoin(File)
87                .filter(Release.project == project)
88                .order_by(Release._pypi_ordering.desc(), File.filename)
89                .all()
90     )
91 
92     # Map our releases + files into a dictionary that maps each release to a
93     # list of all its files.
94     releases = {}
95     for r, file_ in release_files:
96         files = releases.setdefault(r, [])
97         if file_ is not None:
98             files.append(file_)
99 
100     # Serialize our database objects to match the way that PyPI legacy
101     # presented this data.
102     releases = {
103         r.version: [
104             {
105                 "filename": f.filename,
106                 "packagetype": f.packagetype,
107                 "python_version": f.python_version,
108                 "has_sig": f.has_signature,
109                 "comment_text": f.comment_text,
110                 "md5_digest": f.md5_digest,
111                 "size": f.size,
112                 "downloads": f.downloads,
113                 "upload_time": f.upload_time.strftime("%Y-%m-%dT%H:%M:%S"),
114                 "url": request.route_url("packaging.file", path=f.path),
115             }
116             for f in fs
117         ]
118         for r, fs in releases.items()
119     }
120 
121     # Get our stats service
122     stats_svc = request.find_service(IDownloadStatService)
123 
124     return {
125         "info": {
126             "name": project.name,
127             "version": release.version,
128             "summary": release.summary,
129             "description": release.description,
130             "keywords": release.keywords,
131             "license": release.license,
132             "classifiers": list(release.classifiers),
133             "author": release.author,
134             "author_email": release.author_email,
135             "maintainer": release.maintainer,
136             "maintainer_email": release.maintainer_email,
137             "requires_python": release.requires_python,
138             "platform": release.platform,
139             "downloads": {
140                 "last_day": stats_svc.get_daily_stats(project.name),
141                 "last_week": stats_svc.get_weekly_stats(project.name),
142                 "last_month": stats_svc.get_monthly_stats(project.name),
143             },
144             "project_url": request.route_url(
145                 "packaging.project",
146                 name=project.name,
147             ),
148             "release_url": request.route_url(
149                 "packaging.release",
150                 name=project.name,
151                 version=release.version,
152             ),
153             "docs_url": project.documentation_url,
154             "bugtrack_url": project.bugtrack_url,
155             "home_page": release.home_page,
156             "download_url": release.download_url,
157         },
158         "urls": releases[release.version],
159         "releases": releases,
160     }
161
```
--- 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/warehouse/legacy/api/json.py b/warehouse/legacy/api/json.py
--- a/warehouse/legacy/api/json.py
+++ b/warehouse/legacy/api/json.py
@@ -71,6 +71,18 @@
 
     # We want to allow CORS here to enable anyone to fetch data from this API
     request.response.headers["Access-Control-Allow-Origin"] = "*"
+    request.response.headers["Access-Control-Allow-Headers"] = ", ".join([
+        "Content-Type",
+        "If-Match",
+        "If-Modified-Since",
+        "If-None-Match",
+        "If-Unmodified-Since",
+    ])
+    request.response.headers["Access-Control-Allow-Methods"] = "GET"
+    request.response.headers["Access-Control-Max-Age"] = "86400"
+    request.response.headers["Access-Control-Expose-Headers"] = ", ".join([
+        "X-PyPI-Last-Serial",
+    ])
 
     # Get the latest serial number for this project.
     serial = (

{"golden_diff": "diff --git a/warehouse/legacy/api/json.py b/warehouse/legacy/api/json.py\n--- a/warehouse/legacy/api/json.py\n+++ b/warehouse/legacy/api/json.py\n@@ -71,6 +71,18 @@\n \n     # We want to allow CORS here to enable anyone to fetch data from this API\n     request.response.headers[\"Access-Control-Allow-Origin\"] = \"*\"\n+    request.response.headers[\"Access-Control-Allow-Headers\"] = \", \".join([\n+        \"Content-Type\",\n+        \"If-Match\",\n+        \"If-Modified-Since\",\n+        \"If-None-Match\",\n+        \"If-Unmodified-Since\",\n+    ])\n+    request.response.headers[\"Access-Control-Allow-Methods\"] = \"GET\"\n+    request.response.headers[\"Access-Control-Max-Age\"] = \"86400\"\n+    request.response.headers[\"Access-Control-Expose-Headers\"] = \", \".join([\n+        \"X-PyPI-Last-Serial\",\n+    ])\n \n     # Get the latest serial number for this project.\n     serial = (\n", "issue": "Support CORS on JSON API\nSay I want to show project stats like monthly downloads on the project page. Fetching this from client side JavaScript allows me to retain a completely static project page (can be hosted on a CDN).\n\nHowever, the JSON API does not set `Access-Control-Allow-Origin` header - no CORS support.\n\nTest:\nhttps://github.com/oberstet/scratchbox/blob/master/js/badges/pypi.html\n\nFor comparison, this is what GitHub does with it's API:\nhttps://github.com/oberstet/scratchbox/blob/master/js/badges/github.html\n\nIf above would work, that makes #330 void (for me) - if the API doesn't get rate limited or such ..\n\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound\nfrom pyramid.view import view_config\nfrom sqlalchemy import func\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.cache.http import cache_control\nfrom warehouse.cache.origin import origin_cache\nfrom warehouse.packaging.interfaces import IDownloadStatService\nfrom warehouse.packaging.models import File, Release, JournalEntry\n\n\n@view_config(\n    route_name=\"legacy.api.json.project\",\n    renderer=\"json\",\n    decorator=[\n        cache_control(\n            1 * 24 * 60 * 60,                         # 1 day\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=1 * 24 * 60 * 60,          # 1 day\n        ),\n        origin_cache(7 * 24 * 60 * 60),   # 7 days\n    ],\n)\ndef json_project(project, request):\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    try:\n        release = project.releases.order_by(\n            Release._pypi_ordering.desc()\n        ).limit(1).one()\n    except NoResultFound:\n        return HTTPNotFound()\n\n    return json_release(release, request)\n\n\n@view_config(\n    route_name=\"legacy.api.json.release\",\n    renderer=\"json\",\n    decorator=[\n        cache_control(\n            7 * 24 * 60 * 60,                         # 7 days\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=1 * 24 * 60 * 60,          # 1 day\n        ),\n        origin_cache(30 * 24 * 60 * 60),  # 30 days\n    ],\n)\ndef json_release(release, request):\n    project = release.project\n\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    # We want to allow CORS here to enable anyone to fetch data from this API\n    request.response.headers[\"Access-Control-Allow-Origin\"] = \"*\"\n\n    # Get the latest serial number for this project.\n    serial = (\n        request.db.query(func.max(JournalEntry.id))\n                  .filter(JournalEntry.name == project.name)\n                  .scalar()\n    )\n    request.response.headers[\"X-PyPI-Last-Serial\"] = serial or 0\n\n    # Get all of the releases and files for this project.\n    release_files = (\n        request.db.query(Release, File)\n               .outerjoin(File)\n               .filter(Release.project == project)\n               .order_by(Release._pypi_ordering.desc(), File.filename)\n               .all()\n    )\n\n    # Map our releases + files into a dictionary that maps each release to a\n    # list of all its files.\n    releases = {}\n    for r, file_ in release_files:\n        files = releases.setdefault(r, [])\n        if file_ is not None:\n            files.append(file_)\n\n    # Serialize our database objects to match the way that PyPI legacy\n    # presented this data.\n    releases = {\n        r.version: [\n            {\n                \"filename\": f.filename,\n                \"packagetype\": f.packagetype,\n                \"python_version\": f.python_version,\n                \"has_sig\": f.has_signature,\n                \"comment_text\": f.comment_text,\n                \"md5_digest\": f.md5_digest,\n                \"size\": f.size,\n                \"downloads\": f.downloads,\n                \"upload_time\": f.upload_time.strftime(\"%Y-%m-%dT%H:%M:%S\"),\n                \"url\": request.route_url(\"packaging.file\", path=f.path),\n            }\n            for f in fs\n        ]\n        for r, fs in releases.items()\n    }\n\n    # Get our stats service\n    stats_svc = request.find_service(IDownloadStatService)\n\n    return {\n        \"info\": {\n            \"name\": project.name,\n            \"version\": release.version,\n            \"summary\": release.summary,\n            \"description\": release.description,\n            \"keywords\": release.keywords,\n            \"license\": release.license,\n            \"classifiers\": list(release.classifiers),\n            \"author\": release.author,\n            \"author_email\": release.author_email,\n            \"maintainer\": release.maintainer,\n            \"maintainer_email\": release.maintainer_email,\n            \"requires_python\": release.requires_python,\n            \"platform\": release.platform,\n            \"downloads\": {\n                \"last_day\": stats_svc.get_daily_stats(project.name),\n                \"last_week\": stats_svc.get_weekly_stats(project.name),\n                \"last_month\": stats_svc.get_monthly_stats(project.name),\n            },\n            \"project_url\": request.route_url(\n                \"packaging.project\",\n                name=project.name,\n            ),\n            \"release_url\": request.route_url(\n                \"packaging.release\",\n                name=project.name,\n                version=release.version,\n            ),\n            \"docs_url\": project.documentation_url,\n            \"bugtrack_url\": project.bugtrack_url,\n            \"home_page\": release.home_page,\n            \"download_url\": release.download_url,\n        },\n        \"urls\": releases[release.version],\n        \"releases\": releases,\n    }\n", "path": "warehouse/legacy/api/json.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound\nfrom pyramid.view import view_config\nfrom sqlalchemy import func\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.cache.http import cache_control\nfrom warehouse.cache.origin import origin_cache\nfrom warehouse.packaging.interfaces import IDownloadStatService\nfrom warehouse.packaging.models import File, Release, JournalEntry\n\n\n@view_config(\n    route_name=\"legacy.api.json.project\",\n    renderer=\"json\",\n    decorator=[\n        cache_control(\n            1 * 24 * 60 * 60,                         # 1 day\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=1 * 24 * 60 * 60,          # 1 day\n        ),\n        origin_cache(7 * 24 * 60 * 60),   # 7 days\n    ],\n)\ndef json_project(project, request):\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    try:\n        release = project.releases.order_by(\n            Release._pypi_ordering.desc()\n        ).limit(1).one()\n    except NoResultFound:\n        return HTTPNotFound()\n\n    return json_release(release, request)\n\n\n@view_config(\n    route_name=\"legacy.api.json.release\",\n    renderer=\"json\",\n    decorator=[\n        cache_control(\n            7 * 24 * 60 * 60,                         # 7 days\n            stale_while_revalidate=1 * 24 * 60 * 60,  # 1 day\n            stale_if_error=1 * 24 * 60 * 60,          # 1 day\n        ),\n        origin_cache(30 * 24 * 60 * 60),  # 30 days\n    ],\n)\ndef json_release(release, request):\n    project = release.project\n\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n        )\n\n    # We want to allow CORS here to enable anyone to fetch data from this API\n    request.response.headers[\"Access-Control-Allow-Origin\"] = \"*\"\n    request.response.headers[\"Access-Control-Allow-Headers\"] = \", \".join([\n        \"Content-Type\",\n        \"If-Match\",\n        \"If-Modified-Since\",\n        \"If-None-Match\",\n        \"If-Unmodified-Since\",\n    ])\n    request.response.headers[\"Access-Control-Allow-Methods\"] = \"GET\"\n    request.response.headers[\"Access-Control-Max-Age\"] = \"86400\"\n    request.response.headers[\"Access-Control-Expose-Headers\"] = \", \".join([\n        \"X-PyPI-Last-Serial\",\n    ])\n\n    # Get the latest serial number for this project.\n    serial = (\n        request.db.query(func.max(JournalEntry.id))\n                  .filter(JournalEntry.name == project.name)\n                  .scalar()\n    )\n    request.response.headers[\"X-PyPI-Last-Serial\"] = serial or 0\n\n    # Get all of the releases and files for this project.\n    release_files = (\n        request.db.query(Release, File)\n               .outerjoin(File)\n               .filter(Release.project == project)\n               .order_by(Release._pypi_ordering.desc(), File.filename)\n               .all()\n    )\n\n    # Map our releases + files into a dictionary that maps each release to a\n    # list of all its files.\n    releases = {}\n    for r, file_ in release_files:\n        files = releases.setdefault(r, [])\n        if file_ is not None:\n            files.append(file_)\n\n    # Serialize our database objects to match the way that PyPI legacy\n    # presented this data.\n    releases = {\n        r.version: [\n            {\n                \"filename\": f.filename,\n                \"packagetype\": f.packagetype,\n                \"python_version\": f.python_version,\n                \"has_sig\": f.has_signature,\n                \"comment_text\": f.comment_text,\n                \"md5_digest\": f.md5_digest,\n                \"size\": f.size,\n                \"downloads\": f.downloads,\n                \"upload_time\": f.upload_time.strftime(\"%Y-%m-%dT%H:%M:%S\"),\n                \"url\": request.route_url(\"packaging.file\", path=f.path),\n            }\n            for f in fs\n        ]\n        for r, fs in releases.items()\n    }\n\n    # Get our stats service\n    stats_svc = request.find_service(IDownloadStatService)\n\n    return {\n        \"info\": {\n            \"name\": project.name,\n            \"version\": release.version,\n            \"summary\": release.summary,\n            \"description\": release.description,\n            \"keywords\": release.keywords,\n            \"license\": release.license,\n            \"classifiers\": list(release.classifiers),\n            \"author\": release.author,\n            \"author_email\": release.author_email,\n            \"maintainer\": release.maintainer,\n            \"maintainer_email\": release.maintainer_email,\n            \"requires_python\": release.requires_python,\n            \"platform\": release.platform,\n            \"downloads\": {\n                \"last_day\": stats_svc.get_daily_stats(project.name),\n                \"last_week\": stats_svc.get_weekly_stats(project.name),\n                \"last_month\": stats_svc.get_monthly_stats(project.name),\n            },\n            \"project_url\": request.route_url(\n                \"packaging.project\",\n                name=project.name,\n            ),\n            \"release_url\": request.route_url(\n                \"packaging.release\",\n                name=project.name,\n                version=release.version,\n            ),\n            \"docs_url\": project.documentation_url,\n            \"bugtrack_url\": project.bugtrack_url,\n            \"home_page\": release.home_page,\n            \"download_url\": release.download_url,\n        },\n        \"urls\": releases[release.version],\n        \"releases\": releases,\n    }\n", "path": "warehouse/legacy/api/json.py"}]}