serving.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. # -*- coding: utf-8 -*-
  2. """
  3. werkzeug.serving
  4. ~~~~~~~~~~~~~~~~
  5. There are many ways to serve a WSGI application. While you're developing
  6. it you usually don't want a full blown webserver like Apache but a simple
  7. standalone one. From Python 2.5 onwards there is the `wsgiref`_ server in
  8. the standard library. If you're using older versions of Python you can
  9. download the package from the cheeseshop.
  10. However there are some caveats. Sourcecode won't reload itself when
  11. changed and each time you kill the server using ``^C`` you get an
  12. `KeyboardInterrupt` error. While the latter is easy to solve the first
  13. one can be a pain in the ass in some situations.
  14. The easiest way is creating a small ``start-myproject.py`` that runs the
  15. application::
  16. #!/usr/bin/env python
  17. # -*- coding: utf-8 -*-
  18. from myproject import make_app
  19. from werkzeug.serving import run_simple
  20. app = make_app(...)
  21. run_simple('localhost', 8080, app, use_reloader=True)
  22. You can also pass it a `extra_files` keyword argument with a list of
  23. additional files (like configuration files) you want to observe.
  24. For bigger applications you should consider using `werkzeug.script`
  25. instead of a simple start file.
  26. :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
  27. :license: BSD, see LICENSE for more details.
  28. """
  29. from __future__ import with_statement
  30. import os
  31. import socket
  32. import sys
  33. import ssl
  34. import signal
  35. def _get_openssl_crypto_module():
  36. try:
  37. from OpenSSL import crypto
  38. except ImportError:
  39. raise TypeError('Using ad-hoc certificates requires the pyOpenSSL '
  40. 'library.')
  41. else:
  42. return crypto
  43. try:
  44. from SocketServer import ThreadingMixIn, ForkingMixIn
  45. from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
  46. except ImportError:
  47. from socketserver import ThreadingMixIn, ForkingMixIn
  48. from http.server import HTTPServer, BaseHTTPRequestHandler
  49. import werkzeug
  50. from werkzeug._internal import _log
  51. from werkzeug._compat import reraise, wsgi_encoding_dance
  52. from werkzeug.urls import url_parse, url_unquote
  53. from werkzeug.exceptions import InternalServerError
  54. class WSGIRequestHandler(BaseHTTPRequestHandler, object):
  55. """A request handler that implements WSGI dispatching."""
  56. @property
  57. def server_version(self):
  58. return 'Werkzeug/' + werkzeug.__version__
  59. def make_environ(self):
  60. request_url = url_parse(self.path)
  61. def shutdown_server():
  62. self.server.shutdown_signal = True
  63. url_scheme = self.server.ssl_context is None and 'http' or 'https'
  64. path_info = url_unquote(request_url.path)
  65. environ = {
  66. 'wsgi.version': (1, 0),
  67. 'wsgi.url_scheme': url_scheme,
  68. 'wsgi.input': self.rfile,
  69. 'wsgi.errors': sys.stderr,
  70. 'wsgi.multithread': self.server.multithread,
  71. 'wsgi.multiprocess': self.server.multiprocess,
  72. 'wsgi.run_once': False,
  73. 'werkzeug.server.shutdown': shutdown_server,
  74. 'SERVER_SOFTWARE': self.server_version,
  75. 'REQUEST_METHOD': self.command,
  76. 'SCRIPT_NAME': '',
  77. 'PATH_INFO': wsgi_encoding_dance(path_info),
  78. 'QUERY_STRING': wsgi_encoding_dance(request_url.query),
  79. 'CONTENT_TYPE': self.headers.get('Content-Type', ''),
  80. 'CONTENT_LENGTH': self.headers.get('Content-Length', ''),
  81. 'REMOTE_ADDR': self.client_address[0],
  82. 'REMOTE_PORT': self.client_address[1],
  83. 'SERVER_NAME': self.server.server_address[0],
  84. 'SERVER_PORT': str(self.server.server_address[1]),
  85. 'SERVER_PROTOCOL': self.request_version
  86. }
  87. for key, value in self.headers.items():
  88. key = 'HTTP_' + key.upper().replace('-', '_')
  89. if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
  90. environ[key] = value
  91. if request_url.netloc:
  92. environ['HTTP_HOST'] = request_url.netloc
  93. return environ
  94. def run_wsgi(self):
  95. if self.headers.get('Expect', '').lower().strip() == '100-continue':
  96. self.wfile.write(b'HTTP/1.1 100 Continue\r\n\r\n')
  97. environ = self.make_environ()
  98. headers_set = []
  99. headers_sent = []
  100. def write(data):
  101. assert headers_set, 'write() before start_response'
  102. if not headers_sent:
  103. status, response_headers = headers_sent[:] = headers_set
  104. try:
  105. code, msg = status.split(None, 1)
  106. except ValueError:
  107. code, msg = status, ""
  108. self.send_response(int(code), msg)
  109. header_keys = set()
  110. for key, value in response_headers:
  111. self.send_header(key, value)
  112. key = key.lower()
  113. header_keys.add(key)
  114. if 'content-length' not in header_keys:
  115. self.close_connection = True
  116. self.send_header('Connection', 'close')
  117. if 'server' not in header_keys:
  118. self.send_header('Server', self.version_string())
  119. if 'date' not in header_keys:
  120. self.send_header('Date', self.date_time_string())
  121. self.end_headers()
  122. assert type(data) is bytes, 'applications must write bytes'
  123. self.wfile.write(data)
  124. self.wfile.flush()
  125. def start_response(status, response_headers, exc_info=None):
  126. if exc_info:
  127. try:
  128. if headers_sent:
  129. reraise(*exc_info)
  130. finally:
  131. exc_info = None
  132. elif headers_set:
  133. raise AssertionError('Headers already set')
  134. headers_set[:] = [status, response_headers]
  135. return write
  136. def execute(app):
  137. application_iter = app(environ, start_response)
  138. try:
  139. for data in application_iter:
  140. write(data)
  141. if not headers_sent:
  142. write(b'')
  143. finally:
  144. if hasattr(application_iter, 'close'):
  145. application_iter.close()
  146. application_iter = None
  147. try:
  148. execute(self.server.app)
  149. except (socket.error, socket.timeout) as e:
  150. self.connection_dropped(e, environ)
  151. except Exception:
  152. if self.server.passthrough_errors:
  153. raise
  154. from werkzeug.debug.tbtools import get_current_traceback
  155. traceback = get_current_traceback(ignore_system_exceptions=True)
  156. try:
  157. # if we haven't yet sent the headers but they are set
  158. # we roll back to be able to set them again.
  159. if not headers_sent:
  160. del headers_set[:]
  161. execute(InternalServerError())
  162. except Exception:
  163. pass
  164. self.server.log('error', 'Error on request:\n%s',
  165. traceback.plaintext)
  166. def handle(self):
  167. """Handles a request ignoring dropped connections."""
  168. rv = None
  169. try:
  170. rv = BaseHTTPRequestHandler.handle(self)
  171. except (socket.error, socket.timeout) as e:
  172. self.connection_dropped(e)
  173. except Exception:
  174. if self.server.ssl_context is None or not is_ssl_error():
  175. raise
  176. if self.server.shutdown_signal:
  177. self.initiate_shutdown()
  178. return rv
  179. def initiate_shutdown(self):
  180. """A horrible, horrible way to kill the server for Python 2.6 and
  181. later. It's the best we can do.
  182. """
  183. # Windows does not provide SIGKILL, go with SIGTERM then.
  184. sig = getattr(signal, 'SIGKILL', signal.SIGTERM)
  185. # reloader active
  186. if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
  187. os.kill(os.getpid(), sig)
  188. # python 2.7
  189. self.server._BaseServer__shutdown_request = True
  190. # python 2.6
  191. self.server._BaseServer__serving = False
  192. def connection_dropped(self, error, environ=None):
  193. """Called if the connection was closed by the client. By default
  194. nothing happens.
  195. """
  196. def handle_one_request(self):
  197. """Handle a single HTTP request."""
  198. self.raw_requestline = self.rfile.readline()
  199. if not self.raw_requestline:
  200. self.close_connection = 1
  201. elif self.parse_request():
  202. return self.run_wsgi()
  203. def send_response(self, code, message=None):
  204. """Send the response header and log the response code."""
  205. self.log_request(code)
  206. if message is None:
  207. message = code in self.responses and self.responses[code][0] or ''
  208. if self.request_version != 'HTTP/0.9':
  209. hdr = "%s %d %s\r\n" % (self.protocol_version, code, message)
  210. self.wfile.write(hdr.encode('ascii'))
  211. def version_string(self):
  212. return BaseHTTPRequestHandler.version_string(self).strip()
  213. def address_string(self):
  214. return self.client_address[0]
  215. def log_request(self, code='-', size='-'):
  216. self.log('info', '"%s" %s %s', self.requestline, code, size)
  217. def log_error(self, *args):
  218. self.log('error', *args)
  219. def log_message(self, format, *args):
  220. self.log('info', format, *args)
  221. def log(self, type, message, *args):
  222. _log(type, '%s - - [%s] %s\n' % (self.address_string(),
  223. self.log_date_time_string(),
  224. message % args))
  225. #: backwards compatible name if someone is subclassing it
  226. BaseRequestHandler = WSGIRequestHandler
  227. def generate_adhoc_ssl_pair(cn=None):
  228. from random import random
  229. crypto = _get_openssl_crypto_module()
  230. # pretty damn sure that this is not actually accepted by anyone
  231. if cn is None:
  232. cn = '*'
  233. cert = crypto.X509()
  234. cert.set_serial_number(int(random() * sys.maxsize))
  235. cert.gmtime_adj_notBefore(0)
  236. cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
  237. subject = cert.get_subject()
  238. subject.CN = cn
  239. subject.O = 'Dummy Certificate'
  240. issuer = cert.get_issuer()
  241. issuer.CN = 'Untrusted Authority'
  242. issuer.O = 'Self-Signed'
  243. pkey = crypto.PKey()
  244. pkey.generate_key(crypto.TYPE_RSA, 1024)
  245. cert.set_pubkey(pkey)
  246. cert.sign(pkey, 'md5')
  247. return cert, pkey
  248. def make_ssl_devcert(base_path, host=None, cn=None):
  249. """Creates an SSL key for development. This should be used instead of
  250. the ``'adhoc'`` key which generates a new cert on each server start.
  251. It accepts a path for where it should store the key and cert and
  252. either a host or CN. If a host is given it will use the CN
  253. ``*.host/CN=host``.
  254. For more information see :func:`run_simple`.
  255. .. versionadded:: 0.9
  256. :param base_path: the path to the certificate and key. The extension
  257. ``.crt`` is added for the certificate, ``.key`` is
  258. added for the key.
  259. :param host: the name of the host. This can be used as an alternative
  260. for the `cn`.
  261. :param cn: the `CN` to use.
  262. """
  263. from OpenSSL import crypto
  264. if host is not None:
  265. cn = '*.%s/CN=%s' % (host, host)
  266. cert, pkey = generate_adhoc_ssl_pair(cn=cn)
  267. cert_file = base_path + '.crt'
  268. pkey_file = base_path + '.key'
  269. with open(cert_file, 'wb') as f:
  270. f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
  271. with open(pkey_file, 'wb') as f:
  272. f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
  273. return cert_file, pkey_file
  274. def generate_adhoc_ssl_context():
  275. """Generates an adhoc SSL context for the development server."""
  276. crypto = _get_openssl_crypto_module()
  277. import tempfile
  278. import atexit
  279. cert, pkey = generate_adhoc_ssl_pair()
  280. cert_handle, cert_file = tempfile.mkstemp()
  281. pkey_handle, pkey_file = tempfile.mkstemp()
  282. atexit.register(os.remove, pkey_file)
  283. atexit.register(os.remove, cert_file)
  284. os.write(cert_handle, crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
  285. os.write(pkey_handle, crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
  286. os.close(cert_handle)
  287. os.close(pkey_handle)
  288. ctx = load_ssl_context(cert_file, pkey_file)
  289. return ctx
  290. def load_ssl_context(cert_file, pkey_file=None, protocol=None):
  291. """Loads SSL context from cert/private key files and optional protocol.
  292. Many parameters are directly taken from the API of
  293. :py:class:`ssl.SSLContext`.
  294. :param cert_file: Path of the certificate to use.
  295. :param pkey_file: Path of the private key to use. If not given, the key
  296. will be obtained from the certificate file.
  297. :param protocol: One of the ``PROTOCOL_*`` constants in the stdlib ``ssl``
  298. module. Defaults to ``PROTOCOL_SSLv23``.
  299. """
  300. if protocol is None:
  301. protocol = ssl.PROTOCOL_SSLv23
  302. ctx = _SSLContext(protocol)
  303. ctx.load_cert_chain(cert_file, pkey_file)
  304. return ctx
  305. class _SSLContext(object):
  306. '''A dummy class with a small subset of Python3's ``ssl.SSLContext``, only
  307. intended to be used with and by Werkzeug.'''
  308. def __init__(self, protocol):
  309. self._protocol = protocol
  310. self._certfile = None
  311. self._keyfile = None
  312. self._password = None
  313. def load_cert_chain(self, certfile, keyfile=None, password=None):
  314. self._certfile = certfile
  315. self._keyfile = keyfile or certfile
  316. self._password = password
  317. def wrap_socket(self, sock, **kwargs):
  318. return ssl.wrap_socket(sock, keyfile=self._keyfile,
  319. certfile=self._certfile,
  320. ssl_version=self._protocol, **kwargs)
  321. def is_ssl_error(error=None):
  322. """Checks if the given error (or the current one) is an SSL error."""
  323. exc_types = (ssl.SSLError,)
  324. try:
  325. from OpenSSL.SSL import Error
  326. exc_types += (Error,)
  327. except ImportError:
  328. pass
  329. if error is None:
  330. error = sys.exc_info()[1]
  331. return isinstance(error, exc_types)
  332. def select_ip_version(host, port):
  333. """Returns AF_INET4 or AF_INET6 depending on where to connect to."""
  334. # disabled due to problems with current ipv6 implementations
  335. # and various operating systems. Probably this code also is
  336. # not supposed to work, but I can't come up with any other
  337. # ways to implement this.
  338. # try:
  339. # info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
  340. # socket.SOCK_STREAM, 0,
  341. # socket.AI_PASSIVE)
  342. # if info:
  343. # return info[0][0]
  344. # except socket.gaierror:
  345. # pass
  346. if ':' in host and hasattr(socket, 'AF_INET6'):
  347. return socket.AF_INET6
  348. return socket.AF_INET
  349. class BaseWSGIServer(HTTPServer, object):
  350. """Simple single-threaded, single-process WSGI server."""
  351. multithread = False
  352. multiprocess = False
  353. request_queue_size = 128
  354. def __init__(self, host, port, app, handler=None,
  355. passthrough_errors=False, ssl_context=None):
  356. if handler is None:
  357. handler = WSGIRequestHandler
  358. self.address_family = select_ip_version(host, port)
  359. HTTPServer.__init__(self, (host, int(port)), handler)
  360. self.app = app
  361. self.passthrough_errors = passthrough_errors
  362. self.shutdown_signal = False
  363. if ssl_context is not None:
  364. if isinstance(ssl_context, tuple):
  365. ssl_context = load_ssl_context(*ssl_context)
  366. if ssl_context == 'adhoc':
  367. ssl_context = generate_adhoc_ssl_context()
  368. self.socket = ssl_context.wrap_socket(self.socket,
  369. server_side=True)
  370. self.ssl_context = ssl_context
  371. else:
  372. self.ssl_context = None
  373. def log(self, type, message, *args):
  374. _log(type, message, *args)
  375. def serve_forever(self):
  376. self.shutdown_signal = False
  377. try:
  378. HTTPServer.serve_forever(self)
  379. except KeyboardInterrupt:
  380. pass
  381. finally:
  382. self.server_close()
  383. def handle_error(self, request, client_address):
  384. if self.passthrough_errors:
  385. raise
  386. else:
  387. return HTTPServer.handle_error(self, request, client_address)
  388. def get_request(self):
  389. con, info = self.socket.accept()
  390. return con, info
  391. class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
  392. """A WSGI server that does threading."""
  393. multithread = True
  394. class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
  395. """A WSGI server that does forking."""
  396. multiprocess = True
  397. def __init__(self, host, port, app, processes=40, handler=None,
  398. passthrough_errors=False, ssl_context=None):
  399. BaseWSGIServer.__init__(self, host, port, app, handler,
  400. passthrough_errors, ssl_context)
  401. self.max_children = processes
  402. def make_server(host, port, app=None, threaded=False, processes=1,
  403. request_handler=None, passthrough_errors=False,
  404. ssl_context=None):
  405. """Create a new server instance that is either threaded, or forks
  406. or just processes one request after another.
  407. """
  408. if threaded and processes > 1:
  409. raise ValueError("cannot have a multithreaded and "
  410. "multi process server.")
  411. elif threaded:
  412. return ThreadedWSGIServer(host, port, app, request_handler,
  413. passthrough_errors, ssl_context)
  414. elif processes > 1:
  415. return ForkingWSGIServer(host, port, app, processes, request_handler,
  416. passthrough_errors, ssl_context)
  417. else:
  418. return BaseWSGIServer(host, port, app, request_handler,
  419. passthrough_errors, ssl_context)
  420. def is_running_from_reloader():
  421. """Checks if the application is running from within the Werkzeug
  422. reloader subprocess.
  423. .. versionadded:: 0.10
  424. """
  425. return os.environ.get('WERKZEUG_RUN_MAIN') == 'true'
  426. def run_simple(hostname, port, application, use_reloader=False,
  427. use_debugger=False, use_evalex=True,
  428. extra_files=None, reloader_interval=1,
  429. reloader_type='auto', threaded=False, processes=1,
  430. request_handler=None, static_files=None,
  431. passthrough_errors=False, ssl_context=None):
  432. """Start a WSGI application. Optional features include a reloader,
  433. multithreading and fork support.
  434. This function has a command-line interface too::
  435. python -m werkzeug.serving --help
  436. .. versionadded:: 0.5
  437. `static_files` was added to simplify serving of static files as well
  438. as `passthrough_errors`.
  439. .. versionadded:: 0.6
  440. support for SSL was added.
  441. .. versionadded:: 0.8
  442. Added support for automatically loading a SSL context from certificate
  443. file and private key.
  444. .. versionadded:: 0.9
  445. Added command-line interface.
  446. .. versionadded:: 0.10
  447. Improved the reloader and added support for changing the backend
  448. through the `reloader_type` parameter. See :ref:`reloader`
  449. for more information.
  450. :param hostname: The host for the application. eg: ``'localhost'``
  451. :param port: The port for the server. eg: ``8080``
  452. :param application: the WSGI application to execute
  453. :param use_reloader: should the server automatically restart the python
  454. process if modules were changed?
  455. :param use_debugger: should the werkzeug debugging system be used?
  456. :param use_evalex: should the exception evaluation feature be enabled?
  457. :param extra_files: a list of files the reloader should watch
  458. additionally to the modules. For example configuration
  459. files.
  460. :param reloader_interval: the interval for the reloader in seconds.
  461. :param reloader_type: the type of reloader to use. The default is
  462. auto detection. Valid values are ``'stat'`` and
  463. ``'watchdog'``. See :ref:`reloader` for more
  464. information.
  465. :param threaded: should the process handle each request in a separate
  466. thread?
  467. :param processes: if greater than 1 then handle each request in a new process
  468. up to this maximum number of concurrent processes.
  469. :param request_handler: optional parameter that can be used to replace
  470. the default one. You can use this to replace it
  471. with a different
  472. :class:`~BaseHTTPServer.BaseHTTPRequestHandler`
  473. subclass.
  474. :param static_files: a dict of paths for static files. This works exactly
  475. like :class:`SharedDataMiddleware`, it's actually
  476. just wrapping the application in that middleware before
  477. serving.
  478. :param passthrough_errors: set this to `True` to disable the error catching.
  479. This means that the server will die on errors but
  480. it can be useful to hook debuggers in (pdb etc.)
  481. :param ssl_context: an SSL context for the connection. Either an
  482. :class:`ssl.SSLContext`, a tuple in the form
  483. ``(cert_file, pkey_file)``, the string ``'adhoc'`` if
  484. the server should automatically create one, or ``None``
  485. to disable SSL (which is the default).
  486. """
  487. if use_debugger:
  488. from werkzeug.debug import DebuggedApplication
  489. application = DebuggedApplication(application, use_evalex)
  490. if static_files:
  491. from werkzeug.wsgi import SharedDataMiddleware
  492. application = SharedDataMiddleware(application, static_files)
  493. def inner():
  494. make_server(hostname, port, application, threaded,
  495. processes, request_handler,
  496. passthrough_errors, ssl_context).serve_forever()
  497. if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
  498. display_hostname = hostname != '*' and hostname or 'localhost'
  499. if ':' in display_hostname:
  500. display_hostname = '[%s]' % display_hostname
  501. quit_msg = '(Press CTRL+C to quit)'
  502. _log('info', ' * Running on %s://%s:%d/ %s', ssl_context is None
  503. and 'http' or 'https', display_hostname, port, quit_msg)
  504. if use_reloader:
  505. # Create and destroy a socket so that any exceptions are raised before
  506. # we spawn a separate Python interpreter and lose this ability.
  507. address_family = select_ip_version(hostname, port)
  508. test_socket = socket.socket(address_family, socket.SOCK_STREAM)
  509. test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  510. test_socket.bind((hostname, port))
  511. test_socket.close()
  512. from ._reloader import run_with_reloader
  513. run_with_reloader(inner, extra_files, reloader_interval,
  514. reloader_type)
  515. else:
  516. inner()
  517. def run_with_reloader(*args, **kwargs):
  518. # People keep using undocumented APIs. Do not use this function
  519. # please, we do not guarantee that it continues working.
  520. from ._reloader import run_with_reloader
  521. return run_with_reloader(*args, **kwargs)
  522. def main():
  523. '''A simple command-line interface for :py:func:`run_simple`.'''
  524. # in contrast to argparse, this works at least under Python < 2.7
  525. import optparse
  526. from werkzeug.utils import import_string
  527. parser = optparse.OptionParser(
  528. usage='Usage: %prog [options] app_module:app_object')
  529. parser.add_option('-b', '--bind', dest='address',
  530. help='The hostname:port the app should listen on.')
  531. parser.add_option('-d', '--debug', dest='use_debugger',
  532. action='store_true', default=False,
  533. help='Use Werkzeug\'s debugger.')
  534. parser.add_option('-r', '--reload', dest='use_reloader',
  535. action='store_true', default=False,
  536. help='Reload Python process if modules change.')
  537. options, args = parser.parse_args()
  538. hostname, port = None, None
  539. if options.address:
  540. address = options.address.split(':')
  541. hostname = address[0]
  542. if len(address) > 1:
  543. port = address[1]
  544. if len(args) != 1:
  545. sys.stdout.write('No application supplied, or too much. See --help\n')
  546. sys.exit(1)
  547. app = import_string(args[0])
  548. run_simple(
  549. hostname=(hostname or '127.0.0.1'), port=int(port or 5000),
  550. application=app, use_reloader=options.use_reloader,
  551. use_debugger=options.use_debugger
  552. )
  553. if __name__ == '__main__':
  554. main()