-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mechanism for tracking Starlette gzip module, closes #1
- Loading branch information
Showing
3 changed files
with
167 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
name: Track the Starlette version of this | ||
|
||
on: | ||
push: | ||
workflow_dispatch: | ||
schedule: | ||
- cron: '21 5 * * *' | ||
|
||
permissions: | ||
issues: write | ||
contents: write | ||
|
||
jobs: | ||
check: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions/github-script@v6 | ||
env: | ||
URL: https://raw.githubusercontent.com/encode/starlette/master/starlette/middleware/gzip.py | ||
FILE_NAME: tracking/gzip.py | ||
with: | ||
script: | | ||
const { URL, FILE_NAME } = process.env; | ||
const util = require("util"); | ||
// Because 'exec' variable name is already used: | ||
const exec_ = util.promisify(require("child_process").exec); | ||
await exec_(`curl -o ${FILE_NAME} ${URL}`); | ||
const { stdout } = await exec_(`git diff ${FILE_NAME}`); | ||
if (stdout) { | ||
// There was a diff to that file | ||
const title = `${FILE_NAME} was updated`; | ||
const body = | ||
`${URL} changed:` + | ||
"\n\n```diff\n" + | ||
stdout + | ||
"\n```\n\n" + | ||
"Close this issue once those changes have been integrated here"; | ||
const issue = await github.rest.issues.create({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
title: title, | ||
body: body, | ||
}); | ||
const issueNumber = issue.data.number; | ||
// Now commit and reference that issue | ||
const commitMessage = `${FILE_NAME} updated, refs #${issueNumber}`; | ||
await exec_(`git config user.name "Automated"`); | ||
await exec_(`git config user.email "actions@users.noreply.github.com"`); | ||
await exec_(`git add -A`); | ||
await exec_(`git commit -m "${commitMessage}" || exit 0`); | ||
await exec_(`git pull --rebase`); | ||
await exec_(`git push`); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import gzip | ||
import io | ||
import typing | ||
|
||
from starlette.datastructures import Headers, MutableHeaders | ||
from starlette.types import ASGIApp, Message, Receive, Scope, Send | ||
|
||
|
||
class GZipMiddleware: | ||
def __init__( | ||
self, app: ASGIApp, minimum_size: int = 500, compresslevel: int = 9 | ||
) -> None: | ||
self.app = app | ||
self.minimum_size = minimum_size | ||
self.compresslevel = compresslevel | ||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | ||
if scope["type"] == "http": | ||
headers = Headers(scope=scope) | ||
if "gzip" in headers.get("Accept-Encoding", ""): | ||
responder = GZipResponder( | ||
self.app, self.minimum_size, compresslevel=self.compresslevel | ||
) | ||
await responder(scope, receive, send) | ||
return | ||
await self.app(scope, receive, send) | ||
|
||
|
||
class GZipResponder: | ||
def __init__(self, app: ASGIApp, minimum_size: int, compresslevel: int = 9) -> None: | ||
self.app = app | ||
self.minimum_size = minimum_size | ||
self.send: Send = unattached_send | ||
self.initial_message: Message = {} | ||
self.started = False | ||
self.gzip_buffer = io.BytesIO() | ||
self.gzip_file = gzip.GzipFile( | ||
mode="wb", fileobj=self.gzip_buffer, compresslevel=compresslevel | ||
) | ||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | ||
self.send = send | ||
await self.app(scope, receive, self.send_with_gzip) | ||
|
||
async def send_with_gzip(self, message: Message) -> None: | ||
message_type = message["type"] | ||
if message_type == "http.response.start": | ||
# Don't send the initial message until we've determined how to | ||
# modify the outgoing headers correctly. | ||
self.initial_message = message | ||
elif message_type == "http.response.body" and not self.started: | ||
self.started = True | ||
body = message.get("body", b"") | ||
more_body = message.get("more_body", False) | ||
if len(body) < self.minimum_size and not more_body: | ||
# Don't apply GZip to small outgoing responses. | ||
await self.send(self.initial_message) | ||
await self.send(message) | ||
elif not more_body: | ||
# Standard GZip response. | ||
self.gzip_file.write(body) | ||
self.gzip_file.close() | ||
body = self.gzip_buffer.getvalue() | ||
|
||
headers = MutableHeaders(raw=self.initial_message["headers"]) | ||
headers["Content-Encoding"] = "gzip" | ||
headers["Content-Length"] = str(len(body)) | ||
headers.add_vary_header("Accept-Encoding") | ||
message["body"] = body | ||
|
||
await self.send(self.initial_message) | ||
await self.send(message) | ||
else: | ||
# Initial body in streaming GZip response. | ||
headers = MutableHeaders(raw=self.initial_message["headers"]) | ||
headers["Content-Encoding"] = "gzip" | ||
headers.add_vary_header("Accept-Encoding") | ||
del headers["Content-Length"] | ||
|
||
self.gzip_file.write(body) | ||
message["body"] = self.gzip_buffer.getvalue() | ||
self.gzip_buffer.seek(0) | ||
self.gzip_buffer.truncate() | ||
|
||
await self.send(self.initial_message) | ||
await self.send(message) | ||
|
||
elif message_type == "http.response.body": | ||
# Remaining body in streaming GZip response. | ||
body = message.get("body", b"") | ||
more_body = message.get("more_body", False) | ||
|
||
self.gzip_file.write(body) | ||
if not more_body: | ||
self.gzip_file.close() | ||
|
||
message["body"] = self.gzip_buffer.getvalue() | ||
self.gzip_buffer.seek(0) | ||
self.gzip_buffer.truncate() | ||
|
||
await self.send(message) | ||
|
||
|
||
async def unattached_send(message: Message) -> typing.NoReturn: | ||
raise RuntimeError("send awaitable not set") # pragma: no cover |