1################################################################ 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance 9# with the License. You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, 14# software distributed under the License is distributed on an 15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16# KIND, either express or implied. See the License for the 17# specific language governing permissions and limitations 18# under the License. 19# 20################################################################ 21 22"""The worker's request loop. Anything a handler prints goes to stderr so it cannot corrupt the protocol on stdout.""" 23import inspect 24import json 25import os 26import sys 27import time 28import traceback 29 30PROTOCOL = 1 31 32 33class Fail(Exception): 34 """An error a handler wants reported with a specific kind: uno, office_dead, bad_args or python.""" 35 36 def __init__(self, kind, message=""): 37 super().__init__(message) 38 self.kind = kind 39 40 41def serve(ops, stdin=None, stdout=None): 42 """Sends the hello, then answers requests until stdin closes or an op named 'shutdown' arrives.""" 43 out = stdout or sys.stdout 44 inp = stdin or sys.stdin 45 sys.stdout = sys.stderr 46 47 def send(obj): 48 out.write(json.dumps(obj) + "\n") 49 out.flush() 50 51 send({"hello": {"protocol": PROTOCOL, "python": "%d.%d.%d" % sys.version_info[:3], "pid": os.getpid()}}) 52 for line in inp: 53 if not line.strip(): 54 continue 55 try: 56 req = json.loads(line) 57 rid, op, args = req["id"], req["op"], req.get("args") or {} 58 except (ValueError, KeyError, TypeError) as e: 59 send({"id": None, "ok": False, "error": {"kind": "bad_args", "exc": type(e).__name__, "message": "unreadable request: %s" % e, "traceback": ""}}) 60 continue 61 if op == "shutdown": 62 send({"id": rid, "ok": True, "result": {}, "elapsed": 0.0}) 63 return 64 t0 = time.time() 65 try: 66 if op == "ping": 67 result = {"pong": True} 68 elif op in ops: 69 try: 70 inspect.signature(ops[op]).bind(**args) 71 except TypeError as e: 72 raise Fail("bad_args", "%s: %s" % (op, e)) 73 result = ops[op](**args) 74 else: 75 raise Fail("bad_args", "unknown op %r" % op) 76 send({"id": rid, "ok": True, "result": result, "elapsed": round(time.time() - t0, 3)}) 77 except Fail as e: 78 send({"id": rid, "ok": False, "error": {"kind": e.kind, "exc": "Fail", "message": str(e), "traceback": ""}}) 79 except Exception as e: 80 send({"id": rid, "ok": False, "error": {"kind": "python", "exc": type(e).__name__, "message": str(e), "traceback": traceback.format_exc()}}) 81