2 HTTP server that implements the Python WSGI protocol (PEP 333, rev 1.21).
4 Based on wsgiref.simple_server which is part of the standard library since 2.5.
6 This is a simple server for use in testing or debugging Django apps. It hasn't
7 been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE!
16 from SocketServer
import ThreadingMixIn
17 from wsgiref
import simple_server
18 from wsgiref
.util
import FileWrapper
# for backwards compatibility
21 from django
.core
.exceptions
import ImproperlyConfigured
22 from django
.core
.management
.color
import color_style
23 from django
.core
.wsgi
import get_wsgi_application
24 from django
.utils
.importlib
import import_module
25 from django
.utils
._os
import safe_join
26 from django
.views
import static
28 from django
.contrib
.staticfiles
import handlers
30 __all__
= ['WSGIServer', 'WSGIRequestHandler']
33 def get_internal_wsgi_application():
35 Loads and returns the WSGI application as configured by the user in
36 ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
37 this will be the ``application`` object in ``projectname/wsgi.py``.
39 This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
40 for Django's internal servers (runserver, runfcgi); external WSGI servers
41 should just be configured to point to the correct application object
44 If settings.WSGI_APPLICATION is not set (is ``None``), we just return
45 whatever ``django.core.wsgi.get_wsgi_application`` returns.
48 from django
.conf
import settings
49 app_path
= getattr(settings
, 'WSGI_APPLICATION')
51 return get_wsgi_application()
52 module_name
, attr
= app_path
.rsplit('.', 1)
54 mod
= import_module(module_name
)
55 except ImportError, e
:
56 raise ImproperlyConfigured(
57 "WSGI application '%s' could not be loaded; "
58 "could not import module '%s': %s" % (app_path
, module_name
, e
))
60 app
= getattr(mod
, attr
)
61 except AttributeError, e
:
62 raise ImproperlyConfigured(
63 "WSGI application '%s' could not be loaded; "
64 "can't find '%s' in module '%s': %s"
65 % (app_path
, attr
, module_name
, e
))
70 class WSGIServerException(Exception):
74 class ServerHandler(simple_server
.ServerHandler
, object):
75 error_status
= "500 INTERNAL SERVER ERROR"
77 def write(self
, data
):
78 """'write()' callable as specified by PEP 333"""
80 assert isinstance(data
, str), "write() argument must be string"
83 raise AssertionError("write() before start_response()")
85 elif not self
.headers_sent
:
86 # Before the first output, send the stored headers
87 self
.bytes_sent
= len(data
) # make sure we know content-length
90 self
.bytes_sent
+= len(data
)
92 # XXX check Content-Length and truncate if too many bytes written?
94 # If data is too large, socket will choke, so write chunks no larger
95 # than 32MB at a time.
99 while offset
< length
:
100 chunk_size
= min(33554432, length
)
101 self
._write
(data
[offset
:offset
+chunk_size
])
108 def error_output(self
, environ
, start_response
):
109 super(ServerHandler
, self
).error_output(environ
, start_response
)
110 return ['\n'.join(traceback
.format_exception(*sys
.exc_info()))]
113 class WSGIServer(simple_server
.WSGIServer
, object):
114 """BaseHTTPServer that implements the Python WSGI protocol"""
116 def __init__(self
, *args
, **kwargs
):
117 if kwargs
.pop('ipv6', False):
118 self
.address_family
= socket
.AF_INET6
119 super(WSGIServer
, self
).__init
__(*args
, **kwargs
)
121 def server_bind(self
):
122 """Override server_bind to store the server name."""
124 super(WSGIServer
, self
).server_bind()
126 raise WSGIServerException(e
)
130 class WSGIRequestHandler(simple_server
.WSGIRequestHandler
, object):
132 def __init__(self
, *args
, **kwargs
):
133 from django
.conf
import settings
134 self
.admin_media_prefix
= urlparse
.urljoin(settings
.STATIC_URL
, 'admin/')
135 # We set self.path to avoid crashes in log_message() on unsupported
136 # requests (like "OPTIONS").
138 self
.style
= color_style()
139 super(WSGIRequestHandler
, self
).__init
__(*args
, **kwargs
)
141 def get_environ(self
):
142 env
= self
.server
.base_environ
.copy()
143 env
['SERVER_PROTOCOL'] = self
.request_version
144 env
['REQUEST_METHOD'] = self
.command
146 path
,query
= self
.path
.split('?',1)
148 path
,query
= self
.path
,''
150 env
['PATH_INFO'] = urllib
.unquote(path
)
151 env
['QUERY_STRING'] = query
152 env
['REMOTE_ADDR'] = self
.client_address
[0]
154 if self
.headers
.typeheader
is None:
155 env
['CONTENT_TYPE'] = self
.headers
.type
157 env
['CONTENT_TYPE'] = self
.headers
.typeheader
159 length
= self
.headers
.getheader('content-length')
161 env
['CONTENT_LENGTH'] = length
163 for h
in self
.headers
.headers
:
165 k
=k
.replace('-','_').upper(); v
=v
.strip()
167 continue # skip content length, type,etc.
169 env
['HTTP_'+k
] += ','+v
# comma-separate multiple headers
174 def log_message(self
, format
, *args
):
175 # Don't bother logging requests for admin images or the favicon.
176 if (self
.path
.startswith(self
.admin_media_prefix
)
177 or self
.path
== '/favicon.ico'):
180 msg
= "[%s] %s\n" % (self
.log_date_time_string(), format
% args
)
182 # Utilize terminal colors, if available
183 if args
[1][0] == '2':
184 # Put 2XX first, since it should be the common case
185 msg
= self
.style
.HTTP_SUCCESS(msg
)
186 elif args
[1][0] == '1':
187 msg
= self
.style
.HTTP_INFO(msg
)
188 elif args
[1] == '304':
189 msg
= self
.style
.HTTP_NOT_MODIFIED(msg
)
190 elif args
[1][0] == '3':
191 msg
= self
.style
.HTTP_REDIRECT(msg
)
192 elif args
[1] == '404':
193 msg
= self
.style
.HTTP_NOT_FOUND(msg
)
194 elif args
[1][0] == '4':
195 msg
= self
.style
.HTTP_BAD_REQUEST(msg
)
197 # Any 5XX, or any other response
198 msg
= self
.style
.HTTP_SERVER_ERROR(msg
)
200 sys
.stderr
.write(msg
)
203 class AdminMediaHandler(handlers
.StaticFilesHandler
):
205 WSGI middleware that intercepts calls to the admin media directory, as
206 defined by the STATIC_URL setting, and serves those images.
207 Use this ONLY LOCALLY, for development! This hasn't been tested for
208 security and is not super efficient.
210 This is pending for deprecation since 1.3.
212 def get_base_dir(self
):
213 return os
.path
.join(django
.__path
__[0], 'contrib', 'admin', 'static', 'admin')
215 def get_base_url(self
):
216 from django
.conf
import settings
217 return urlparse
.urljoin(settings
.STATIC_URL
, 'admin/')
219 def file_path(self
, url
):
221 Returns the path to the media file on disk for the given URL.
223 The passed URL is assumed to begin with ``self.base_url``. If the
224 resulting file path is outside the media directory, then a ValueError
227 relative_url
= url
[len(self
.base_url
[2]):]
228 relative_path
= urllib
.url2pathname(relative_url
)
229 return safe_join(self
.base_dir
, relative_path
)
231 def serve(self
, request
):
232 document_root
, path
= os
.path
.split(self
.file_path(request
.path
))
233 return static
.serve(request
, path
, document_root
=document_root
)
235 def _should_handle(self
, path
):
237 Checks if the path should be handled. Ignores the path if:
239 * the host is provided as part of the base_url
240 * the request's path isn't under the base path
242 return path
.startswith(self
.base_url
[2]) and not self
.base_url
[1]
245 def run(addr
, port
, wsgi_handler
, ipv6
=False, threading
=False):
246 server_address
= (addr
, port
)
248 httpd_cls
= type('WSGIServer', (ThreadingMixIn
, WSGIServer
), {})
250 httpd_cls
= WSGIServer
251 httpd
= httpd_cls(server_address
, WSGIRequestHandler
, ipv6
=ipv6
)
252 httpd
.set_app(wsgi_handler
)
253 httpd
.serve_forever()