OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 """ | 3 """ |
4 Copyright 2013 Google Inc. | 4 Copyright 2013 Google Inc. |
5 | 5 |
6 Use of this source code is governed by a BSD-style license that can be | 6 Use of this source code is governed by a BSD-style license that can be |
7 found in the LICENSE file. | 7 found in the LICENSE file. |
8 | 8 |
9 HTTP server for our HTML rebaseline viewer. | 9 HTTP server for our HTML rebaseline viewer. |
10 """ | 10 """ |
(...skipping 30 matching lines...) Expand all Loading... |
41 # Server.results() property. See discussion at | 41 # Server.results() property. See discussion at |
42 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p
y#newcode44 | 42 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p
y#newcode44 |
43 # pylint: enable=C0301 | 43 # pylint: enable=C0301 |
44 import compare_configs | 44 import compare_configs |
45 import compare_rendered_pictures | 45 import compare_rendered_pictures |
46 import compare_to_expectations | 46 import compare_to_expectations |
47 import download_actuals | 47 import download_actuals |
48 import imagediffdb | 48 import imagediffdb |
49 import imagepairset | 49 import imagepairset |
50 import results as results_mod | 50 import results as results_mod |
| 51 import writable_expectations as writable_expectations_mod |
| 52 |
51 | 53 |
52 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') | 54 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') |
53 | 55 |
54 # A simple dictionary of file name extensions to MIME types. The empty string | 56 # A simple dictionary of file name extensions to MIME types. The empty string |
55 # entry is used as the default when no extension was given or if the extension | 57 # entry is used as the default when no extension was given or if the extension |
56 # has no entry in this dictionary. | 58 # has no entry in this dictionary. |
57 MIME_TYPE_MAP = {'': 'application/octet-stream', | 59 MIME_TYPE_MAP = {'': 'application/octet-stream', |
58 'html': 'text/html', | 60 'html': 'text/html', |
59 'css': 'text/css', | 61 'css': 'text/css', |
60 'png': 'image/png', | 62 'png': 'image/png', |
61 'js': 'application/javascript', | 63 'js': 'application/javascript', |
62 'json': 'application/json' | 64 'json': 'application/json' |
63 } | 65 } |
64 | 66 |
65 # Keys that server.py uses to create the toplevel content header. | 67 # Keys that server.py uses to create the toplevel content header. |
66 # NOTE: Keep these in sync with static/constants.js | 68 # NOTE: Keep these in sync with static/constants.js |
67 KEY__EDITS__MODIFICATIONS = 'modifications' | 69 KEY__EDITS__MODIFICATIONS = 'modifications' |
68 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' | 70 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' |
69 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' | 71 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' |
| 72 KEY__LIVE_EDITS__MODIFICATIONS = 'modifications' |
| 73 KEY__LIVE_EDITS__SET_A_DESCRIPTIONS = 'setA' |
| 74 KEY__LIVE_EDITS__SET_B_DESCRIPTIONS = 'setB' |
70 | 75 |
71 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR | 76 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR |
72 DEFAULT_GM_SUMMARIES_BUCKET = download_actuals.GM_SUMMARIES_BUCKET | 77 DEFAULT_GM_SUMMARIES_BUCKET = download_actuals.GM_SUMMARIES_BUCKET |
73 DEFAULT_JSON_FILENAME = download_actuals.DEFAULT_JSON_FILENAME | 78 DEFAULT_JSON_FILENAME = download_actuals.DEFAULT_JSON_FILENAME |
74 DEFAULT_PORT = 8888 | 79 DEFAULT_PORT = 8888 |
75 | 80 |
76 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) | 81 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) |
77 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) | 82 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) |
78 | 83 |
79 # Directory, relative to PARENT_DIRECTORY, within which the server will serve | 84 # Directory, relative to PARENT_DIRECTORY, within which the server will serve |
(...skipping 598 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
678 def do_POST(self): | 683 def do_POST(self): |
679 """ Handles all POST requests, forwarding them to the appropriate | 684 """ Handles all POST requests, forwarding them to the appropriate |
680 do_POST_* dispatcher. """ | 685 do_POST_* dispatcher. """ |
681 # All requests must be of this form: | 686 # All requests must be of this form: |
682 # /dispatcher | 687 # /dispatcher |
683 # where 'dispatcher' indicates which do_POST_* dispatcher to run. | 688 # where 'dispatcher' indicates which do_POST_* dispatcher to run. |
684 logging.debug('do_POST: path="%s"' % self.path) | 689 logging.debug('do_POST: path="%s"' % self.path) |
685 normpath = posixpath.normpath(self.path) | 690 normpath = posixpath.normpath(self.path) |
686 dispatchers = { | 691 dispatchers = { |
687 '/edits': self.do_POST_edits, | 692 '/edits': self.do_POST_edits, |
| 693 '/live-edits': self.do_POST_live_edits, |
688 } | 694 } |
689 try: | 695 try: |
690 dispatcher = dispatchers[normpath] | 696 dispatcher = dispatchers[normpath] |
691 dispatcher() | 697 dispatcher() |
692 self.send_response(200) | |
693 except: | 698 except: |
694 self.send_error(404) | 699 self.send_error(404) |
695 raise | 700 raise |
696 | 701 |
697 def do_POST_edits(self): | 702 def do_POST_edits(self): |
698 """ Handle a POST request with modifications to GM expectations, in this | 703 """ Handle a POST request with modifications to GM expectations, in this |
699 format: | 704 format: |
700 | 705 |
701 { | 706 { |
702 KEY__EDITS__OLD_RESULTS_TYPE: 'all', # type of results that the client | 707 KEY__EDITS__OLD_RESULTS_TYPE: 'all', # type of results that the client |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
742 raise Exception('results of type "%s" changed while the client was ' | 747 raise Exception('results of type "%s" changed while the client was ' |
743 'making modifications. The client should reload the ' | 748 'making modifications. The client should reload the ' |
744 'results and submit the modifications again.' % | 749 'results and submit the modifications again.' % |
745 oldResultsType) | 750 oldResultsType) |
746 _SERVER.results.edit_expectations(data[KEY__EDITS__MODIFICATIONS]) | 751 _SERVER.results.edit_expectations(data[KEY__EDITS__MODIFICATIONS]) |
747 | 752 |
748 # Read the updated results back from disk. | 753 # Read the updated results back from disk. |
749 # We can do this in a separate thread; we should return our success message | 754 # We can do this in a separate thread; we should return our success message |
750 # to the UI as soon as possible. | 755 # to the UI as soon as possible. |
751 thread.start_new_thread(_SERVER.update_results, (True,)) | 756 thread.start_new_thread(_SERVER.update_results, (True,)) |
| 757 self.send_response(200) |
| 758 |
| 759 def do_POST_live_edits(self): |
| 760 """ Handle a POST request with modifications to SKP expectations, in this |
| 761 format: |
| 762 |
| 763 { |
| 764 KEY__LIVE_EDITS__SET_A_DESCRIPTIONS: { |
| 765 # setA descriptions from the original data |
| 766 }, |
| 767 KEY__LIVE_EDITS__SET_B_DESCRIPTIONS: { |
| 768 # setB descriptions from the original data |
| 769 }, |
| 770 KEY__LIVE_EDITS__MODIFICATIONS: [ |
| 771 # as needed by writable_expectations.modify() |
| 772 ], |
| 773 } |
| 774 |
| 775 Raises an Exception if there were any problems. |
| 776 """ |
| 777 content_type = self.headers[_HTTP_HEADER_CONTENT_TYPE] |
| 778 if content_type != 'application/json;charset=UTF-8': |
| 779 raise Exception('unsupported %s [%s]' % ( |
| 780 _HTTP_HEADER_CONTENT_TYPE, content_type)) |
| 781 |
| 782 content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH]) |
| 783 json_data = self.rfile.read(content_length) |
| 784 data = json.loads(json_data) |
| 785 logging.debug('do_POST_live_edits: received new GM expectations data [%s]' % |
| 786 data) |
| 787 with writable_expectations_mod.WritableExpectations( |
| 788 data[KEY__LIVE_EDITS__SET_A_DESCRIPTIONS]) as writable_expectations: |
| 789 writable_expectations.modify(data[KEY__LIVE_EDITS__MODIFICATIONS]) |
| 790 diffs = writable_expectations.get_diffs() |
| 791 # TODO(stephana): Move to a simpler web framework so we don't have to |
| 792 # call these functions. See http://skbug.com/2856 ('rebaseline_server: |
| 793 # Refactor server to use a simple web framework') |
| 794 self.send_response(200) |
| 795 self.send_header('Content-type', 'text/plain') |
| 796 self.end_headers() |
| 797 self.wfile.write(diffs) |
752 | 798 |
753 def redirect_to(self, url): | 799 def redirect_to(self, url): |
754 """ Redirect the HTTP client to a different url. | 800 """ Redirect the HTTP client to a different url. |
755 | 801 |
756 Args: | 802 Args: |
757 url: URL to redirect the HTTP client to | 803 url: URL to redirect the HTTP client to |
758 """ | 804 """ |
759 self.send_response(301) | 805 self.send_response(301) |
760 self.send_header('Location', url) | 806 self.send_header('Location', url) |
761 self.end_headers() | 807 self.end_headers() |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
898 reload_seconds=args.reload, config_pairs=config_pairs, | 944 reload_seconds=args.reload, config_pairs=config_pairs, |
899 builder_regex_list=args.builders, boto_file_path=args.boto, | 945 builder_regex_list=args.builders, boto_file_path=args.boto, |
900 imagediffdb_threads=args.threads) | 946 imagediffdb_threads=args.threads) |
901 if args.truncate: | 947 if args.truncate: |
902 _SERVER.truncate_results = True | 948 _SERVER.truncate_results = True |
903 _SERVER.run() | 949 _SERVER.run() |
904 | 950 |
905 | 951 |
906 if __name__ == '__main__': | 952 if __name__ == '__main__': |
907 main() | 953 main() |
OLD | NEW |