Skip to content

Commit 144b2b1

Browse files
committed
- Fix Startup loading
- Fix import issues
1 parent 8d4de25 commit 144b2b1

File tree

18 files changed

+1327
-181
lines changed

18 files changed

+1327
-181
lines changed

backend/backend.spec

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ a = Analysis(
9595
'core.serializers',
9696
'core.urls',
9797
'core.utils',
98+
'core.patches',
99+
'core.logging_filters',
100+
'core.middleware',
101+
'core.exception_handler',
98102
# Python standard library modules that might be needed
99103
'sqlite3',
100104
'json',

backend/config/settings.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
DEBUG = os.getenv('DEBUG', 'True') == 'True'
1818
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')
1919

20+
# Import patches early to suppress BrokenPipeError
21+
try:
22+
import core.patches # noqa: F401
23+
except ImportError:
24+
pass
25+
2026
# Application definition
2127
INSTALLED_APPS = [
2228
'django.contrib.admin',
@@ -41,6 +47,7 @@
4147
'django.contrib.auth.middleware.AuthenticationMiddleware',
4248
'django.contrib.messages.middleware.MessageMiddleware',
4349
'django.middleware.clickjacking.XFrameOptionsMiddleware',
50+
'core.middleware.BrokenPipeHandlerMiddleware',
4451
]
4552

4653
ROOT_URLCONF = 'config.urls'
@@ -121,6 +128,7 @@
121128
'DEFAULT_PARSER_CLASSES': [
122129
'rest_framework.parsers.JSONParser',
123130
],
131+
'EXCEPTION_HANDLER': 'core.exception_handler.rest_framework_exception_handler',
124132
}
125133

126134
# CORS settings
@@ -141,3 +149,36 @@
141149
CORS_ALLOW_ALL_ORIGINS = os.getenv('DEBUG', 'True') == 'True'
142150

143151
CORS_ALLOW_CREDENTIALS = True
152+
153+
# Logging configuration to suppress BrokenPipeError
154+
LOGGING = {
155+
'version': 1,
156+
'disable_existing_loggers': False,
157+
'filters': {
158+
'suppress_broken_pipe': {
159+
'()': 'core.logging_filters.SuppressBrokenPipe',
160+
},
161+
},
162+
'handlers': {
163+
'console': {
164+
'class': 'logging.StreamHandler',
165+
'filters': ['suppress_broken_pipe'],
166+
},
167+
},
168+
'root': {
169+
'handlers': ['console'],
170+
'level': 'INFO',
171+
},
172+
'loggers': {
173+
'django': {
174+
'handlers': ['console'],
175+
'level': 'INFO',
176+
'propagate': False,
177+
},
178+
'django.server': {
179+
'handlers': ['console'],
180+
'level': 'INFO',
181+
'propagate': False,
182+
},
183+
},
184+
}

backend/config/urls.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,30 @@
33
"""
44
from django.contrib import admin
55
from django.urls import path, include
6+
from django.http import JsonResponse
7+
from django.views.decorators.csrf import csrf_exempt
8+
9+
def handler500(request):
10+
"""
11+
Custom 500 handler that returns JSON for API requests.
12+
This ensures API errors return JSON instead of HTML, even during server initialization.
13+
"""
14+
# Check if this is an API request
15+
if request.path.startswith('/api/'):
16+
import traceback
17+
import sys
18+
exc_type, exc_value, tb = sys.exc_info()
19+
return JsonResponse(
20+
{
21+
'error': str(exc_value) if exc_value else 'Internal server error',
22+
'error_type': exc_type.__name__ if exc_type else 'UnknownError',
23+
'detail': 'An error occurred processing your request'
24+
},
25+
status=500
26+
)
27+
# For non-API requests, use Django's default handler
28+
from django.views.debug import technical_500_response
29+
return technical_500_response(request)
630

731
urlpatterns = [
832
path('admin/', admin.site.urls),

backend/config/wsgi.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,100 @@
33
"""
44

55
import os
6+
import logging
7+
import sys
68

79
from django.core.wsgi import get_wsgi_application
810

911
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
1012

11-
application = get_wsgi_application()
13+
logger = logging.getLogger(__name__)
14+
15+
# Get the base WSGI application
16+
_base_application = get_wsgi_application()
17+
18+
19+
class BrokenPipeHandler:
20+
"""
21+
WSGI wrapper that handles BrokenPipeError gracefully.
22+
23+
BrokenPipeError occurs when Django tries to write a response to a connection
24+
that has already been closed by the client. This is not a critical error
25+
and should be handled silently.
26+
"""
27+
28+
def __init__(self, application):
29+
self.application = application
30+
31+
def __call__(self, environ, start_response):
32+
# Wrap start_response to catch BrokenPipeError
33+
response_sent = [False]
34+
35+
def wrapped_start_response(status, headers, exc_info=None):
36+
try:
37+
start_response(status, headers, exc_info)
38+
response_sent[0] = True
39+
except (BrokenPipeError, OSError) as e:
40+
errno = getattr(e, 'errno', None)
41+
if errno == 32 or 'Broken pipe' in str(e) or 'BrokenPipeError' in str(type(e).__name__):
42+
logger.warning("Client disconnected during start_response")
43+
response_sent[0] = True
44+
raise
45+
raise
46+
47+
try:
48+
# Get the response iterator
49+
response_iter = self.application(environ, wrapped_start_response)
50+
51+
# Wrap the iterator to catch BrokenPipeError during iteration
52+
class SafeIterator:
53+
def __init__(self, iterator):
54+
# Convert to an actual iterator - Response objects are iterable
55+
# but not iterators (they have __iter__ but not __next__)
56+
self.iterator = iter(iterator)
57+
58+
def __iter__(self):
59+
return self
60+
61+
def __next__(self):
62+
try:
63+
return next(self.iterator)
64+
except (BrokenPipeError, OSError) as e:
65+
errno = getattr(e, 'errno', None)
66+
if errno == 32 or 'Broken pipe' in str(e) or 'BrokenPipeError' in str(type(e).__name__):
67+
logger.warning("Client disconnected during response iteration")
68+
raise StopIteration
69+
raise
70+
71+
def close(self):
72+
if hasattr(self.iterator, 'close'):
73+
try:
74+
self.iterator.close()
75+
except (BrokenPipeError, OSError) as e:
76+
errno = getattr(e, 'errno', None)
77+
if errno == 32 or 'Broken pipe' in str(e) or 'BrokenPipeError' in str(type(e).__name__):
78+
logger.warning("Client disconnected during response close")
79+
return
80+
raise
81+
82+
return SafeIterator(response_iter)
83+
84+
except (BrokenPipeError, OSError) as e:
85+
# Check if it's a broken pipe error (client disconnected)
86+
errno = getattr(e, 'errno', None)
87+
if errno == 32 or 'Broken pipe' in str(e) or 'BrokenPipeError' in str(type(e).__name__):
88+
# Client disconnected - log as warning but don't fail
89+
logger.warning(f"Client disconnected during response (operation may have succeeded)")
90+
# Return an empty response
91+
if not response_sent[0]:
92+
try:
93+
wrapped_start_response('200 OK', [])
94+
except:
95+
pass
96+
return []
97+
# Re-raise other OSErrors
98+
raise
99+
100+
101+
# Wrap the application to handle BrokenPipeError
102+
application = BrokenPipeHandler(_base_application)

backend/core/exception_handler.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Custom exception handler to suppress BrokenPipeError in Django.
3+
"""
4+
import logging
5+
import sys
6+
from rest_framework.views import exception_handler as drf_exception_handler
7+
from rest_framework.response import Response
8+
from rest_framework import status
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
def custom_exception_handler(exc, context):
14+
"""
15+
Custom exception handler that suppresses BrokenPipeError.
16+
17+
This prevents Django from showing error pages when clients disconnect
18+
during response writing.
19+
"""
20+
# Check if it's a BrokenPipeError
21+
if isinstance(exc, (BrokenPipeError, OSError)):
22+
errno = getattr(exc, 'errno', None)
23+
if errno == 32 or 'Broken pipe' in str(exc) or 'BrokenPipeError' in str(type(exc).__name__):
24+
# Suppress BrokenPipeError - log as warning but don't show error page
25+
logger.warning(f"Client disconnected during response: {context.get('request', {}).path if context else 'unknown'}")
26+
# Return None to suppress the error page
27+
return None
28+
29+
# For all other exceptions, use Django's default handler
30+
# Import here to avoid circular imports
31+
from django.views.debug import technical_500_response
32+
if sys.exc_info()[1] is exc:
33+
return technical_500_response(request=context.get('request'), exc=exc)
34+
return None
35+
36+
37+
def rest_framework_exception_handler(exc, context):
38+
"""
39+
Custom REST framework exception handler that ensures JSON responses.
40+
41+
This ensures all exceptions return JSON, not HTML error pages.
42+
"""
43+
# First, try REST framework's default exception handler
44+
response = drf_exception_handler(exc, context)
45+
46+
# If REST framework handled it, return the response (should be JSON)
47+
if response is not None:
48+
return response
49+
50+
# If REST framework didn't handle it, it's likely a non-API exception
51+
# Log it and return a JSON error response
52+
import traceback
53+
logger.error(f"Unhandled exception in REST framework view: {exc}", exc_info=True)
54+
55+
# Check if it's a BrokenPipeError (should be suppressed)
56+
if isinstance(exc, (BrokenPipeError, OSError)):
57+
errno = getattr(exc, 'errno', None)
58+
if errno == 32 or 'Broken pipe' in str(exc) or 'BrokenPipeError' in str(type(exc).__name__):
59+
logger.warning(f"Client disconnected during response: {context.get('request', {}).path if context else 'unknown'}")
60+
# Return a minimal JSON response
61+
return Response({'error': 'Client disconnected'}, status=status.HTTP_200_OK)
62+
63+
# For all other exceptions, return JSON error response
64+
return Response(
65+
{
66+
'error': str(exc),
67+
'error_type': type(exc).__name__,
68+
'detail': 'An error occurred processing your request'
69+
},
70+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
71+
)

backend/core/logging_filters.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
Logging filters to suppress BrokenPipeError.
3+
"""
4+
import logging
5+
6+
7+
class SuppressBrokenPipe(logging.Filter):
8+
"""
9+
Filter to suppress BrokenPipeError from being logged.
10+
11+
BrokenPipeError occurs when Django tries to write a response to a connection
12+
that has already been closed by the client. This is not a critical error
13+
and should be handled silently.
14+
"""
15+
16+
def filter(self, record):
17+
"""Filter out BrokenPipeError log records."""
18+
# Check if the log record is about BrokenPipeError
19+
if hasattr(record, 'exc_info') and record.exc_info:
20+
exc_type, exc_value, exc_traceback = record.exc_info
21+
if exc_type is BrokenPipeError or (isinstance(exc_value, BrokenPipeError)):
22+
return False # Suppress this log record
23+
# Also check for OSError with errno 32 (broken pipe)
24+
if exc_type is OSError and hasattr(exc_value, 'errno') and exc_value.errno == 32:
25+
return False # Suppress this log record
26+
27+
# Check the message text for BrokenPipeError
28+
if record.getMessage():
29+
msg = str(record.getMessage())
30+
if 'BrokenPipeError' in msg or 'Broken pipe' in msg:
31+
return False # Suppress this log record
32+
33+
return True # Allow other log records

backend/core/middleware.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
Custom middleware to handle BrokenPipeError gracefully.
3+
This prevents Django from showing error pages when clients disconnect during response.
4+
"""
5+
import logging
6+
import sys
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class BrokenPipeHandlerMiddleware:
12+
"""
13+
Middleware to catch and handle BrokenPipeError gracefully.
14+
15+
BrokenPipeError occurs when Django tries to write a response to a connection
16+
that has already been closed by the client. This is not a critical error
17+
and should be handled silently.
18+
19+
Note: This middleware may not catch all BrokenPipeError cases as they can
20+
occur at the WSGI level during response writing. The WSGI wrapper in
21+
config/wsgi.py also handles these errors.
22+
"""
23+
24+
def __init__(self, get_response):
25+
self.get_response = get_response
26+
27+
def __call__(self, request):
28+
try:
29+
response = self.get_response(request)
30+
return response
31+
except (BrokenPipeError, OSError) as e:
32+
# Check if it's a broken pipe error (client disconnected)
33+
errno = getattr(e, 'errno', None)
34+
if errno == 32 or 'Broken pipe' in str(e) or 'BrokenPipeError' in str(type(e).__name__):
35+
# Client disconnected - log as warning but don't fail
36+
logger.warning(f"Client disconnected during request: {request.path}")
37+
# Suppress the error - operation likely succeeded
38+
# Return a minimal response that won't be sent anyway
39+
from django.http import HttpResponse
40+
return HttpResponse(status=200)
41+
# Re-raise other OSErrors
42+
raise
43+
44+
def process_exception(self, request, exception):
45+
"""Handle exceptions during response rendering."""
46+
if isinstance(exception, (BrokenPipeError, OSError)):
47+
errno = getattr(exception, 'errno', None)
48+
if errno == 32 or 'Broken pipe' in str(exception) or 'BrokenPipeError' in str(type(exception).__name__):
49+
logger.warning(f"Client disconnected during response: {request.path}")
50+
# Suppress the error - operation likely succeeded
51+
# Return None to suppress the error page
52+
return None
53+
return None

0 commit comments

Comments
 (0)