Fixed #7735 -- Added support for IPv6 adresses to runserver and testserver management command. Thanks to Jason Alonso and Łukasz Rekucki for the report and initial patches.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14711 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2010-11-26 13:33:53 +00:00
parent 132afbf8ee
commit 6a32e253f6
5 changed files with 82 additions and 27 deletions

View File

@ -1,14 +1,21 @@
from optparse import make_option from optparse import make_option
import os import os
import re
import sys import sys
import socket
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.core.handlers.wsgi import WSGIHandler from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandler, run, WSGIServerException from django.core.servers.basehttp import AdminMediaHandler, run, WSGIServerException
from django.utils import autoreload from django.utils import autoreload
naiveip_re = r'^(?:(?P<addr>\d{1,3}(?:\.\d{1,3}){3}|\[[a-fA-F0-9:]+\]):)?(?P<port>\d+)$'
DEFAULT_PORT = "8000"
class BaseRunserverCommand(BaseCommand): class BaseRunserverCommand(BaseCommand):
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--ipv6', '-6', action='store_true', dest='use_ipv6', default=False,
help='Tells Django to use a IPv6 address.'),
make_option('--noreload', action='store_false', dest='use_reloader', default=True, make_option('--noreload', action='store_false', dest='use_reloader', default=True,
help='Tells Django to NOT use the auto-reloader.'), help='Tells Django to NOT use the auto-reloader.'),
) )
@ -25,22 +32,31 @@ class BaseRunserverCommand(BaseCommand):
return WSGIHandler() return WSGIHandler()
def handle(self, addrport='', *args, **options): def handle(self, addrport='', *args, **options):
self.use_ipv6 = options.get('use_ipv6')
if self.use_ipv6 and not hasattr(socket, 'AF_INET6'):
raise CommandError('Your Python does not support IPv6.')
if args: if args:
raise CommandError('Usage is runserver %s' % self.args) raise CommandError('Usage is runserver %s' % self.args)
if not addrport: if not addrport:
self.addr = '' self.addr = ''
self.port = '8000' self.port = DEFAULT_PORT
else: else:
try: m = re.match(naiveip_re, addrport)
self.addr, self.port = addrport.split(':') if m is None:
except ValueError: raise CommandError('%r is not a valid port number'
self.addr, self.port = '', addrport 'or address:port pair.' % addrport)
self.addr, 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(']'):
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].')
if not self.addr: if not self.addr:
self.addr = '127.0.0.1' self.addr = self.use_ipv6 and '::1' or '127.0.0.1'
if not self.port.isdigit():
raise CommandError("%r is not a valid port number." % self.port)
self.run(*args, **options) self.run(*args, **options)
def run(self, *args, **options): def run(self, *args, **options):
@ -70,7 +86,7 @@ class BaseRunserverCommand(BaseCommand):
) % { ) % {
"version": self.get_version(), "version": self.get_version(),
"settings": settings.SETTINGS_MODULE, "settings": settings.SETTINGS_MODULE,
"addr": self.addr, "addr": self.use_ipv6 and '[%s]' % self.addr or self.addr,
"port": self.port, "port": self.port,
"quit_command": quit_command, "quit_command": quit_command,
}) })
@ -81,7 +97,7 @@ class BaseRunserverCommand(BaseCommand):
try: try:
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)
except WSGIServerException, e: except WSGIServerException, e:
# Use helpful error messages instead of ugly tracebacks. # Use helpful error messages instead of ugly tracebacks.
ERRORS = { ERRORS = {

View File

@ -9,6 +9,8 @@ class Command(BaseCommand):
make_option('--addrport', action='store', dest='addrport', make_option('--addrport', action='store', dest='addrport',
type='string', default='', type='string', default='',
help='port number or ipaddr:port to run the server on'), help='port number or ipaddr:port to run the server on'),
make_option('--ipv6', '-6', action='store_true', dest='use_ipv6', default=False,
help='Tells Django to use a IPv6 address.'),
) )
help = 'Runs a development server with data from the given fixture(s).' help = 'Runs a development server with data from the given fixture(s).'
args = '[fixture ...]' args = '[fixture ...]'
@ -33,4 +35,4 @@ class Command(BaseCommand):
# a strange error -- it causes this handle() method to be called # a strange error -- it causes this handle() method to be called
# multiple times. # multiple times.
shutdown_message = '\nServer stopped.\nNote that the test database, %r, has not been deleted. You can explore it on your own.' % db_name shutdown_message = '\nServer stopped.\nNote that the test database, %r, has not been deleted. You can explore it on your own.' % db_name
call_command('runserver', addrport=addrport, shutdown_message=shutdown_message, use_reloader=False) call_command('runserver', addrport=addrport, shutdown_message=shutdown_message, use_reloader=False, use_ipv6=options['use_ipv6'])

View File

@ -10,6 +10,7 @@ been reviewed for security issues. Don't use it for production use.
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import os import os
import re import re
import socket
import sys import sys
import urllib import urllib
import warnings import warnings
@ -526,6 +527,11 @@ class WSGIServer(HTTPServer):
"""BaseHTTPServer that implements the Python WSGI protocol""" """BaseHTTPServer that implements the Python WSGI protocol"""
application = None application = None
def __init__(self, *args, **kwargs):
if kwargs.pop('ipv6', False):
self.address_family = socket.AF_INET6
HTTPServer.__init__(self, *args, **kwargs)
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: try:
@ -683,9 +689,8 @@ class AdminMediaHandler(handlers.StaticFilesHandler):
""" """
return path.startswith(self.base_url[2]) and not self.base_url[1] return path.startswith(self.base_url[2]) and not self.base_url[1]
def run(addr, port, wsgi_handler, ipv6=False):
def run(addr, port, wsgi_handler):
server_address = (addr, port) server_address = (addr, port)
httpd = WSGIServer(server_address, WSGIRequestHandler) httpd = WSGIServer(server_address, WSGIRequestHandler, ipv6=ipv6)
httpd.set_app(wsgi_handler) httpd.set_app(wsgi_handler)
httpd.serve_forever() httpd.serve_forever()

View File

@ -75,7 +75,7 @@ Runs this project as a FastCGI application. Requires flup. Use
.B runfcgi help .B runfcgi help
for help on the KEY=val pairs. for help on the KEY=val pairs.
.TP .TP
.BI "runserver [" "\-\-noreload" "] [" "\-\-nostatic" "] [" "\-\-insecure" "] [" "\-\-adminmedia=ADMIN_MEDIA_PATH" "] [" "port|ipaddr:port" "]" .BI "runserver [" "\-\-noreload" "] [" "\-\-nostatic" "] [" "\-\-insecure" "] [" "\-\-ipv6" "] [" "\-\-adminmedia=ADMIN_MEDIA_PATH" "] [" "port|ipaddr:port" "]"
Starts a lightweight Web server for development. Starts a lightweight Web server for development.
.TP .TP
.BI "shell [" "\-\-plain" "]" .BI "shell [" "\-\-plain" "]"
@ -170,6 +170,9 @@ Disable automatic serving of static files from STATIC_URL.
.I \-\-insecure .I \-\-insecure
Enables serving of static files even if DEBUG is False. Enables serving of static files even if DEBUG is False.
.TP .TP
.I \-\-ipv6
Enables IPv6 addresses.
.TP
.I \-\-verbosity=VERBOSITY .I \-\-verbosity=VERBOSITY
Verbosity level: 0=minimal output, 1=normal output, 2=all output. Verbosity level: 0=minimal output, 1=normal output, 2=all output.
.TP .TP

View File

@ -630,7 +630,7 @@ runserver [port or ipaddr:port]
.. django-admin:: runserver .. django-admin:: runserver
Starts a lightweight development Web server on the local machine. By default, Starts a lightweight development Web server on the local machine. By default,
the server runs on port 8000 on the IP address 127.0.0.1. You can pass in an the server runs on port 8000 on the IP address ``127.0.0.1``. You can pass in an
IP address and port number explicitly. IP address and port number explicitly.
If you run this script as a user with normal privileges (recommended), you If you run this script as a user with normal privileges (recommended), you
@ -654,10 +654,15 @@ them to standard output, but it won't stop the server.
You can run as many servers as you want, as long as they're on separate ports. You can run as many servers as you want, as long as they're on separate ports.
Just execute ``django-admin.py runserver`` more than once. Just execute ``django-admin.py runserver`` more than once.
Note that the default IP address, 127.0.0.1, is not accessible from other Note that the default IP address, ``127.0.0.1``, is not accessible from other
machines on your network. To make your development server viewable to other machines on your network. To make your development server viewable to other
machines on the network, use its own IP address (e.g. ``192.168.2.1``) or machines on the network, use its own IP address (e.g. ``192.168.2.1``) or
``0.0.0.0``. ``0.0.0.0`` or ``::`` (with IPv6 enabled).
.. versionchanged:: 1.3
You can also provide an IPv6 address surrounded by brackets
(eg. ``[200a::1]:8000``). This will automaticaly enable IPv6 support.
.. django-admin-option:: --adminmedia .. django-admin-option:: --adminmedia
@ -681,25 +686,49 @@ Example usage::
django-admin.py runserver --noreload django-admin.py runserver --noreload
.. django-admin-option:: --ipv6, -6
.. versionadded:: 1.3
Use the ``--ipv6`` (or shorter ``-6``) option to tell Django to use IPv6 for
the development server. This changes the default IP address from
``127.0.0.1`` to ``::1``.
Example usage::
django-admin.py runserver --ipv6
Examples of using different ports and addresses Examples of using different ports and addresses
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Port 8000 on IP address 127.0.0.1:: Port 8000 on IP address ``127.0.0.1``::
django-admin.py runserver django-admin.py runserver
Port 8000 on IP address 1.2.3.4:: Port 8000 on IP address ``1.2.3.4``::
django-admin.py runserver 1.2.3.4:8000 django-admin.py runserver 1.2.3.4:8000
Port 7000 on IP address 127.0.0.1:: Port 7000 on IP address ``127.0.0.1``::
django-admin.py runserver 7000 django-admin.py runserver 7000
Port 7000 on IP address 1.2.3.4:: Port 7000 on IP address ``1.2.3.4``::
django-admin.py runserver 1.2.3.4:7000 django-admin.py runserver 1.2.3.4:7000
Port 8000 on IPv6 address ``::1``::
django-admin.py runserver -6
Port 7000 on IPv6 address ``::1``::
django-admin.py runserver -6 7000
Port 7000 on IPv6 address ``2001:0db8:1234:5678::9``::
django-admin.py runserver [2001:0db8:1234:5678::9]:7000
Serving static files with the development server Serving static files with the development server
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -963,7 +992,7 @@ templates.
.. django-admin-option:: --addrport [port number or ipaddr:port] .. django-admin-option:: --addrport [port number or ipaddr:port]
Use ``--addrport`` to specify a different port, or IP address and port, from Use ``--addrport`` to specify a different port, or IP address and port, from
the default of 127.0.0.1:8000. This value follows exactly the same format and the default of ``127.0.0.1:8000``. This value follows exactly the same format and
serves exactly the same function as the argument to the ``runserver`` command. serves exactly the same function as the argument to the ``runserver`` command.
Examples: Examples: