diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index b63b57a67c..55c7869fea 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -9,7 +9,12 @@ from django.core.handlers.wsgi import WSGIHandler from django.core.servers.basehttp import AdminMediaHandler, run, WSGIServerException from django.utils import autoreload -naiveip_re = r'^(?:(?P\d{1,3}(?:\.\d{1,3}){3}|\[[a-fA-F0-9:]+\]):)?(?P\d+)$' +naiveip_re = re.compile(r"""^(?: +(?P + (?P\d{1,3}(?:\.\d{1,3}){3}) | # IPv4 address + (?P\[[a-fA-F0-9:]+\]) | # IPv6 address + (?P[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*) # FQDN +):)?(?P\d+)$""", re.X) DEFAULT_PORT = "8000" class BaseRunserverCommand(BaseCommand): @@ -33,28 +38,29 @@ class BaseRunserverCommand(BaseCommand): def handle(self, addrport='', *args, **options): self.use_ipv6 = options.get('use_ipv6') - if self.use_ipv6 and not hasattr(socket, 'AF_INET6'): + if self.use_ipv6 and not socket.has_ipv6: raise CommandError('Your Python does not support IPv6.') if args: raise CommandError('Usage is runserver %s' % self.args) + self._raw_ipv6 = False if not addrport: self.addr = '' self.port = DEFAULT_PORT else: m = re.match(naiveip_re, addrport) if m is None: - raise CommandError('%r is not a valid port number' + raise CommandError('"%s" is not a valid port number ' 'or address:port pair.' % addrport) - self.addr, self.port = m.groups() + self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups() if not self.port.isdigit(): raise CommandError("%r is not a valid port number." % self.port) if self.addr: - if self.addr.startswith('[') and self.addr.endswith(']'): + if _ipv6: self.addr = self.addr[1:-1] self.use_ipv6 = True - elif self.use_ipv6: - raise CommandError('IPv6 addresses must be surrounded ' - 'with brackets, e.g. [::1].') + self._raw_ipv6 = True + elif self.use_ipv6 and not _fqdn: + raise CommandError('"%s" is not a valid IPv6 address.' % self.addr) if not self.addr: self.addr = self.use_ipv6 and '::1' or '127.0.0.1' self.run(*args, **options) @@ -86,7 +92,7 @@ class BaseRunserverCommand(BaseCommand): ) % { "version": self.get_version(), "settings": settings.SETTINGS_MODULE, - "addr": self.use_ipv6 and '[%s]' % self.addr or self.addr, + "addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr, "port": self.port, "quit_command": quit_command, }) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 09856201ea..1150acb0cd 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -616,7 +616,7 @@ Example usage:: Run a FastCGI server as a daemon and write the spawned PID in a file. -runserver [port or ipaddr:port] +runserver [port or address:port] ------------------------------- .. django-admin:: runserver @@ -653,8 +653,10 @@ machines on the network, use its own IP address (e.g. ``192.168.2.1``) or .. versionchanged:: 1.3 -You can also provide an IPv6 address surrounded by brackets -(eg. ``[200a::1]:8000``). This will automaticaly enable IPv6 support. +You can provide an IPv6 address surrounded by brackets +(e.g. ``[200a::1]:8000``). This will automatically enable IPv6 support. + +A hostname containing ASCII-only characters can also be used. .. django-admin-option:: --adminmedia @@ -721,6 +723,14 @@ Port 7000 on IPv6 address ``2001:0db8:1234:5678::9``:: django-admin.py runserver [2001:0db8:1234:5678::9]:7000 +Port 8000 on IPv4 address of host ``localhost``:: + + django-admin.py runserver localhost:8000 + +Port 8000 on IPv6 address of host ``localhost``:: + + django-admin.py runserver -6 localhost:8000 + Serving static files with the development server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py index 94d8f93809..ae37eb3a95 100644 --- a/tests/regressiontests/admin_scripts/tests.py +++ b/tests/regressiontests/admin_scripts/tests.py @@ -999,6 +999,61 @@ class ManageValidate(AdminScriptTestCase): self.assertNoOutput(err) self.assertOutput(out, '0 errors found') +class ManageRunserver(AdminScriptTestCase): + def setUp(self): + from django.core.management.commands.runserver import BaseRunserverCommand + def monkey_run(*args, **options): return + + self.cmd = BaseRunserverCommand() + self.cmd.run = monkey_run + + def assertServerSettings(self, addr, port, ipv6=None, raw_ipv6=False): + self.assertEqual(self.cmd.addr, addr) + self.assertEqual(self.cmd.port, port) + self.assertEqual(self.cmd.use_ipv6, ipv6) + self.assertEqual(self.cmd._raw_ipv6, raw_ipv6) + + def test_runserver_addrport(self): + self.cmd.handle() + self.assertServerSettings('127.0.0.1', '8000') + + self.cmd.handle(addrport="1.2.3.4:8000") + self.assertServerSettings('1.2.3.4', '8000') + + self.cmd.handle(addrport="7000") + self.assertServerSettings('127.0.0.1', '7000') + + # IPv6 + self.cmd.handle(addrport="", use_ipv6=True) + self.assertServerSettings('::1', '8000', ipv6=True) + + self.cmd.handle(addrport="7000", use_ipv6=True) + self.assertServerSettings('::1', '7000', ipv6=True) + + self.cmd.handle(addrport="[2001:0db8:1234:5678::9]:7000") + self.assertServerSettings('2001:0db8:1234:5678::9', '7000', ipv6=True, raw_ipv6=True) + + # Hostname + self.cmd.handle(addrport="localhost:8000") + self.assertServerSettings('localhost', '8000') + + self.cmd.handle(addrport="test.domain.local:7000") + self.assertServerSettings('test.domain.local', '7000') + + self.cmd.handle(addrport="test.domain.local:7000", use_ipv6=True) + self.assertServerSettings('test.domain.local', '7000', ipv6=True) + + # Potentially ambiguous + + # Only 4 characters, all of which coudl be in an ipv6 address + self.cmd.handle(addrport="beef:7654") + self.assertServerSettings('beef', '7654') + + # Uses only characters that could be in an ipv6 address + self.cmd.handle(addrport="deadbeef:7654") + self.assertServerSettings('deadbeef', '7654') + + ########################################################################## # COMMAND PROCESSING TESTS # Check that user-space commands are correctly handled - in particular,