Fixed "Address already in use" from liveserver.

Our WSGIServer rewrapped the socket errors from server_bind into
WSGIServerExceptions, which is used later on to provide nicer
error messages in runserver and used by the liveserver to see if
the port is already in use. But wrapping server_bind isn't enough since
it only binds to the socket, socket.listen (which is called from
server_activate) could also raise "Address already in use".

Instead of overriding server_activate too I chose to just catch socket
errors, which seems to make more sense anyways and should be more robust
against changes in wsgiref.
This commit is contained in:
Florian Apolloner 2013-09-22 15:55:09 +02:00
parent bebb449ac3
commit 2ca00faa91
4 changed files with 15 additions and 22 deletions

View File

@ -1,12 +1,13 @@
from optparse import make_option from optparse import make_option
from datetime import datetime from datetime import datetime
import errno
import os import os
import re import re
import sys import sys
import socket import socket
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.core.servers.basehttp import run, WSGIServerException, get_internal_wsgi_application from django.core.servers.basehttp import run, get_internal_wsgi_application
from django.utils import autoreload from django.utils import autoreload
naiveip_re = re.compile(r"""^(?: naiveip_re = re.compile(r"""^(?:
@ -117,16 +118,16 @@ class Command(BaseCommand):
handler = self.get_handler(*args, **options) handler = self.get_handler(*args, **options)
run(self.addr, int(self.port), handler, run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading) ipv6=self.use_ipv6, threading=threading)
except WSGIServerException as e: except socket.error as e:
# Use helpful error messages instead of ugly tracebacks. # Use helpful error messages instead of ugly tracebacks.
ERRORS = { ERRORS = {
13: "You don't have permission to access that port.", errno.EACCES: "You don't have permission to access that port.",
98: "That port is already in use.", errno.EADDRINUSE: "That port is already in use.",
99: "That IP address can't be assigned-to.", errno.EADDRNOTAVAIL: "That IP address can't be assigned-to.",
} }
try: try:
error_text = ERRORS[e.args[0].args[0]] error_text = ERRORS[e.errno]
except (AttributeError, KeyError): except KeyError:
error_text = str(e) error_text = str(e)
self.stderr.write("Error: %s" % error_text) self.stderr.write("Error: %s" % error_text)
# Need to use an OS exit because sys.exit doesn't work in a thread # Need to use an OS exit because sys.exit doesn't work in a thread

View File

@ -58,10 +58,6 @@ def get_internal_wsgi_application():
) )
class WSGIServerException(Exception):
pass
class ServerHandler(simple_server.ServerHandler, object): class ServerHandler(simple_server.ServerHandler, object):
error_status = str("500 INTERNAL SERVER ERROR") error_status = str("500 INTERNAL SERVER ERROR")
@ -114,10 +110,7 @@ class WSGIServer(simple_server.WSGIServer, object):
def server_bind(self): def server_bind(self):
"""Override server_bind to store the server name.""" """Override server_bind to store the server name."""
try:
super(WSGIServer, self).server_bind() super(WSGIServer, self).server_bind()
except Exception as e:
six.reraise(WSGIServerException, WSGIServerException(e), sys.exc_info()[2])
self.setup_environ() self.setup_environ()

View File

@ -8,6 +8,7 @@ import json
import os import os
import posixpath import posixpath
import re import re
import socket
import sys import sys
import threading import threading
import unittest import unittest
@ -21,8 +22,7 @@ from django.core.handlers.wsgi import get_path_info, WSGIHandler
from django.core.management import call_command from django.core.management import call_command
from django.core.management.color import no_style from django.core.management.color import no_style
from django.core.management.commands import flush from django.core.management.commands import flush
from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer, from django.core.servers.basehttp import WSGIRequestHandler, WSGIServer
WSGIServerException)
from django.core.urlresolvers import clear_url_caches, set_urlconf from django.core.urlresolvers import clear_url_caches, set_urlconf
from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction
from django.db.models.loading import cache from django.db.models.loading import cache
@ -1028,10 +1028,9 @@ class LiveServerThread(threading.Thread):
try: try:
self.httpd = WSGIServer( self.httpd = WSGIServer(
(self.host, port), QuietWSGIRequestHandler) (self.host, port), QuietWSGIRequestHandler)
except WSGIServerException as e: except socket.error as e:
if (index + 1 < len(self.possible_ports) and if (index + 1 < len(self.possible_ports) and
hasattr(e.args[0], 'errno') and e.errno == errno.EADDRINUSE):
e.args[0].errno == errno.EADDRINUSE):
# This port is already in use, so we go on and try with # This port is already in use, so we go on and try with
# the next one in the list. # the next one in the list.
continue continue

View File

@ -5,10 +5,10 @@ Tests for django.core.servers.
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import socket
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.test import LiveServerTestCase from django.test import LiveServerTestCase
from django.core.servers.basehttp import WSGIServerException
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.six.moves.urllib.error import HTTPError from django.utils.six.moves.urllib.error import HTTPError
@ -71,7 +71,7 @@ class LiveServerAddress(LiveServerBase):
cls.raises_exception('localhost', ImproperlyConfigured) cls.raises_exception('localhost', ImproperlyConfigured)
# The host must be valid # The host must be valid
cls.raises_exception('blahblahblah:8081', WSGIServerException) cls.raises_exception('blahblahblah:8081', socket.error)
# The list of ports must be in a valid format # The list of ports must be in a valid format
cls.raises_exception('localhost:8081,', ImproperlyConfigured) cls.raises_exception('localhost:8081,', ImproperlyConfigured)