app.py 5.4 KB

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