thinhost 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {thinhost-0.1.0 → thinhost-0.2.0}/PKG-INFO +2 -3
- {thinhost-0.1.0 → thinhost-0.2.0}/setup.py +2 -3
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost/simple_upload_host.py +27 -4
- thinhost-0.2.0/thinhost/simple_web_server.py +224 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost.egg-info/PKG-INFO +2 -3
- thinhost-0.1.0/thinhost/simple_web_server.py +0 -99
- {thinhost-0.1.0 → thinhost-0.2.0}/README.md +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/pyproject.toml +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/setup.cfg +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost/__init__.py +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost/llm_base_service.py +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost/llm_base_service_test.py +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost/proxy_service_model.py +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost/simple_upload_host_tests.py +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost.egg-info/SOURCES.txt +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost.egg-info/dependency_links.txt +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost.egg-info/requires.txt +0 -0
- {thinhost-0.1.0 → thinhost-0.2.0}/thinhost.egg-info/top_level.txt +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thinhost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Thin HTTP hosts for Decelium microsite workflows
|
|
5
|
-
Home-page: https://github.com/Decelium/
|
|
5
|
+
Home-page: https://github.com/Decelium/thinhost
|
|
6
6
|
Author: Justin Girard
|
|
7
7
|
Author-email: justingirard@decelium.com
|
|
8
8
|
License: MIT
|
|
9
9
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
12
|
Requires-Python: >=3.7
|
|
14
13
|
Description-Content-Type: text/markdown
|
|
@@ -5,18 +5,17 @@ long_description = Path("README.md").read_text(encoding="utf-8")
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="thinhost",
|
|
8
|
-
version="0.
|
|
8
|
+
version="0.2.0",
|
|
9
9
|
description="Thin HTTP hosts for Decelium microsite workflows",
|
|
10
10
|
long_description=long_description,
|
|
11
11
|
long_description_content_type="text/markdown",
|
|
12
12
|
author="Justin Girard",
|
|
13
13
|
author_email="justingirard@decelium.com",
|
|
14
|
-
url="https://github.com/Decelium/
|
|
14
|
+
url="https://github.com/Decelium/thinhost",
|
|
15
15
|
license="MIT",
|
|
16
16
|
classifiers=[
|
|
17
17
|
"Development Status :: 2 - Pre-Alpha",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
|
-
"License :: OSI Approved :: MIT License",
|
|
20
19
|
"Programming Language :: Python :: 3",
|
|
21
20
|
],
|
|
22
21
|
packages=find_packages(),
|
|
@@ -561,14 +561,25 @@ class UploadService(BaseService):
|
|
|
561
561
|
)
|
|
562
562
|
return mapped
|
|
563
563
|
|
|
564
|
+
@staticmethod
|
|
565
|
+
def _format_size_compact(num_bytes) -> str:
|
|
566
|
+
try:
|
|
567
|
+
size = float(num_bytes or 0)
|
|
568
|
+
except Exception:
|
|
569
|
+
size = 0.0
|
|
570
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
571
|
+
idx = 0
|
|
572
|
+
while size >= 1024.0 and idx < len(units) - 1:
|
|
573
|
+
size /= 1024.0
|
|
574
|
+
idx += 1
|
|
575
|
+
if idx == 0:
|
|
576
|
+
return f"{int(size)} {units[idx]}"
|
|
577
|
+
return f"{size:.1f} {units[idx]}"
|
|
578
|
+
|
|
564
579
|
@classmethod
|
|
565
580
|
def run_remote(cls, hostAddress, cmd):
|
|
566
|
-
# headers = {"Upload-Token": d[d.f_token]}
|
|
567
581
|
headers = {}
|
|
568
|
-
print(type(hostAddress))
|
|
569
|
-
print(hostAddress)
|
|
570
582
|
s = UploadService.tHostAddress(hostAddress)
|
|
571
|
-
# print(jsondateencode_local.dumps(cmd,indent =1))
|
|
572
583
|
|
|
573
584
|
resp = requests.post(
|
|
574
585
|
s.getCommandUrl(),
|
|
@@ -599,6 +610,16 @@ class UploadService(BaseService):
|
|
|
599
610
|
for idx, transfer in mapped.items():
|
|
600
611
|
transfer = cls.tTransferProgress(transfer)
|
|
601
612
|
uploadRecord = cls.tClientUploadData(transfer[ts.f_data])
|
|
613
|
+
try:
|
|
614
|
+
source_path = str(uploadRecord[uploadRecord.f_stream_source])
|
|
615
|
+
except Exception:
|
|
616
|
+
source_path = ""
|
|
617
|
+
try:
|
|
618
|
+
size_bytes = int(transfer[ts.f_size])
|
|
619
|
+
except Exception:
|
|
620
|
+
size_bytes = 0
|
|
621
|
+
size_text = cls._format_size_compact(size_bytes)
|
|
622
|
+
print(f'uploading ({size_text}) "{source_path}" ...', flush=True)
|
|
602
623
|
uploadEID = cls.EntityID(transfer[ts.f_data])
|
|
603
624
|
args = {
|
|
604
625
|
"__command": ["upload_file"],
|
|
@@ -622,12 +643,14 @@ class UploadService(BaseService):
|
|
|
622
643
|
results[idx][ts.f_status] = "uploaded"
|
|
623
644
|
results[idx][ts.f_attrib]["resp.upload_file"] = str(rec)
|
|
624
645
|
results[idx][ts.f_attrib]["resp.has_file"] = str(has_file)
|
|
646
|
+
print("... upload finished", flush=True)
|
|
625
647
|
else:
|
|
626
648
|
results[idx][ts.f_progress] = 0
|
|
627
649
|
results[idx][ts.f_status] = "failed"
|
|
628
650
|
results[idx][ts.f_attrib]["resp.upload_file"] = str(rec)
|
|
629
651
|
results[idx][ts.f_attrib]["resp.has_file"] = str(has_file)
|
|
630
652
|
# return results
|
|
653
|
+
print("... upload failed", flush=True)
|
|
631
654
|
try:
|
|
632
655
|
results[idx][td.f_stream].close()
|
|
633
656
|
except:
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from nodejobs.dependencies.BaseService import BaseService
|
|
2
|
+
from nodejobs.dependencies.BaseData import BaseData
|
|
3
|
+
from thinhost.simple_upload_host import jsondateencode_local
|
|
4
|
+
from flask import Flask, send_from_directory, send_file, Response, request, has_request_context
|
|
5
|
+
from flask_cors import CORS
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import base64
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SimpleWebServer(BaseService):
|
|
12
|
+
class Command(BaseData):
|
|
13
|
+
f_command_id = "command_id"
|
|
14
|
+
f_data = "data"
|
|
15
|
+
|
|
16
|
+
def get_keys(self):
|
|
17
|
+
return {
|
|
18
|
+
self.f_command_id: str,
|
|
19
|
+
self.f_data: dict,
|
|
20
|
+
}, {}
|
|
21
|
+
|
|
22
|
+
class tHostSpec(BaseData):
|
|
23
|
+
f_host = "host"
|
|
24
|
+
f_port = "port"
|
|
25
|
+
f_command_uri = "command_uri"
|
|
26
|
+
f_public_root = "public_root"
|
|
27
|
+
f_cwd = "cwd"
|
|
28
|
+
|
|
29
|
+
def get_keys(self):
|
|
30
|
+
required = {
|
|
31
|
+
self.f_host: str,
|
|
32
|
+
self.f_port: str,
|
|
33
|
+
self.f_command_uri: str,
|
|
34
|
+
self.f_public_root: str,
|
|
35
|
+
}
|
|
36
|
+
optional = { self.f_cwd: str }
|
|
37
|
+
return required, optional
|
|
38
|
+
|
|
39
|
+
c_index_file = "index.html"
|
|
40
|
+
c_cors_path = r"/*"
|
|
41
|
+
c_cors_origins_key = "origins"
|
|
42
|
+
c_cors_any = "*"
|
|
43
|
+
c_http_mode_key = "_mode"
|
|
44
|
+
c_http_mode_rpc = "rpc"
|
|
45
|
+
c_http_mode_html = "html"
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def get_command_map(cls):
|
|
49
|
+
return {
|
|
50
|
+
"launch": { "required_args": [], "method": cls._cmd_launch },
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _after_launch_endpoints(cls, app, hostSpec):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def encode_http_object_base64url(payload: dict) -> str:
|
|
59
|
+
if not isinstance(payload, dict):
|
|
60
|
+
raise Exception("invalid_payload")
|
|
61
|
+
raw = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
|
62
|
+
encoded = base64.urlsafe_b64encode(raw).decode("ascii")
|
|
63
|
+
return encoded.rstrip("=")
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def decode_http_object_base64url(encoded: str) -> dict:
|
|
67
|
+
if not isinstance(encoded, str) or not encoded.strip():
|
|
68
|
+
raise Exception("invalid_encoded_payload")
|
|
69
|
+
text = encoded.strip()
|
|
70
|
+
text = text + ("=" * ((4 - (len(text) % 4)) % 4))
|
|
71
|
+
raw = base64.urlsafe_b64decode(text.encode("ascii"))
|
|
72
|
+
payload = json.loads(raw.decode("utf-8"))
|
|
73
|
+
if not isinstance(payload, dict):
|
|
74
|
+
raise Exception("invalid_encoded_payload")
|
|
75
|
+
return payload
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def parse_http_request_object(cls, request_obj):
|
|
79
|
+
method = str(getattr(request_obj, "method", "") or "").upper()
|
|
80
|
+
if method == "GET":
|
|
81
|
+
encoded = request_obj.args.get("__encoded")
|
|
82
|
+
if isinstance(encoded, str) and encoded.strip():
|
|
83
|
+
return cls.decode_http_object_base64url(encoded)
|
|
84
|
+
return dict(request_obj.args)
|
|
85
|
+
raw = request_obj.get_json()
|
|
86
|
+
if not isinstance(raw, dict):
|
|
87
|
+
raise Exception("invalid_request_payload")
|
|
88
|
+
return raw
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def get_request(cls):
|
|
92
|
+
if not has_request_context():
|
|
93
|
+
return {}
|
|
94
|
+
return cls.parse_http_request_object(request)
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def get_request_data(cls, request_payload: dict = None):
|
|
98
|
+
payload = request_payload if isinstance(request_payload, dict) else cls.get_request()
|
|
99
|
+
if not isinstance(payload, dict):
|
|
100
|
+
return {}
|
|
101
|
+
data = payload.get(cls.Command.f_data)
|
|
102
|
+
return data if isinstance(data, dict) else payload
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def resolve_http_mode(cls, payload: dict):
|
|
106
|
+
if not isinstance(payload, dict):
|
|
107
|
+
return cls.c_http_mode_rpc
|
|
108
|
+
mode = str(payload.get(cls.c_http_mode_key) or "").strip().lower()
|
|
109
|
+
if mode == cls.c_http_mode_html:
|
|
110
|
+
return cls.c_http_mode_html
|
|
111
|
+
return cls.c_http_mode_rpc
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def respond_mode_aware(
|
|
115
|
+
cls,
|
|
116
|
+
*,
|
|
117
|
+
request_payload: dict,
|
|
118
|
+
rpc_result,
|
|
119
|
+
rpc_status: int = 200,
|
|
120
|
+
html_file_spec: dict = None,
|
|
121
|
+
html_file_spec_builder=None,
|
|
122
|
+
):
|
|
123
|
+
mode = cls.resolve_http_mode(request_payload)
|
|
124
|
+
if mode == cls.c_http_mode_html:
|
|
125
|
+
spec = html_file_spec
|
|
126
|
+
if spec is None and callable(html_file_spec_builder):
|
|
127
|
+
spec = html_file_spec_builder(request_payload, rpc_result)
|
|
128
|
+
if not isinstance(spec, dict):
|
|
129
|
+
raise Exception("html_mode_requires_file_spec")
|
|
130
|
+
return cls.serve_file_from(
|
|
131
|
+
root_path=str(spec.get("root_path") or ""),
|
|
132
|
+
cache_path=str(spec.get("cache_path") or ""),
|
|
133
|
+
rel_path=str(spec.get("rel_path") or ""),
|
|
134
|
+
as_attachment=bool(spec.get("as_attachment")),
|
|
135
|
+
download_name=str(spec.get("download_name") or ""),
|
|
136
|
+
)
|
|
137
|
+
return (jsondateencode_local.dumps(rpc_result), int(rpc_status))
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def serve_file_from(
|
|
141
|
+
cls,
|
|
142
|
+
*,
|
|
143
|
+
root_path: str,
|
|
144
|
+
rel_path: str,
|
|
145
|
+
cache_path: str = "",
|
|
146
|
+
as_attachment: bool = False,
|
|
147
|
+
download_name: str = "",
|
|
148
|
+
):
|
|
149
|
+
if not isinstance(root_path, str) or not root_path.strip():
|
|
150
|
+
raise Exception("invalid_root_path")
|
|
151
|
+
base_abs = os.path.abspath(root_path.strip())
|
|
152
|
+
if isinstance(cache_path, str) and cache_path.strip():
|
|
153
|
+
base_abs = os.path.abspath(os.path.join(base_abs, cache_path.strip()))
|
|
154
|
+
if not os.path.isdir(base_abs):
|
|
155
|
+
raise Exception("invalid_base_path")
|
|
156
|
+
|
|
157
|
+
rel = str(rel_path or "").strip().lstrip("/\\")
|
|
158
|
+
if not rel:
|
|
159
|
+
raise Exception("invalid_rel_path")
|
|
160
|
+
full_abs = os.path.abspath(os.path.join(base_abs, rel))
|
|
161
|
+
if os.path.commonpath([base_abs, full_abs]) != base_abs:
|
|
162
|
+
raise Exception("path_escapes_base")
|
|
163
|
+
if not os.path.isfile(full_abs):
|
|
164
|
+
raise FileNotFoundError("file_not_found")
|
|
165
|
+
|
|
166
|
+
name = str(download_name or "").strip() or None
|
|
167
|
+
return send_file(
|
|
168
|
+
full_abs,
|
|
169
|
+
as_attachment=bool(as_attachment),
|
|
170
|
+
download_name=name,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
def _cmd_launch(cls, **kwargs):
|
|
175
|
+
if has_request_context():
|
|
176
|
+
raise Exception(
|
|
177
|
+
"launch is CLI-only and cannot be invoked via /cmd on a running server"
|
|
178
|
+
)
|
|
179
|
+
cls.currentHa = cls.tHostSpec(kwargs)
|
|
180
|
+
cwd_start = os.getcwd()
|
|
181
|
+
root_dir_raw = cls.currentHa[cls.currentHa.f_public_root]
|
|
182
|
+
cmd_uri = cls.currentHa[cls.currentHa.f_command_uri]
|
|
183
|
+
if not os.path.isdir(root_dir_raw): raise Exception(f"public_root does not exist: {root_dir_raw}")
|
|
184
|
+
if not cmd_uri.startswith("/"): raise Exception("command_uri must start with '/'")
|
|
185
|
+
os.chdir(root_dir_raw)
|
|
186
|
+
root_dir = os.getcwd()
|
|
187
|
+
|
|
188
|
+
app = Flask(__name__, static_folder=None, static_url_path=None)
|
|
189
|
+
app.config.from_object(__name__)
|
|
190
|
+
CORS(app, resources={cls.c_cors_path: {cls.c_cors_origins_key: cls.c_cors_any}})
|
|
191
|
+
|
|
192
|
+
@app.route("/", methods=["GET", "POST"])
|
|
193
|
+
@app.route("/<path:u_path>", methods=["GET", "POST"])
|
|
194
|
+
def index(u_path="/"):
|
|
195
|
+
rel_path = u_path.lstrip("/")
|
|
196
|
+
full = os.path.join(root_dir, rel_path)
|
|
197
|
+
if os.path.isfile(full): return send_from_directory(root_dir, rel_path)
|
|
198
|
+
if os.path.isdir(full):
|
|
199
|
+
index_path = os.path.join(full, cls.c_index_file)
|
|
200
|
+
if os.path.isfile(index_path): return send_from_directory(full, cls.c_index_file)
|
|
201
|
+
root_index = os.path.join(root_dir, cls.c_index_file)
|
|
202
|
+
if os.path.isfile(root_index): return send_from_directory(root_dir, cls.c_index_file)
|
|
203
|
+
msg = (
|
|
204
|
+
f"File not found: {rel_path}\n"
|
|
205
|
+
f"cwd_start: {cwd_start}\n"
|
|
206
|
+
f"public_root: {root_dir_raw}\n"
|
|
207
|
+
f"root_dir: {root_dir}\n"
|
|
208
|
+
f"full: {full}"
|
|
209
|
+
)
|
|
210
|
+
return Response(msg, status=404)
|
|
211
|
+
|
|
212
|
+
@app.route(cmd_uri, methods=["GET", "POST"])
|
|
213
|
+
def process_command():
|
|
214
|
+
cmd = cls.Command(cls.get_request())
|
|
215
|
+
resp = cls.run(__command=[cmd[cmd.f_command_id]], **cmd[cmd.f_data])
|
|
216
|
+
return (jsondateencode_local.dumps(resp), 201)
|
|
217
|
+
|
|
218
|
+
cls._after_launch_endpoints(app, cls.currentHa)
|
|
219
|
+
app.run(host=cls.currentHa[cls.currentHa.f_host], port=int(cls.currentHa[cls.currentHa.f_port]))
|
|
220
|
+
return "ok"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
SimpleWebServer.run_cli()
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thinhost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Thin HTTP hosts for Decelium microsite workflows
|
|
5
|
-
Home-page: https://github.com/Decelium/
|
|
5
|
+
Home-page: https://github.com/Decelium/thinhost
|
|
6
6
|
Author: Justin Girard
|
|
7
7
|
Author-email: justingirard@decelium.com
|
|
8
8
|
License: MIT
|
|
9
9
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
12
|
Requires-Python: >=3.7
|
|
14
13
|
Description-Content-Type: text/markdown
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
from nodejobs.dependencies.BaseService import BaseService
|
|
2
|
-
from nodejobs.dependencies.BaseData import BaseData
|
|
3
|
-
from thinhost.simple_upload_host import jsondateencode_local
|
|
4
|
-
from flask import Flask, send_from_directory, Response, request
|
|
5
|
-
from flask_cors import CORS
|
|
6
|
-
import os
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class SimpleWebServer(BaseService):
|
|
10
|
-
class Command(BaseData):
|
|
11
|
-
f_command_id = "command_id"
|
|
12
|
-
f_data = "data"
|
|
13
|
-
|
|
14
|
-
def get_keys(self):
|
|
15
|
-
return {
|
|
16
|
-
self.f_command_id: str,
|
|
17
|
-
self.f_data: dict,
|
|
18
|
-
}, {}
|
|
19
|
-
|
|
20
|
-
class tHostSpec(BaseData):
|
|
21
|
-
f_host = "host"
|
|
22
|
-
f_port = "port"
|
|
23
|
-
f_command_uri = "command_uri"
|
|
24
|
-
f_public_root = "public_root"
|
|
25
|
-
f_cwd = "cwd"
|
|
26
|
-
|
|
27
|
-
def get_keys(self):
|
|
28
|
-
required = {
|
|
29
|
-
self.f_host: str,
|
|
30
|
-
self.f_port: str,
|
|
31
|
-
self.f_command_uri: str,
|
|
32
|
-
self.f_public_root: str,
|
|
33
|
-
}
|
|
34
|
-
optional = { self.f_cwd: str }
|
|
35
|
-
return required, optional
|
|
36
|
-
|
|
37
|
-
c_index_file = "index.html"
|
|
38
|
-
c_cors_path = r"/*"
|
|
39
|
-
c_cors_origins_key = "origins"
|
|
40
|
-
c_cors_any = "*"
|
|
41
|
-
|
|
42
|
-
@classmethod
|
|
43
|
-
def get_command_map(cls):
|
|
44
|
-
return {
|
|
45
|
-
"launch": { "required_args": [], "method": cls._cmd_launch },
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
@classmethod
|
|
49
|
-
def _after_launch_endpoints(cls, app, hostSpec):
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
@classmethod
|
|
53
|
-
def _cmd_launch(cls, **kwargs):
|
|
54
|
-
cls.currentHa = cls.tHostSpec(kwargs)
|
|
55
|
-
cwd_start = os.getcwd()
|
|
56
|
-
root_dir_raw = cls.currentHa[cls.currentHa.f_public_root]
|
|
57
|
-
cmd_uri = cls.currentHa[cls.currentHa.f_command_uri]
|
|
58
|
-
if not os.path.isdir(root_dir_raw): raise Exception(f"public_root does not exist: {root_dir_raw}")
|
|
59
|
-
if not cmd_uri.startswith("/"): raise Exception("command_uri must start with '/'")
|
|
60
|
-
os.chdir(root_dir_raw)
|
|
61
|
-
root_dir = os.getcwd()
|
|
62
|
-
|
|
63
|
-
app = Flask(__name__, static_folder=None, static_url_path=None)
|
|
64
|
-
app.config.from_object(__name__)
|
|
65
|
-
CORS(app, resources={cls.c_cors_path: {cls.c_cors_origins_key: cls.c_cors_any}})
|
|
66
|
-
|
|
67
|
-
@app.route("/", methods=["GET", "POST"])
|
|
68
|
-
@app.route("/<path:u_path>", methods=["GET", "POST"])
|
|
69
|
-
def index(u_path="/"):
|
|
70
|
-
rel_path = u_path.lstrip("/")
|
|
71
|
-
full = os.path.join(root_dir, rel_path)
|
|
72
|
-
if os.path.isfile(full): return send_from_directory(root_dir, rel_path)
|
|
73
|
-
if os.path.isdir(full):
|
|
74
|
-
index_path = os.path.join(full, cls.c_index_file)
|
|
75
|
-
if os.path.isfile(index_path): return send_from_directory(full, cls.c_index_file)
|
|
76
|
-
root_index = os.path.join(root_dir, cls.c_index_file)
|
|
77
|
-
if os.path.isfile(root_index): return send_from_directory(root_dir, cls.c_index_file)
|
|
78
|
-
msg = (
|
|
79
|
-
f"File not found: {rel_path}\n"
|
|
80
|
-
f"cwd_start: {cwd_start}\n"
|
|
81
|
-
f"public_root: {root_dir_raw}\n"
|
|
82
|
-
f"root_dir: {root_dir}\n"
|
|
83
|
-
f"full: {full}"
|
|
84
|
-
)
|
|
85
|
-
return Response(msg, status=404)
|
|
86
|
-
|
|
87
|
-
@app.route(cmd_uri, methods=["POST"])
|
|
88
|
-
def process_command():
|
|
89
|
-
cmd = cls.Command(request.get_json())
|
|
90
|
-
resp = cls.run(__command=[cmd[cmd.f_command_id]], **cmd[cmd.f_data])
|
|
91
|
-
return (jsondateencode_local.dumps(resp), 201)
|
|
92
|
-
|
|
93
|
-
cls._after_launch_endpoints(app, cls.currentHa)
|
|
94
|
-
app.run(host=cls.currentHa[cls.currentHa.f_host], port=int(cls.currentHa[cls.currentHa.f_port]))
|
|
95
|
-
return "ok"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if __name__ == "__main__":
|
|
99
|
-
SimpleWebServer.run_cli()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|