app.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import asyncio
  2. import asyncpg
  3. import jinja2
  4. import os
  5. import ujson
  6. from random import randint
  7. from operator import itemgetter
  8. from urllib.parse import parse_qs
  9. async def setup():
  10. global pool
  11. pool = await asyncpg.create_pool(
  12. user=os.getenv('PGUSER', 'benchmarkdbuser'),
  13. password=os.getenv('PGPASS', 'benchmarkdbpass'),
  14. database='hello_world',
  15. host='tfb-database',
  16. port=5432
  17. )
  18. READ_ROW_SQL = 'SELECT "randomnumber" FROM "world" WHERE id = $1'
  19. WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
  20. ADDITIONAL_ROW = [0, 'Additional fortune added at request time.']
  21. JSON_RESPONSE = {
  22. 'type': 'http.response.start',
  23. 'status': 200,
  24. 'headers': [
  25. [b'content-type', b'application/json'],
  26. ]
  27. }
  28. HTML_RESPONSE = {
  29. 'type': 'http.response.start',
  30. 'status': 200,
  31. 'headers': [
  32. [b'content-type', b'text/html; charset=utf-8'],
  33. ]
  34. }
  35. PLAINTEXT_RESPONSE = {
  36. 'type': 'http.response.start',
  37. 'status': 200,
  38. 'headers': [
  39. [b'content-type', b'text/plain; charset=utf-8'],
  40. ]
  41. }
  42. pool = None
  43. key = itemgetter(1)
  44. json_dumps = ujson.dumps
  45. template = None
  46. path = os.path.join('templates', 'fortune.html')
  47. with open(path, 'r') as template_file:
  48. template_text = template_file.read()
  49. template = jinja2.Template(template_text)
  50. loop = asyncio.get_event_loop()
  51. loop.run_until_complete(setup())
  52. def get_num_queries(scope):
  53. try:
  54. query_string = scope['query_string']
  55. query_count = int(parse_qs(query_string)[b'queries'][0])
  56. except (KeyError, IndexError, ValueError):
  57. return 1
  58. if query_count < 1:
  59. return 1
  60. if query_count > 500:
  61. return 500
  62. return query_count
  63. class JSONSerialization:
  64. """
  65. Test type 1: JSON Serialization
  66. """
  67. def __init__(self, scope):
  68. pass
  69. async def __call__(self, receive, send):
  70. content = json_dumps({'message': 'Hello, world!'}).encode('utf-8')
  71. await send(JSON_RESPONSE)
  72. await send({
  73. 'type': 'http.response.body',
  74. 'body': content,
  75. 'more_body': False
  76. })
  77. class SingleDatabaseQuery:
  78. """
  79. Test type 2: Single database object
  80. """
  81. def __init__(self, scope):
  82. pass
  83. async def __call__(self, receive, send):
  84. row_id = randint(1, 10000)
  85. connection = await pool.acquire()
  86. try:
  87. number = await connection.fetchval(READ_ROW_SQL, row_id)
  88. world = {'id': row_id, 'randomNumber': number}
  89. finally:
  90. await pool.release(connection)
  91. content = json_dumps(world).encode('utf-8')
  92. await send(JSON_RESPONSE)
  93. await send({
  94. 'type': 'http.response.body',
  95. 'body': content,
  96. 'more_body': False
  97. })
  98. class MultipleDatabaseQueries:
  99. """
  100. Test type 3: Multiple database queries
  101. """
  102. def __init__(self, scope):
  103. self.scope = scope
  104. async def __call__(self, receive, send):
  105. num_queries = get_num_queries(self.scope)
  106. row_ids = [randint(1, 10000) for _ in range(num_queries)]
  107. worlds = []
  108. connection = await pool.acquire()
  109. try:
  110. statement = await connection.prepare(READ_ROW_SQL)
  111. for row_id in row_ids:
  112. number = await statement.fetchval(row_id)
  113. worlds.append({'id': row_id, 'randomNumber': number})
  114. finally:
  115. await pool.release(connection)
  116. content = json_dumps(worlds).encode('utf-8')
  117. await send(JSON_RESPONSE)
  118. await send({
  119. 'type': 'http.response.body',
  120. 'body': content,
  121. 'more_body': False
  122. })
  123. class Fortunes:
  124. """
  125. Test type 4: Fortunes
  126. """
  127. def __init__(self, scope):
  128. pass
  129. async def __call__(self, receive, send):
  130. connection = await pool.acquire()
  131. try:
  132. fortunes = await connection.fetch('SELECT * FROM Fortune')
  133. finally:
  134. await pool.release(connection)
  135. fortunes.append(ADDITIONAL_ROW)
  136. fortunes.sort(key=key)
  137. content = template.render(fortunes=fortunes).encode('utf-8')
  138. await send(HTML_RESPONSE)
  139. await send({
  140. 'type': 'http.response.body',
  141. 'body': content,
  142. 'more_body': False
  143. })
  144. class DatabaseUpdates:
  145. """
  146. Test type 5: Database updates
  147. """
  148. def __init__(self, scope):
  149. self.scope = scope
  150. async def __call__(self, receive, send):
  151. num_queries = get_num_queries(self.scope)
  152. updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
  153. worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
  154. connection = await pool.acquire()
  155. try:
  156. statement = await connection.prepare(READ_ROW_SQL)
  157. for row_id, _ in updates:
  158. await statement.fetchval(row_id)
  159. await connection.executemany(WRITE_ROW_SQL, updates)
  160. finally:
  161. await pool.release(connection)
  162. content = json_dumps(worlds).encode('utf-8')
  163. await send(JSON_RESPONSE)
  164. await send({
  165. 'type': 'http.response.body',
  166. 'body': content,
  167. 'more_body': False
  168. })
  169. class Plaintext:
  170. """
  171. Test type 6: Plaintext
  172. """
  173. def __init__(self, scope):
  174. pass
  175. async def __call__(self, receive, send):
  176. content = b'Hello, world!'
  177. await send(PLAINTEXT_RESPONSE)
  178. await send({
  179. 'type': 'http.response.body',
  180. 'body': content,
  181. 'more_body': False
  182. })
  183. class Handle404:
  184. def __init__(self, scope):
  185. pass
  186. async def __call__(self, receive, send):
  187. content = b'Not found'
  188. await send(PLAINTEXT_RESPONSE)
  189. await send({
  190. 'type': 'http.response.body',
  191. 'body': content,
  192. 'more_body': False
  193. })
  194. routes = {
  195. '/json': JSONSerialization,
  196. '/db': SingleDatabaseQuery,
  197. '/queries': MultipleDatabaseQueries,
  198. '/fortunes': Fortunes,
  199. '/updates': DatabaseUpdates,
  200. '/plaintext': Plaintext,
  201. }
  202. def main(scope):
  203. path = scope['path']
  204. return routes.get(path, Handle404)(scope)