app.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import asyncio
  2. import asyncpg
  3. import jinja2
  4. import os
  5. import ujson
  6. from random import randint, sample
  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", "id" 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. def get_num_queries(scope):
  51. try:
  52. query_string = scope['query_string']
  53. query_count = int(parse_qs(query_string)[b'queries'][0])
  54. except (KeyError, IndexError, ValueError):
  55. return 1
  56. if query_count < 1:
  57. return 1
  58. if query_count > 500:
  59. return 500
  60. return query_count
  61. async def json_serialization(scope, receive, send):
  62. """
  63. Test type 1: JSON Serialization
  64. """
  65. content = json_dumps({'message': 'Hello, world!'}).encode('utf-8')
  66. await send(JSON_RESPONSE)
  67. await send({
  68. 'type': 'http.response.body',
  69. 'body': content,
  70. 'more_body': False
  71. })
  72. async def single_database_query(scope, receive, send):
  73. """
  74. Test type 2: Single database object
  75. """
  76. row_id = randint(1, 10000)
  77. connection = await pool.acquire()
  78. try:
  79. number = await connection.fetchval(READ_ROW_SQL, row_id)
  80. world = {'id': row_id, 'randomNumber': number}
  81. finally:
  82. await pool.release(connection)
  83. content = json_dumps(world).encode('utf-8')
  84. await send(JSON_RESPONSE)
  85. await send({
  86. 'type': 'http.response.body',
  87. 'body': content,
  88. 'more_body': False
  89. })
  90. async def multiple_database_queries(scope, receive, send):
  91. """
  92. Test type 3: Multiple database queries
  93. """
  94. num_queries = get_num_queries(scope)
  95. row_ids = sample(range(1, 10000), num_queries)
  96. worlds = []
  97. connection = await pool.acquire()
  98. try:
  99. statement = await connection.prepare(READ_ROW_SQL)
  100. for row_id in row_ids:
  101. number = await statement.fetchval(row_id)
  102. worlds.append({'id': row_id, 'randomNumber': number})
  103. finally:
  104. await pool.release(connection)
  105. content = json_dumps(worlds).encode('utf-8')
  106. await send(JSON_RESPONSE)
  107. await send({
  108. 'type': 'http.response.body',
  109. 'body': content,
  110. 'more_body': False
  111. })
  112. async def fortunes(scope, receive, send):
  113. """
  114. Test type 4: Fortunes
  115. """
  116. connection = await pool.acquire()
  117. try:
  118. fortunes = await connection.fetch('SELECT * FROM Fortune')
  119. finally:
  120. await pool.release(connection)
  121. fortunes.append(ADDITIONAL_ROW)
  122. fortunes.sort(key=key)
  123. content = template.render(fortunes=fortunes).encode('utf-8')
  124. await send(HTML_RESPONSE)
  125. await send({
  126. 'type': 'http.response.body',
  127. 'body': content,
  128. 'more_body': False
  129. })
  130. async def database_updates(scope, receive, send):
  131. """
  132. Test type 5: Database updates
  133. """
  134. num_queries = get_num_queries(scope)
  135. updates = [(row_id, randint(1, 10000)) for row_id in sample(range(1, 10000), num_queries)]
  136. worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
  137. connection = await pool.acquire()
  138. try:
  139. statement = await connection.prepare(READ_ROW_SQL)
  140. for row_id, _ in updates:
  141. await statement.fetchval(row_id)
  142. await connection.executemany(WRITE_ROW_SQL, updates)
  143. finally:
  144. await pool.release(connection)
  145. content = json_dumps(worlds).encode('utf-8')
  146. await send(JSON_RESPONSE)
  147. await send({
  148. 'type': 'http.response.body',
  149. 'body': content,
  150. 'more_body': False
  151. })
  152. async def plaintext(scope, receive, send):
  153. """
  154. Test type 6: Plaintext
  155. """
  156. content = b'Hello, world!'
  157. await send(PLAINTEXT_RESPONSE)
  158. await send({
  159. 'type': 'http.response.body',
  160. 'body': content,
  161. 'more_body': False
  162. })
  163. async def handle_404(scope, receive, send):
  164. content = b'Not found'
  165. await send(PLAINTEXT_RESPONSE)
  166. await send({
  167. 'type': 'http.response.body',
  168. 'body': content,
  169. 'more_body': False
  170. })
  171. routes = {
  172. '/json': json_serialization,
  173. '/db': single_database_query,
  174. '/queries': multiple_database_queries,
  175. '/fortunes': fortunes,
  176. '/updates': database_updates,
  177. '/plaintext': plaintext,
  178. }
  179. async def main(scope, receive, send):
  180. if scope['type'] == 'lifespan':
  181. message = await receive()
  182. if message['type'] == 'lifespan.startup':
  183. await setup()
  184. await send({'type': 'lifespan.startup.complete'})
  185. path = scope['path']
  186. handler = routes.get(path, handle_404)
  187. await handler(scope, receive, send)