app.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #!/usr/bin/env python
  2. import os
  3. import sys
  4. import falcon
  5. from db_orm import session, World, Fortune
  6. from helpers import load_template, FortuneTuple, generate_ids, sanitize
  7. from operator import attrgetter
  8. from random import randint
  9. from email.utils import formatdate
  10. # ------------------------------------------------------------------
  11. # setup
  12. wsgi = app = falcon.App()
  13. response_server = "Falcon"
  14. response_add_date = False
  15. def add_ext_headers(response):
  16. if response_server:
  17. response.set_header('Server', response_server)
  18. if response_add_date:
  19. response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
  20. if os.getenv('USE_ORJSON', "0") == "1":
  21. import orjson
  22. # custom JSON handler
  23. JSONHandler = falcon.media.JSONHandler(dumps=orjson.dumps, loads=orjson.loads)
  24. extra_handlers = {
  25. "application/json": JSONHandler,
  26. "application/json; charset=UTF-8": JSONHandler
  27. }
  28. wsgi.req_options.media_handlers.update(extra_handlers)
  29. wsgi.resp_options.media_handlers.update(extra_handlers)
  30. # ------------------------------------------------------------------
  31. # resource endpoints
  32. class JSONResource(object):
  33. def on_get(self, request, response):
  34. add_ext_headers(response)
  35. response.media = {'message': "Hello, world!"}
  36. class SingleQuery(object):
  37. @session(serializable=False)
  38. def on_get(self, request, response):
  39. wid = randint(1, 10000)
  40. world = World[wid]
  41. add_ext_headers(response)
  42. response.media = world.to_dict()
  43. class MultipleQueries(object):
  44. @session(serializable=False)
  45. def on_get(self, request, response, num):
  46. num = sanitize(num)
  47. worlds = [World[ident].to_dict() for ident in generate_ids(num)]
  48. add_ext_headers(response)
  49. response.media = worlds
  50. class UpdateQueries(object):
  51. @session(serializable=False)
  52. def on_get(self, request, response, num):
  53. num = sanitize(num)
  54. ids = generate_ids(num)
  55. ids.sort()
  56. worlds = []
  57. for item in ids:
  58. world = World[item]
  59. world.randomNumber = randint(1, 10000)
  60. worlds.append({"id": world.id, "randomNumber": world.randomNumber})
  61. add_ext_headers(response)
  62. response.media = worlds
  63. class Fortunes(object):
  64. _template = load_template()
  65. @session(serializable=False)
  66. def on_get(self, request, response):
  67. fortunes = [FortuneTuple(id=f.id, message=f.message) for f in Fortune.select()]
  68. fortunes.append(FortuneTuple(id=0, message="Additional fortune added at request time."))
  69. fortunes.sort(key=attrgetter("message"))
  70. content = self._template.render(fortunes=fortunes)
  71. add_ext_headers(response)
  72. response.content_type = falcon.MEDIA_HTML
  73. response.text = content
  74. class PlaintextResource(object):
  75. def on_get(self, request, response):
  76. add_ext_headers(response)
  77. response.content_type = falcon.MEDIA_TEXT
  78. response.text = 'Hello, world!'
  79. # register resources
  80. app.add_route("/json", JSONResource())
  81. app.add_route("/db", SingleQuery())
  82. app.add_route("/queries/{num}", MultipleQueries())
  83. app.add_route("/updates/{num}", UpdateQueries())
  84. app.add_route("/fortunes", Fortunes())
  85. app.add_route("/plaintext", PlaintextResource())
  86. # ------------------------------------------------------------------
  87. if __name__ == "__main__":
  88. import optparse
  89. import multiprocessing
  90. import logging
  91. import re
  92. parser = optparse.OptionParser("usage: %prog [options]", add_help_option=False)
  93. parser.add_option("-h", "--host", dest="host", default='0.0.0.0', type="string")
  94. parser.add_option("-p", "--port", dest="port", default=8080, type="int")
  95. parser.add_option("-s", "--server", dest="server", default="gunicorn", type="string")
  96. parser.add_option("-w", "--workers", dest="workers", default=0, type="int")
  97. parser.add_option("-k", "--keepalive", dest="keepalive", default=60, type="int")
  98. parser.add_option("-v", "--verbose", dest="verbose", default=0, type="int")
  99. (opt, args) = parser.parse_args()
  100. _is_travis = os.environ.get('TRAVIS') == 'true'
  101. workers = opt.workers
  102. if workers <= 0:
  103. workers = int(multiprocessing.cpu_count())
  104. if _is_travis:
  105. workers = 2
  106. if opt.server == 'waitress':
  107. import waitress
  108. response_server = None
  109. response_add_date = False
  110. logging.basicConfig()
  111. logging.getLogger().setLevel(logging.CRITICAL)
  112. logging.disable(True)
  113. if workers < 4:
  114. workers = 4
  115. waitress.serve(
  116. app=wsgi,
  117. listen=f"{opt.host}:{opt.port}",
  118. log_socket_errors=False,
  119. threads=workers,
  120. asyncore_use_poll=True,
  121. expose_tracebacks=False,
  122. connection_limit=128,
  123. channel_timeout=opt.keepalive,
  124. _quiet=True)
  125. sys.exit(0)
  126. def run_app():
  127. global response_server
  128. global response_add_date
  129. if opt.server == 'bjoern':
  130. import bjoern
  131. # Note:
  132. # Bjoern doesn't provide any additional response headers like Date and Server
  133. # so we need to provide them manually.
  134. bjoern_version = [i for i in open('requirements-bjoern.txt', 'r') if re.search('bjoern', i)][0].strip().split('==')
  135. response_server = '{}/{}'.format(*bjoern_version).title()
  136. response_add_date = True
  137. bjoern.run(app, host=opt.host, port=opt.port, reuse_port=True)
  138. if opt.server == 'fastwsgi':
  139. import fastwsgi
  140. response_server = "FastWSGI"
  141. response_add_date = False
  142. fastwsgi.server.backlog = 4096
  143. fastwsgi.run(app, host=opt.host, port=opt.port, loglevel=opt.verbose)
  144. if opt.server == 'socketify':
  145. import socketify
  146. response_server = None
  147. response_add_date = False
  148. socketify.WSGI(app).listen(opt.port, lambda config: logging.info(f"Listening on port http://localhost:{opt.port} now\n")).run()
  149. def create_fork():
  150. n = os.fork()
  151. # n greater than 0 means parent process
  152. if not n > 0:
  153. run_app()
  154. # fork limiting the cpu count - 1
  155. for i in range(1, workers):
  156. create_fork()
  157. run_app() # run app on the main process too :)