|
| 1 | +import json |
| 2 | +import os |
| 3 | +import queue |
| 4 | +import subprocess |
| 5 | +import threading |
| 6 | +import time |
| 7 | +import traceback |
| 8 | + |
| 9 | +import six |
| 10 | +from flask import Flask, request, send_from_directory, abort, Response |
| 11 | + |
| 12 | +import configs_model |
| 13 | +import external_model |
| 14 | +import utils.file_utils as file_utils |
| 15 | + |
| 16 | +app = Flask(__name__) |
| 17 | +script_inputs = {} |
| 18 | + |
| 19 | + |
| 20 | +def read_configs(): |
| 21 | + configs_dir = "configs" |
| 22 | + files = os.listdir(configs_dir) |
| 23 | + |
| 24 | + configs = [file for file in files if file.lower().endswith(".json")] |
| 25 | + |
| 26 | + result = [] |
| 27 | + |
| 28 | + for config_path in configs: |
| 29 | + path = os.path.join(configs_dir, config_path) |
| 30 | + content = file_utils.read_file(path) |
| 31 | + result.append(configs_model.from_json(path, content)) |
| 32 | + |
| 33 | + return result |
| 34 | + |
| 35 | + |
| 36 | +@app.route("/scripts/list", methods=["GET"]) |
| 37 | +def get_scripts(): |
| 38 | + configs = read_configs() |
| 39 | + |
| 40 | + return json.dumps([config.get_name() for config in configs]) |
| 41 | + |
| 42 | + |
| 43 | +@app.route("/scripts/info", methods=["GET"]) |
| 44 | +def get_script_info(): |
| 45 | + params = request.values |
| 46 | + |
| 47 | + if not ("name" in params): |
| 48 | + return abort("Name is not specified") |
| 49 | + |
| 50 | + name = params["name"] |
| 51 | + |
| 52 | + config = find_config_by_name(name) |
| 53 | + |
| 54 | + if not config: |
| 55 | + return "Couldn't find a script by name" |
| 56 | + |
| 57 | + return external_model.config_to_json(config) |
| 58 | + |
| 59 | + |
| 60 | +def find_config_by_name(name): |
| 61 | + configs = read_configs() |
| 62 | + config_by_name = None |
| 63 | + for config in configs: |
| 64 | + if config.get_name() == name: |
| 65 | + config_by_name = config |
| 66 | + break |
| 67 | + return config_by_name |
| 68 | + |
| 69 | + |
| 70 | +@app.route("/<path:filename>") |
| 71 | +def web_resources(filename): |
| 72 | + if not filename: |
| 73 | + filename = "index.html" |
| 74 | + |
| 75 | + return send_from_directory("web", filename) |
| 76 | + |
| 77 | + |
| 78 | +def build_parameter_string(param_values, config): |
| 79 | + result = [] |
| 80 | + |
| 81 | + for parameter in config.get_parameters(): |
| 82 | + name = parameter.get_name() |
| 83 | + |
| 84 | + if name in param_values: |
| 85 | + value = param_values[name] |
| 86 | + |
| 87 | + if parameter.is_no_value(): |
| 88 | + # do not replace == True, since REST service can start accepting boolean as string |
| 89 | + if (value == True) or (value == "true"): |
| 90 | + result.append(parameter.get_param()) |
| 91 | + else: |
| 92 | + if value: |
| 93 | + result.append(parameter.get_param()) |
| 94 | + |
| 95 | + # value_string = '"' + value.replace("\"", "\\\"") + '"' |
| 96 | + # result.append(value_string) |
| 97 | + result.append(value) |
| 98 | + |
| 99 | + return result |
| 100 | + |
| 101 | + |
| 102 | +@app.route("/scripts/execute/input", methods=["POST"]) |
| 103 | +def post_user_input(): |
| 104 | + request_data = request.data.decode("UTF-8") |
| 105 | + id = json.loads(request_data).get("id") |
| 106 | + value = json.loads(request_data).get("value") |
| 107 | + |
| 108 | + script_inputs[id].put(value) |
| 109 | + |
| 110 | + return "" |
| 111 | + |
| 112 | + |
| 113 | +@app.route("/scripts/execute", methods=["POST"]) |
| 114 | +def execute_script(): |
| 115 | + request_data = request.data.decode("UTF-8") |
| 116 | + |
| 117 | + execution_info = external_model.to_execution_info(request_data) |
| 118 | + |
| 119 | + script_name = execution_info.get_script() |
| 120 | + |
| 121 | + config = find_config_by_name(script_name) |
| 122 | + |
| 123 | + if not config: |
| 124 | + return abort("Script with name '" + str(script_name) + "' not found") |
| 125 | + |
| 126 | + try: |
| 127 | + script_path = file_utils.normalize_path(config.get_script_path()) |
| 128 | + script_args = build_parameter_string(execution_info.get_param_values(), config) |
| 129 | + |
| 130 | + command = [] |
| 131 | + command.append(script_path) |
| 132 | + command.extend(script_args) |
| 133 | + |
| 134 | + six.print_("Calling script: " + " ".join(command)) |
| 135 | + |
| 136 | + process = subprocess.Popen(command, |
| 137 | + stdin=subprocess.PIPE, |
| 138 | + stdout=subprocess.PIPE, |
| 139 | + stderr=subprocess.STDOUT) |
| 140 | + |
| 141 | + input_id = int(round(time.time() * 1000)) |
| 142 | + input_queue = queue.Queue() |
| 143 | + script_inputs[input_id] = input_queue |
| 144 | + |
| 145 | + output = queue.Queue() |
| 146 | + |
| 147 | + to_process = threading.Thread(target=pipe_http_to_process, args=(input_id, process, output)) |
| 148 | + to_process.start() |
| 149 | + |
| 150 | + from_process = threading.Thread(target=pipe_process_to_http, args=(process, output)) |
| 151 | + from_process.start() |
| 152 | + |
| 153 | + output.put(to_script_output(" --- OUTPUT --- \n")) |
| 154 | + |
| 155 | + def response_stream(): |
| 156 | + while True: |
| 157 | + try: |
| 158 | + output_object = output.get(timeout=1) |
| 159 | + yield output_object |
| 160 | + except queue.Empty: |
| 161 | + if not (process.poll() is None): |
| 162 | + break |
| 163 | + |
| 164 | + return Response(response_stream(), mimetype='text/html') |
| 165 | + |
| 166 | + except Exception as e: |
| 167 | + traceback.print_exc() |
| 168 | + if (hasattr(e, "strerror") and e.strerror): |
| 169 | + error_output = e.strerror |
| 170 | + else: |
| 171 | + error_output = "Unknown error occurred, contact the administrator" |
| 172 | + |
| 173 | + result = " --- ERRORS --- \n" |
| 174 | + result += error_output |
| 175 | + return to_script_output(result) |
| 176 | + |
| 177 | + |
| 178 | +def pipe_http_to_process(input_id, process, output): |
| 179 | + output.put(json.dumps({ |
| 180 | + "input": "your input >>", |
| 181 | + "id": input_id |
| 182 | + })) |
| 183 | + |
| 184 | + input_queue = script_inputs.get(input_id) |
| 185 | + |
| 186 | + while True: |
| 187 | + try: |
| 188 | + value = input_queue.get(timeout=1) |
| 189 | + input_value = value + "\n" |
| 190 | + |
| 191 | + output.put(to_script_output(input_value)) |
| 192 | + |
| 193 | + process.stdin.write(input_value.encode()) |
| 194 | + process.stdin.flush() |
| 195 | + except queue.Empty: |
| 196 | + if process.poll() is not None: |
| 197 | + break |
| 198 | + |
| 199 | + |
| 200 | +def pipe_process_to_http(process, output): |
| 201 | + empty_count = 0 |
| 202 | + |
| 203 | + while True: |
| 204 | + line_bytes = process.stdout.readline() |
| 205 | + |
| 206 | + if not line_bytes: |
| 207 | + empty_count += 1 |
| 208 | + |
| 209 | + if process.poll() is None: |
| 210 | + break |
| 211 | + |
| 212 | + time.sleep(min(1, 0.1 * empty_count)) |
| 213 | + |
| 214 | + else: |
| 215 | + line = line_bytes.decode("UTF-8") |
| 216 | + output.put(to_script_output(line)) |
| 217 | + |
| 218 | + |
| 219 | +def to_script_output(text): |
| 220 | + return json.dumps({ |
| 221 | + "output": text |
| 222 | + }) |
| 223 | + |
| 224 | + |
| 225 | +def main(): |
| 226 | + app.debug = True |
| 227 | + app.run(threaded=True) |
| 228 | + |
| 229 | + |
| 230 | +if __name__ == "__main__": |
| 231 | + main() |
0 commit comments