mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-07 05:01:30 +08:00
asgi: Fix pylint and pycodestyle warnings
- Remove unused imports (ssl, os, base64, hashlib, traceback) - Remove unused variables (body_parts, has_content_length, etc.) - Fix no-else-break patterns in protocol.py and websocket.py - Replace __anext__() with anext() builtin - Remove unnecessary pass statements - Add proper access logging to ASGI protocol handler - Add ASGIResponseInfo class and _build_environ method for logging - Disable too-many-return-statements for _read_frame method - Fix raising-bad-type error (use 'is not None' check) - Fix whitespace before colon in message.py
This commit is contained in:
parent
ae1eea8108
commit
11c6a97c47
@ -477,7 +477,7 @@ class AsyncRequest:
|
|||||||
self._body_reader = self._chunked_body_reader()
|
self._body_reader = self._chunked_body_reader()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await self._body_reader.__anext__()
|
return await anext(self._body_reader)
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
self._body_remaining = 0
|
self._body_remaining = 0
|
||||||
return b""
|
return b""
|
||||||
@ -489,7 +489,7 @@ class AsyncRequest:
|
|||||||
size_line = await self._read_chunk_size_line()
|
size_line = await self._read_chunk_size_line()
|
||||||
# Parse chunk size (handle extensions)
|
# Parse chunk size (handle extensions)
|
||||||
chunk_size, *_ = size_line.split(b";", 1)
|
chunk_size, *_ = size_line.split(b";", 1)
|
||||||
if _ :
|
if _:
|
||||||
chunk_size = chunk_size.rstrip(b" \t")
|
chunk_size = chunk_size.rstrip(b" \t")
|
||||||
|
|
||||||
if any(n not in b"0123456789abcdefABCDEF" for n in chunk_size):
|
if any(n not in b"0123456789abcdefABCDEF" for n in chunk_size):
|
||||||
|
|||||||
@ -10,9 +10,6 @@ to ASGI applications.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
|
||||||
import hashlib
|
|
||||||
import traceback
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from gunicorn.asgi.unreader import AsyncUnreader
|
from gunicorn.asgi.unreader import AsyncUnreader
|
||||||
@ -20,6 +17,22 @@ from gunicorn.asgi.message import AsyncRequest
|
|||||||
from gunicorn.http.errors import NoMoreData
|
from gunicorn.http.errors import NoMoreData
|
||||||
|
|
||||||
|
|
||||||
|
class ASGIResponseInfo:
|
||||||
|
"""Simple container for ASGI response info for access logging."""
|
||||||
|
|
||||||
|
def __init__(self, status, headers, sent):
|
||||||
|
self.status = status
|
||||||
|
self.sent = sent
|
||||||
|
# Convert headers to list of string tuples for logging
|
||||||
|
self.headers = []
|
||||||
|
for name, value in headers:
|
||||||
|
if isinstance(name, bytes):
|
||||||
|
name = name.decode("latin-1")
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
value = value.decode("latin-1")
|
||||||
|
self.headers.append((name, value))
|
||||||
|
|
||||||
|
|
||||||
class ASGIProtocol(asyncio.Protocol):
|
class ASGIProtocol(asyncio.Protocol):
|
||||||
"""HTTP/1.1 protocol handler for ASGI applications.
|
"""HTTP/1.1 protocol handler for ASGI applications.
|
||||||
|
|
||||||
@ -97,30 +110,30 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
if self._is_websocket_upgrade(request):
|
if self._is_websocket_upgrade(request):
|
||||||
await self._handle_websocket(request, sockname, peername)
|
await self._handle_websocket(request, sockname, peername)
|
||||||
break # WebSocket takes over the connection
|
break # WebSocket takes over the connection
|
||||||
else:
|
|
||||||
# Handle HTTP request
|
|
||||||
keepalive = await self._handle_http_request(
|
|
||||||
request, sockname, peername
|
|
||||||
)
|
|
||||||
|
|
||||||
# Increment worker request count
|
# Handle HTTP request
|
||||||
self.worker.nr += 1
|
keepalive = await self._handle_http_request(
|
||||||
|
request, sockname, peername
|
||||||
|
)
|
||||||
|
|
||||||
# Check max_requests
|
# Increment worker request count
|
||||||
if self.worker.nr >= self.worker.max_requests:
|
self.worker.nr += 1
|
||||||
self.log.info("Autorestarting worker after current request.")
|
|
||||||
self.worker.alive = False
|
|
||||||
keepalive = False
|
|
||||||
|
|
||||||
if not keepalive or not self.worker.alive:
|
# Check max_requests
|
||||||
break
|
if self.worker.nr >= self.worker.max_requests:
|
||||||
|
self.log.info("Autorestarting worker after current request.")
|
||||||
|
self.worker.alive = False
|
||||||
|
keepalive = False
|
||||||
|
|
||||||
# Check connection limits for keepalive
|
if not keepalive or not self.worker.alive:
|
||||||
if not self.cfg.keepalive:
|
break
|
||||||
break
|
|
||||||
|
|
||||||
# Drain any unread body before next request
|
# Check connection limits for keepalive
|
||||||
await request.drain_body()
|
if not self.cfg.keepalive:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Drain any unread body before next request
|
||||||
|
await request.drain_body()
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
@ -155,9 +168,13 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
scope = self._build_http_scope(request, sockname, peername)
|
scope = self._build_http_scope(request, sockname, peername)
|
||||||
response_started = False
|
response_started = False
|
||||||
response_complete = False
|
response_complete = False
|
||||||
body_parts = []
|
|
||||||
exc_to_raise = None
|
exc_to_raise = None
|
||||||
|
|
||||||
|
# Response tracking for access logging
|
||||||
|
response_status = 500
|
||||||
|
response_headers = []
|
||||||
|
response_sent = 0
|
||||||
|
|
||||||
# Receive queue for body
|
# Receive queue for body
|
||||||
receive_queue = asyncio.Queue()
|
receive_queue = asyncio.Queue()
|
||||||
|
|
||||||
@ -177,6 +194,7 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
|
|
||||||
async def send(message):
|
async def send(message):
|
||||||
nonlocal response_started, response_complete, exc_to_raise
|
nonlocal response_started, response_complete, exc_to_raise
|
||||||
|
nonlocal response_status, response_headers, response_sent
|
||||||
|
|
||||||
msg_type = message["type"]
|
msg_type = message["type"]
|
||||||
|
|
||||||
@ -185,9 +203,9 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
exc_to_raise = RuntimeError("Response already started")
|
exc_to_raise = RuntimeError("Response already started")
|
||||||
return
|
return
|
||||||
response_started = True
|
response_started = True
|
||||||
status = message["status"]
|
response_status = message["status"]
|
||||||
headers = message.get("headers", [])
|
response_headers = message.get("headers", [])
|
||||||
await self._send_response_start(status, headers, request)
|
await self._send_response_start(response_status, response_headers, request)
|
||||||
|
|
||||||
elif msg_type == "http.response.body":
|
elif msg_type == "http.response.body":
|
||||||
if not response_started:
|
if not response_started:
|
||||||
@ -202,32 +220,42 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
|
|
||||||
if body:
|
if body:
|
||||||
await self._send_body(body)
|
await self._send_body(body)
|
||||||
|
response_sent += len(body)
|
||||||
|
|
||||||
if not more_body:
|
if not more_body:
|
||||||
response_complete = True
|
response_complete = True
|
||||||
|
|
||||||
|
# Build environ for logging
|
||||||
|
environ = self._build_environ(request, sockname, peername)
|
||||||
|
resp = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request_start = datetime.now()
|
request_start = datetime.now()
|
||||||
self.cfg.pre_request(self.worker, request)
|
self.cfg.pre_request(self.worker, request)
|
||||||
|
|
||||||
await self.app(scope, receive, send)
|
await self.app(scope, receive, send)
|
||||||
|
|
||||||
if exc_to_raise:
|
if exc_to_raise is not None:
|
||||||
raise exc_to_raise
|
raise exc_to_raise
|
||||||
|
|
||||||
# Ensure response was sent
|
# Ensure response was sent
|
||||||
if not response_started:
|
if not response_started:
|
||||||
await self._send_error_response(500, "Internal Server Error")
|
await self._send_error_response(500, "Internal Server Error")
|
||||||
|
response_status = 500
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
self.log.exception("Error in ASGI application")
|
self.log.exception("Error in ASGI application")
|
||||||
if not response_started:
|
if not response_started:
|
||||||
await self._send_error_response(500, "Internal Server Error")
|
await self._send_error_response(500, "Internal Server Error")
|
||||||
|
response_status = 500
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
request_time = datetime.now() - request_start
|
request_time = datetime.now() - request_start
|
||||||
self.cfg.post_request(self.worker, request, {}, None)
|
# Create response info for logging
|
||||||
|
resp = ASGIResponseInfo(response_status, response_headers, response_sent)
|
||||||
|
self.log.access(resp, request, environ, request_time)
|
||||||
|
self.cfg.post_request(self.worker, request, environ, resp)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.exception("Exception in post_request hook")
|
self.log.exception("Exception in post_request hook")
|
||||||
|
|
||||||
@ -291,6 +319,24 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
|
|
||||||
return scope
|
return scope
|
||||||
|
|
||||||
|
def _build_environ(self, request, sockname, peername):
|
||||||
|
"""Build minimal WSGI-like environ dict for access logging."""
|
||||||
|
environ = {
|
||||||
|
"REQUEST_METHOD": request.method,
|
||||||
|
"RAW_URI": request.uri,
|
||||||
|
"PATH_INFO": request.path,
|
||||||
|
"QUERY_STRING": request.query or "",
|
||||||
|
"SERVER_PROTOCOL": f"HTTP/{request.version[0]}.{request.version[1]}",
|
||||||
|
"REMOTE_ADDR": peername[0] if peername else "-",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add HTTP headers as environ vars
|
||||||
|
for name, value in request.headers:
|
||||||
|
key = "HTTP_" + name.replace("-", "_")
|
||||||
|
environ[key] = value
|
||||||
|
|
||||||
|
return environ
|
||||||
|
|
||||||
def _build_websocket_scope(self, request, sockname, peername):
|
def _build_websocket_scope(self, request, sockname, peername):
|
||||||
"""Build ASGI WebSocket scope from parsed request."""
|
"""Build ASGI WebSocket scope from parsed request."""
|
||||||
# Build headers list as bytes tuples
|
# Build headers list as bytes tuples
|
||||||
@ -334,9 +380,6 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
|
|
||||||
# Build headers
|
# Build headers
|
||||||
header_lines = []
|
header_lines = []
|
||||||
has_content_length = False
|
|
||||||
has_transfer_encoding = False
|
|
||||||
has_connection = False
|
|
||||||
|
|
||||||
for name, value in headers:
|
for name, value in headers:
|
||||||
if isinstance(name, bytes):
|
if isinstance(name, bytes):
|
||||||
@ -344,13 +387,6 @@ class ASGIProtocol(asyncio.Protocol):
|
|||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
value = value.decode("latin-1")
|
value = value.decode("latin-1")
|
||||||
header_lines.append(f"{name}: {value}\r\n")
|
header_lines.append(f"{name}: {value}\r\n")
|
||||||
name_lower = name.lower()
|
|
||||||
if name_lower == "content-length":
|
|
||||||
has_content_length = True
|
|
||||||
elif name_lower == "transfer-encoding":
|
|
||||||
has_transfer_encoding = True
|
|
||||||
elif name_lower == "connection":
|
|
||||||
has_connection = True
|
|
||||||
|
|
||||||
# Add server header if not present
|
# Add server header if not present
|
||||||
header_lines.append("Server: gunicorn/asgi\r\n")
|
header_lines.append("Server: gunicorn/asgi\r\n")
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import asyncio
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import struct
|
import struct
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
# WebSocket frame opcodes
|
# WebSocket frame opcodes
|
||||||
@ -81,7 +80,7 @@ class WebSocketProtocol:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await self.app(self.scope, self._receive, self._send)
|
await self.app(self.scope, self._receive, self._send)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
self.log.exception("Error in WebSocket ASGI application")
|
self.log.exception("Error in WebSocket ASGI application")
|
||||||
finally:
|
finally:
|
||||||
read_task.cancel()
|
read_task.cancel()
|
||||||
@ -180,7 +179,8 @@ class WebSocketProtocol:
|
|||||||
if opcode == OPCODE_CLOSE:
|
if opcode == OPCODE_CLOSE:
|
||||||
await self._handle_close(payload)
|
await self._handle_close(payload)
|
||||||
break
|
break
|
||||||
elif opcode == OPCODE_PING:
|
|
||||||
|
if opcode == OPCODE_PING:
|
||||||
await self._send_frame(OPCODE_PONG, payload)
|
await self._send_frame(OPCODE_PONG, payload)
|
||||||
elif opcode == OPCODE_PONG:
|
elif opcode == OPCODE_PONG:
|
||||||
# Ignore pongs
|
# Ignore pongs
|
||||||
@ -212,7 +212,7 @@ class WebSocketProtocol:
|
|||||||
"code": self.close_code or CLOSE_ABNORMAL,
|
"code": self.close_code or CLOSE_ABNORMAL,
|
||||||
})
|
})
|
||||||
|
|
||||||
async def _read_frame(self):
|
async def _read_frame(self): # pylint: disable=too-many-return-statements
|
||||||
"""Read a single WebSocket frame.
|
"""Read a single WebSocket frame.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -326,10 +326,9 @@ class WebSocketProtocol:
|
|||||||
|
|
||||||
self.closed = True
|
self.closed = True
|
||||||
|
|
||||||
async def _handle_continuation(self, payload):
|
async def _handle_continuation(self, payload): # pylint: disable=unused-argument
|
||||||
"""Handle continuation frame (already processed in _read_frame)."""
|
"""Handle continuation frame (already processed in _read_frame)."""
|
||||||
# This is called for partial fragments, nothing to do
|
# This is called for partial fragments, nothing to do here
|
||||||
pass
|
|
||||||
|
|
||||||
async def _send_frame(self, opcode, payload):
|
async def _send_frame(self, opcode, payload):
|
||||||
"""Send a WebSocket frame.
|
"""Send a WebSocket frame.
|
||||||
|
|||||||
@ -12,7 +12,6 @@ HTTP parsing infrastructure.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import ssl
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from gunicorn.workers import base
|
from gunicorn.workers import base
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user