request-tracker.py (6469B)
1 import json 2 import mimetypes 3 import os 4 5 from fledge.tentative.resources import fledge_http_server_util 6 import wptserve.stash 7 from wptserve.utils import isomorphic_decode, isomorphic_encode 8 9 10 # Test server that tracks requests it has previously seen, keyed by a token. 11 # 12 # All requests have a "dispatch" field indicating what to do, and a "uuid" 13 # field which should be unique for each test, to avoid tests that fail to 14 # clean up after themselves, or that are running concurrently, from interfering 15 # with other tests. 16 # 17 # Each uuid has a stash entry with a dictionary with the following entries: 18 # "trackedRequests" is a list of all observed requested URLs with a 19 # dispatch of "track_get" or "track_post". POSTS are in the format 20 # "<url>, body: <body>". 21 # "trackedHeaders" is an object mapping HTTP header names to lists 22 # of received HTTP header values for a single request with a 23 # dispatch of "track_headers". 24 # "errors" is a list of an errors that occurred. 25 # 26 # A dispatch of "tracked_data" will return all tracked information associated 27 # with the uuid, as a JSON string. The "errors" field should be checked by 28 # the caller before checking other fields. 29 # 30 # A dispatch of "clean_up" will delete all information associated with the uuid. 31 def main(request, response): 32 # Don't cache responses, since tests expect duplicate requests to always 33 # reach the server. 34 response.headers.set(b"Cache-Control", b"no-store") 35 36 dispatch = request.GET.first(b"dispatch", None) 37 uuid = request.GET.first(b"uuid", None) 38 39 # If we're used as a trusted scoring signals handler, our params are 40 # smuggled in via renderURLs. We won't have dispatch and uuid provided 41 # directly then. 42 if dispatch is None and uuid is None: 43 try: 44 signals_params = fledge_http_server_util.decode_trusted_scoring_signals_params(request) 45 for urlList in signals_params.urlLists: 46 for renderUrl in urlList["urls"]: 47 try: 48 signalsParams = fledge_http_server_util.decode_render_url_signals_params(renderUrl) 49 except ValueError as ve: 50 return simple_response(request, response, 500, 51 b"InternalError", str(ve)) 52 for signalsParam in signalsParams: 53 if signalsParam.startswith("dispatch:"): 54 dispatch = signalsParam.split(':', 1)[1].encode("utf-8") 55 elif signalsParam.startswith("uuid:"): 56 uuid = signalsParam.split(':', 1)[1].encode("utf-8") 57 except ValueError: 58 # It doesn't look like a trusted scoring signals request, so 59 # never mind. 60 pass 61 62 if not uuid or not dispatch: 63 return simple_response(request, response, 404, b"Not found", 64 b"Invalid query parameters") 65 66 stash = request.server.stash 67 with stash.lock: 68 # Take ownership of stashed entry, if any. This removes the entry of the 69 # stash. 70 server_state = stash.take(uuid) or {"trackedRequests": [], "errors": [], "trackedHeaders": None} 71 72 # Clear the entire stash. No work to do, since stash entry was already 73 # removed. 74 if dispatch == b"clean_up": 75 return simple_response(request, response, 200, b"OK", 76 b"cleanup complete") 77 78 # Return the list of entries in the stash. Need to add data back to the 79 # stash first. 80 if dispatch == b"tracked_data": 81 stash.put(uuid, server_state) 82 return simple_response(request, response, 200, b"OK", 83 json.dumps(server_state)) 84 85 # Tracks a request that's expected to be a GET. 86 if dispatch == b"track_get": 87 if request.method != "GET": 88 server_state["errors"].append( 89 request.url + " has wrong method: " + request.method) 90 else: 91 server_state["trackedRequests"].append(request.url) 92 93 stash.put(uuid, server_state) 94 return simple_response(request, response, 200, b"OK", b"") 95 96 # Tracks a request that's expected to be a POST. 97 # In addition to the method, check the Content-Type, which is currently 98 # always text/plain. The request body is stored in trackedRequests. 99 if dispatch == b"track_post": 100 contentType = request.headers.get(b"Content-Type", b"missing") 101 if request.method != "POST": 102 server_state["errors"].append( 103 request.url + " has wrong method: " + request.method) 104 elif not contentType.startswith(b"text/plain"): 105 server_state["errors"].append( 106 request.url + " has wrong Content-Type: " + 107 contentType.decode("utf-8")) 108 else: 109 server_state["trackedRequests"].append( 110 request.url + ", body: " + request.body.decode("utf-8")) 111 stash.put(uuid, server_state) 112 return simple_response(request, response, 200, b"OK", b"") 113 114 # Tracks request headers for a single request. 115 if dispatch == b"track_headers": 116 if server_state["trackedHeaders"] != None: 117 server_state["errors"].append("Second track_headers request received.") 118 else: 119 server_state["trackedHeaders"] = fledge_http_server_util.headers_to_ascii(request.headers) 120 121 stash.put(uuid, server_state) 122 return simple_response(request, response, 200, b"OK", b"") 123 124 # Report unrecognized dispatch line. 125 server_state["errors"].append( 126 request.url + " request with unknown dispatch value received: " + 127 dispatch.decode("utf-8")) 128 stash.put(uuid, server_state) 129 return simple_response(request, response, 404, b"Not Found", 130 b"Unrecognized dispatch parameter: " + dispatch) 131 132 def simple_response(request, response, status_code, status_message, body, 133 content_type=b"text/plain"): 134 response.status = (status_code, status_message) 135 response.headers.set(b"Content-Type", content_type) 136 # Force refetch on reuse, so multiple requests to tracked URLs are all visible. 137 response.headers.set(b"Cache-control", b"no-store") 138 return body