tina4-python 0.2.122__py3-none-any.whl
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.
- tina4_python/Auth.py +222 -0
- tina4_python/Constant.py +43 -0
- tina4_python/Database.py +591 -0
- tina4_python/DatabaseResult.py +107 -0
- tina4_python/DatabaseTypes.py +15 -0
- tina4_python/Debug.py +126 -0
- tina4_python/Env.py +37 -0
- tina4_python/Localization.py +42 -0
- tina4_python/Messages.py +30 -0
- tina4_python/MiddleWare.py +90 -0
- tina4_python/Migration.py +107 -0
- tina4_python/ORM.py +639 -0
- tina4_python/Queue.py +615 -0
- tina4_python/Request.py +19 -0
- tina4_python/Response.py +121 -0
- tina4_python/Router.py +423 -0
- tina4_python/Session.py +342 -0
- tina4_python/ShellColors.py +20 -0
- tina4_python/Swagger.py +228 -0
- tina4_python/Template.py +107 -0
- tina4_python/Webserver.py +429 -0
- tina4_python/Websocket.py +49 -0
- tina4_python/__init__.py +392 -0
- tina4_python/messages.pot +83 -0
- tina4_python/public/css/readme.md +0 -0
- tina4_python/public/favicon.ico +0 -0
- tina4_python/public/images/403.png +0 -0
- tina4_python/public/images/404.png +0 -0
- tina4_python/public/images/500.png +0 -0
- tina4_python/public/images/logo.png +0 -0
- tina4_python/public/images/readme.md +0 -0
- tina4_python/public/js/readme.md +0 -0
- tina4_python/public/js/reconnecting-websocket.js +365 -0
- tina4_python/public/js/tina4helper.js +397 -0
- tina4_python/public/swagger/index.html +90 -0
- tina4_python/public/swagger/oauth2-redirect.html +63 -0
- tina4_python/templates/errors/403.twig +10 -0
- tina4_python/templates/errors/404.twig +10 -0
- tina4_python/templates/errors/500.twig +11 -0
- tina4_python/templates/readme.md +1 -0
- tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- tina4_python/translations/en/LC_MESSAGES/messages.po +80 -0
- tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- tina4_python/translations/fr/LC_MESSAGES/messages.po +84 -0
- tina4_python-0.2.122.dist-info/METADATA +465 -0
- tina4_python-0.2.122.dist-info/RECORD +47 -0
- tina4_python-0.2.122.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Tina4 - This is not a 4ramework.
|
|
3
|
+
# Copy-right 2007 - current Tina4
|
|
4
|
+
# License: MIT https://opensource.org/licenses/MIT
|
|
5
|
+
#
|
|
6
|
+
# flake8: noqa: E501
|
|
7
|
+
import asyncio
|
|
8
|
+
import base64
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import mimetypes
|
|
13
|
+
from urllib.parse import unquote_plus
|
|
14
|
+
from urllib.parse import urlparse, parse_qsl
|
|
15
|
+
import tina4_python
|
|
16
|
+
from tina4_python import Constant
|
|
17
|
+
from tina4_python.Constant import HTTP_REDIRECT, HTTP_OK, HTTP_SERVER_ERROR
|
|
18
|
+
from tina4_python.Session import Session
|
|
19
|
+
from tina4_python.Router import Router
|
|
20
|
+
from tina4_python.Debug import Debug
|
|
21
|
+
from tina4_python.Template import Template
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_int(v):
|
|
25
|
+
try:
|
|
26
|
+
int(v)
|
|
27
|
+
except ValueError:
|
|
28
|
+
return False
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Webserver:
|
|
33
|
+
async def get_content_length(self):
|
|
34
|
+
# get the content length
|
|
35
|
+
if "content-length" in self.lowercase_headers:
|
|
36
|
+
return int(self.lowercase_headers["content-length"])
|
|
37
|
+
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
async def get_content_body(self, content_length):
|
|
41
|
+
# get lines of content where at the end of the request
|
|
42
|
+
content = self.content_raw
|
|
43
|
+
|
|
44
|
+
if "content-type" in self.lowercase_headers:
|
|
45
|
+
if self.lowercase_headers["content-type"] == "application/x-www-form-urlencoded":
|
|
46
|
+
body = {}
|
|
47
|
+
content_data = content.decode("utf-8").split("&")
|
|
48
|
+
for data in content_data:
|
|
49
|
+
data = data.split("=", 1)
|
|
50
|
+
body[data[0]] = unquote_plus(data[1])
|
|
51
|
+
return body, {}
|
|
52
|
+
elif self.lowercase_headers["content-type"] == "application/json":
|
|
53
|
+
# print("CONTENT", content, self.request)
|
|
54
|
+
try:
|
|
55
|
+
return json.loads(content), {}
|
|
56
|
+
except Exception:
|
|
57
|
+
return content.decode("utf-8"), {}
|
|
58
|
+
|
|
59
|
+
elif self.lowercase_headers["content-type"] == "text/plain":
|
|
60
|
+
return content.decode("utf-8"), {}
|
|
61
|
+
else:
|
|
62
|
+
content_data = self.lowercase_headers["content-type"].split("; ")
|
|
63
|
+
|
|
64
|
+
if content_data[0] == "multipart/form-data":
|
|
65
|
+
boundary = content_data[1].split("=")[1] + "\r\n"
|
|
66
|
+
content = b"\r\n" + content
|
|
67
|
+
data_array = content.split(str.encode(boundary))
|
|
68
|
+
body = {}
|
|
69
|
+
files = {}
|
|
70
|
+
for data in data_array:
|
|
71
|
+
data = data.split(b"\r\n\r\n")
|
|
72
|
+
data_names = data[0].decode("utf-8").split("; ")
|
|
73
|
+
|
|
74
|
+
if data_names[0] == "Content-Disposition: form-data":
|
|
75
|
+
key_name = data_names[1].split("=")[1][1:-1]
|
|
76
|
+
|
|
77
|
+
if len(data_names) == 2:
|
|
78
|
+
data_value = data[1].split(b"\r\n")[0]
|
|
79
|
+
body[key_name] = unquote_plus(data_value.decode("utf-8"))
|
|
80
|
+
else:
|
|
81
|
+
data_value = data[1].split(b"\r\n--")[0]
|
|
82
|
+
file_data = data_names[2].split("\r\n")
|
|
83
|
+
file_name = "Unknown"
|
|
84
|
+
content_type = "Unknown"
|
|
85
|
+
meta_data = {}
|
|
86
|
+
for file_info in file_data:
|
|
87
|
+
file_info1 = file_info.split("=")
|
|
88
|
+
if len(file_info1) > 1:
|
|
89
|
+
meta_data[file_info1[0]] = file_info1[1].strip()
|
|
90
|
+
file_info2 = file_info.split(":")
|
|
91
|
+
if len(file_info2) > 1:
|
|
92
|
+
meta_data[file_info2[0]] = file_info2[1].strip()
|
|
93
|
+
|
|
94
|
+
if "filename" in meta_data:
|
|
95
|
+
file_name = meta_data["filename"][1:-1]
|
|
96
|
+
if "Content-Type" in meta_data:
|
|
97
|
+
content_type = meta_data["Content-Type"]
|
|
98
|
+
|
|
99
|
+
if key_name in body:
|
|
100
|
+
body[key_name] = [body[key_name]]
|
|
101
|
+
body[key_name].append({"file_name": file_name, "content_type": content_type,"content": base64.encodebytes(data_value).decode("utf-8").replace(
|
|
102
|
+
"\n", "")})
|
|
103
|
+
else:
|
|
104
|
+
body[key_name] = {"file_name": file_name, "content_type": content_type,"content": base64.encodebytes(data_value).decode("utf-8").replace(
|
|
105
|
+
"\n", "")}
|
|
106
|
+
|
|
107
|
+
if key_name in files:
|
|
108
|
+
files[key_name] = [files[key_name]]
|
|
109
|
+
files[key_name].append({"file_name": file_name, "content_type": content_type,"content": base64.encodebytes(data_value).decode("utf-8").replace(
|
|
110
|
+
"\n", "")})
|
|
111
|
+
else:
|
|
112
|
+
files[key_name] = {"file_name": file_name, "content_type": content_type,"content": base64.encodebytes(data_value).decode("utf-8").replace(
|
|
113
|
+
"\n", "")}
|
|
114
|
+
|
|
115
|
+
return body, files
|
|
116
|
+
|
|
117
|
+
return {"data": base64.encodebytes(content).decode("utf-8").replace("\n", "")}, {}
|
|
118
|
+
|
|
119
|
+
async def send_basic_headers(self, headers):
|
|
120
|
+
self.send_header("Access-Control-Allow-Origin", "*", headers)
|
|
121
|
+
self.send_header("Access-Control-Allow-Headers",
|
|
122
|
+
"Origin, X-Requested-With, Content-Type, Accept, Authorization", headers)
|
|
123
|
+
self.send_header("Access-Control-Allow-Credentials", "True", headers)
|
|
124
|
+
# self.send_header("Content-Length", str(len(response.content)), headers)
|
|
125
|
+
self.send_header("Connection", "Keep-Alive", headers)
|
|
126
|
+
self.send_header("Keep-Alive", "timeout=5, max=30", headers)
|
|
127
|
+
|
|
128
|
+
async def get_response(self, method, transport, asgi_response=False):
|
|
129
|
+
"""
|
|
130
|
+
Get response
|
|
131
|
+
:param method: GET, POST, PATCH, DELETE, PUT
|
|
132
|
+
:return:
|
|
133
|
+
"""
|
|
134
|
+
headers = []
|
|
135
|
+
if method == "OPTIONS":
|
|
136
|
+
self.send_header("Access-Control-Allow-Origin", "*", headers)
|
|
137
|
+
self.send_header("Access-Control-Allow-Headers",
|
|
138
|
+
"Origin, X-Requested-With, Content-Type, Accept, Authorization", headers)
|
|
139
|
+
self.send_header("Access-Control-Allow-Credentials", "True", headers)
|
|
140
|
+
|
|
141
|
+
headers = await self.get_headers(headers, self.response_protocol, Constant.HTTP_OK)
|
|
142
|
+
|
|
143
|
+
if asgi_response:
|
|
144
|
+
return None, headers
|
|
145
|
+
else:
|
|
146
|
+
return headers
|
|
147
|
+
|
|
148
|
+
params = dict(parse_qsl(urlparse(self.path).query, keep_blank_values=True))
|
|
149
|
+
|
|
150
|
+
new_params = {}
|
|
151
|
+
new_params.update(params)
|
|
152
|
+
|
|
153
|
+
for key, value in params.items():
|
|
154
|
+
regex = r"(\w+)"
|
|
155
|
+
matches = re.finditer(regex, key)
|
|
156
|
+
|
|
157
|
+
var_names = []
|
|
158
|
+
for matchNum, match in enumerate(matches, start=0):
|
|
159
|
+
if is_int(match.group()):
|
|
160
|
+
var_names.append(int(match.group()))
|
|
161
|
+
else:
|
|
162
|
+
var_names.append(match.group())
|
|
163
|
+
|
|
164
|
+
if len(var_names) > 1:
|
|
165
|
+
start_var = new_params
|
|
166
|
+
counter = 0
|
|
167
|
+
while counter < len(var_names):
|
|
168
|
+
var_name = var_names[counter]
|
|
169
|
+
if not is_int(var_name):
|
|
170
|
+
if isinstance(start_var, dict) and var_name in start_var:
|
|
171
|
+
start_var = start_var[var_name]
|
|
172
|
+
else:
|
|
173
|
+
if counter + 1 < len(var_names) and is_int(var_names[counter + 1]):
|
|
174
|
+
if var_name not in start_var:
|
|
175
|
+
start_var[var_name] = []
|
|
176
|
+
start_var = start_var[var_name]
|
|
177
|
+
else:
|
|
178
|
+
if counter - 1 > 0 and is_int(var_names[counter - 1]):
|
|
179
|
+
index = int(var_names[counter - 1])
|
|
180
|
+
new_value = {var_name: value}
|
|
181
|
+
if index in range(len(start_var)):
|
|
182
|
+
start_var[index].update(new_value)
|
|
183
|
+
else:
|
|
184
|
+
while len(start_var) < index:
|
|
185
|
+
start_var.append({})
|
|
186
|
+
start_var.append(new_value)
|
|
187
|
+
start_var = start_var[index]
|
|
188
|
+
else:
|
|
189
|
+
if isinstance(start_var, dict):
|
|
190
|
+
if counter + 1 == len(var_names):
|
|
191
|
+
start_var[var_name] = value
|
|
192
|
+
else:
|
|
193
|
+
start_var[var_name] = {}
|
|
194
|
+
start_var = start_var[var_name]
|
|
195
|
+
|
|
196
|
+
counter += 1
|
|
197
|
+
|
|
198
|
+
params.update(new_params)
|
|
199
|
+
|
|
200
|
+
content_length = await self.get_content_length()
|
|
201
|
+
if method != Constant.TINA4_GET:
|
|
202
|
+
body, files = await self.get_content_body(content_length)
|
|
203
|
+
else:
|
|
204
|
+
body = None
|
|
205
|
+
files = None
|
|
206
|
+
|
|
207
|
+
request = {"params": params, "body": body, "files": files, "raw_data": self.request, "url": self.path, "session": self.session,
|
|
208
|
+
"headers": self.lowercase_headers, "raw_request": self.request_raw, "raw_content": self.content_raw,
|
|
209
|
+
"transport": transport, "asgi_response": asgi_response}
|
|
210
|
+
|
|
211
|
+
tina4_python.tina4_current_request = request
|
|
212
|
+
|
|
213
|
+
response = await self.router_handler.resolve(method, self.path, request, self.lowercase_headers, self.session)
|
|
214
|
+
|
|
215
|
+
if HTTP_REDIRECT != response.http_code:
|
|
216
|
+
self.send_header("Content-Type", response.content_type, headers)
|
|
217
|
+
await self.send_basic_headers(headers)
|
|
218
|
+
|
|
219
|
+
if os.getenv("TINA4_SESSION", "PY_SESS") in self.cookies:
|
|
220
|
+
self.send_header("Set-Cookie",
|
|
221
|
+
os.getenv("TINA4_SESSION", "PY_SESS") + '=' + self.cookies[
|
|
222
|
+
os.getenv("TINA4_SESSION", "PY_SESS")], headers)
|
|
223
|
+
|
|
224
|
+
# add the custom headers from the response
|
|
225
|
+
for response_header in response.headers:
|
|
226
|
+
self.send_header(response_header, response.headers[response_header], headers)
|
|
227
|
+
|
|
228
|
+
if asgi_response:
|
|
229
|
+
return response, headers
|
|
230
|
+
|
|
231
|
+
headers = await self.get_headers(headers, self.response_protocol, response.http_code)
|
|
232
|
+
|
|
233
|
+
if isinstance(response.content, str):
|
|
234
|
+
return headers + response.content.encode()
|
|
235
|
+
else:
|
|
236
|
+
return headers + response.content
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def send_header(header, value, headers):
|
|
240
|
+
headers.append(header + ": " + value)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
async def get_headers(response_headers, response_protocol, response_code):
|
|
244
|
+
headers = response_protocol + " " + str(response_code) + " " + Constant.LOOKUP_HTTP_CODE[
|
|
245
|
+
response_code] + "\r\n"
|
|
246
|
+
for header in response_headers:
|
|
247
|
+
headers += header + "\r\n"
|
|
248
|
+
headers += "\r\n"
|
|
249
|
+
|
|
250
|
+
return headers.encode()
|
|
251
|
+
|
|
252
|
+
async def run_server(self):
|
|
253
|
+
self.server = await asyncio.start_server(self.handle_client, self.host_name, self.port)
|
|
254
|
+
await self.server.serve_forever()
|
|
255
|
+
|
|
256
|
+
async def get_data(self, reader):
|
|
257
|
+
try:
|
|
258
|
+
raw_data = await reader.readuntil(b"\r\n\r\n")
|
|
259
|
+
except Exception:
|
|
260
|
+
raw_data = await reader.read(128)
|
|
261
|
+
|
|
262
|
+
protocol = raw_data.decode("utf-8").split("\r\n", 1)[0]
|
|
263
|
+
header_array = raw_data.decode("utf-8").split("\r\n\r\n")[0]
|
|
264
|
+
header_array = header_array.split("\r\n")
|
|
265
|
+
headers = {}
|
|
266
|
+
for header in header_array:
|
|
267
|
+
split = header.split(":", 1)
|
|
268
|
+
if len(split) == 2:
|
|
269
|
+
headers[split[0]] = split[1].strip()
|
|
270
|
+
|
|
271
|
+
lowercase_headers = {k.lower(): v for k, v in headers.items()}
|
|
272
|
+
content = ""
|
|
273
|
+
content_data = b''
|
|
274
|
+
if "content-length" in lowercase_headers:
|
|
275
|
+
content_length = int(lowercase_headers["content-length"])
|
|
276
|
+
count = 0
|
|
277
|
+
read_size = 64
|
|
278
|
+
content_data = b''
|
|
279
|
+
while len(content_data) < content_length:
|
|
280
|
+
read = await reader.read(read_size)
|
|
281
|
+
count += len(read)
|
|
282
|
+
content_data += read
|
|
283
|
+
raw_data += read
|
|
284
|
+
# print('COUNT', count, len(read))
|
|
285
|
+
if len(read) < read_size and len(content_data) == content_length:
|
|
286
|
+
break
|
|
287
|
+
try:
|
|
288
|
+
content = content_data.decode("utf-8")
|
|
289
|
+
except Exception: # probably binary or multipart form?
|
|
290
|
+
content = content_data
|
|
291
|
+
|
|
292
|
+
return protocol, headers, lowercase_headers, content, raw_data, content_data
|
|
293
|
+
|
|
294
|
+
async def handle_client(self, reader, writer):
|
|
295
|
+
try:
|
|
296
|
+
# Get the client request
|
|
297
|
+
protocol, headers_list, lowercase_headers, request, request_raw, content_raw = await self.get_data(reader)
|
|
298
|
+
# Strange blank request ?
|
|
299
|
+
if protocol == '':
|
|
300
|
+
return
|
|
301
|
+
# Decode the request
|
|
302
|
+
self.request_raw = request_raw
|
|
303
|
+
self.content_raw = content_raw
|
|
304
|
+
self.request = request
|
|
305
|
+
self.headers = headers_list
|
|
306
|
+
self.lowercase_headers = lowercase_headers
|
|
307
|
+
|
|
308
|
+
protocol = protocol.split(" ")
|
|
309
|
+
# print(protocol, headers_list)
|
|
310
|
+
self.method = protocol[0]
|
|
311
|
+
self.path = protocol[1]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
method_list = [Constant.TINA4_GET, Constant.TINA4_DELETE, Constant.TINA4_PUT, Constant.TINA4_ANY,
|
|
315
|
+
Constant.TINA4_POST, Constant.TINA4_PATCH, Constant.TINA4_OPTIONS]
|
|
316
|
+
|
|
317
|
+
contains_method = [ele for ele in method_list if (ele in self.method)]
|
|
318
|
+
|
|
319
|
+
request_handled = False
|
|
320
|
+
# return static content asap
|
|
321
|
+
if self.method == "GET" and "sec-websocket-key" not in self.lowercase_headers:
|
|
322
|
+
# split URL and extract query string
|
|
323
|
+
url = Router.clean_url(self.path)
|
|
324
|
+
url_parts = url.split('?')
|
|
325
|
+
url = url_parts[0]
|
|
326
|
+
|
|
327
|
+
# Serve statics
|
|
328
|
+
static_file = tina4_python.root_path + os.sep + "src" + os.sep + "public" + url.replace("/", os.sep)
|
|
329
|
+
Debug.info("Attempting to serve static file: " + static_file)
|
|
330
|
+
if os.path.isfile(static_file):
|
|
331
|
+
mime_type = mimetypes.guess_type(url)[0]
|
|
332
|
+
with open(static_file, 'rb') as file:
|
|
333
|
+
headers = []
|
|
334
|
+
self.send_header("Content-Type", mime_type, headers)
|
|
335
|
+
await self.send_basic_headers(headers)
|
|
336
|
+
content = file.read()
|
|
337
|
+
headers = await self.get_headers(headers, self.response_protocol, HTTP_OK)
|
|
338
|
+
writer.write(headers + content)
|
|
339
|
+
await writer.drain()
|
|
340
|
+
if writer is not None:
|
|
341
|
+
writer.close()
|
|
342
|
+
request_handled = True
|
|
343
|
+
|
|
344
|
+
if not request_handled:
|
|
345
|
+
# parse cookies
|
|
346
|
+
cookie_list = {}
|
|
347
|
+
content = ""
|
|
348
|
+
if "cookie" in self.lowercase_headers:
|
|
349
|
+
cookie_list_temp = self.lowercase_headers["cookie"].split(";")
|
|
350
|
+
for cookie_value in cookie_list_temp:
|
|
351
|
+
cookie = cookie_value.split("=", 1)
|
|
352
|
+
cookie_list[cookie[0].strip()] = cookie[1].strip()
|
|
353
|
+
|
|
354
|
+
self.cookies = cookie_list
|
|
355
|
+
|
|
356
|
+
# initialize the session
|
|
357
|
+
self.session = Session(os.getenv("TINA4_SESSION", "PY_SESS"),
|
|
358
|
+
os.getenv("TINA4_SESSION_FOLDER", tina4_python.root_path + os.sep + "sessions"),
|
|
359
|
+
os.getenv("TINA4_SESSION_HANDLER", "SessionFileHandler")
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
if os.getenv("TINA4_SESSION", "PY_SESS") in self.cookies:
|
|
363
|
+
self.session.load(self.cookies[os.getenv("TINA4_SESSION", "PY_SESS")])
|
|
364
|
+
else:
|
|
365
|
+
self.cookies[os.getenv("TINA4_SESSION", "PY_SESS")] = self.session.start()
|
|
366
|
+
|
|
367
|
+
if "sec-websocket-key" not in self.lowercase_headers:
|
|
368
|
+
try:
|
|
369
|
+
if self.method != "" and contains_method:
|
|
370
|
+
content = await (self.get_response(self.method, writer))
|
|
371
|
+
if content != "":
|
|
372
|
+
writer.write(content)
|
|
373
|
+
await writer.drain()
|
|
374
|
+
writer.close()
|
|
375
|
+
except BrokenPipeError as e:
|
|
376
|
+
Debug.warning("Socket connection broken: " + str(e))
|
|
377
|
+
# socket got terminated
|
|
378
|
+
pass
|
|
379
|
+
else:
|
|
380
|
+
# for sockets
|
|
381
|
+
await (self.get_response(self.method, writer))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
except Exception as e:
|
|
385
|
+
error_string = tina4_python.global_exception_handler(e)
|
|
386
|
+
headers = []
|
|
387
|
+
await self.send_basic_headers(headers)
|
|
388
|
+
headers = await self.get_headers(headers, self.response_protocol, HTTP_SERVER_ERROR)
|
|
389
|
+
url = Router.clean_url(self.path)
|
|
390
|
+
|
|
391
|
+
content_type = "text/html"
|
|
392
|
+
if "content-type" in self.lowercase_headers:
|
|
393
|
+
content_type = self.lowercase_headers["content-type"].lower()
|
|
394
|
+
|
|
395
|
+
if content_type == "application/json":
|
|
396
|
+
html = json.dumps({"error": "500 - Internal Server Error",
|
|
397
|
+
"data": {"server": {"url": url}, "error_message": error_string}})
|
|
398
|
+
else:
|
|
399
|
+
html = Template.render_twig_template("errors/500.twig",
|
|
400
|
+
{"server": {"url": url}, "error_message": error_string})
|
|
401
|
+
|
|
402
|
+
writer.write(headers + html.encode())
|
|
403
|
+
await writer.drain()
|
|
404
|
+
writer.close()
|
|
405
|
+
|
|
406
|
+
def __init__(self, host_name, port):
|
|
407
|
+
self.content_raw = None
|
|
408
|
+
self.session = Session
|
|
409
|
+
self.cookies = {}
|
|
410
|
+
self.method = None
|
|
411
|
+
self.response_protocol = "HTTP/1.1"
|
|
412
|
+
self.headers = None
|
|
413
|
+
self.lowercase_headers = None
|
|
414
|
+
self.request = None
|
|
415
|
+
self.request_raw = None
|
|
416
|
+
self.path = None
|
|
417
|
+
self.server_socket = None
|
|
418
|
+
self.host_name = host_name
|
|
419
|
+
self.port = port
|
|
420
|
+
self.router_handler = None
|
|
421
|
+
self.running = False
|
|
422
|
+
self.server = None
|
|
423
|
+
|
|
424
|
+
async def serve_forever(self):
|
|
425
|
+
await self.run_server()
|
|
426
|
+
|
|
427
|
+
def server_close(self):
|
|
428
|
+
self.running = False
|
|
429
|
+
self.server_socket.close()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Tina4 - This is not a 4ramework.
|
|
3
|
+
# Copy-right 2007 - current Tina4
|
|
4
|
+
# License: MIT https://opensource.org/licenses/MIT
|
|
5
|
+
#
|
|
6
|
+
# flake8: noqa: E501
|
|
7
|
+
import importlib
|
|
8
|
+
import os
|
|
9
|
+
from asyncio.trsock import TransportSocket
|
|
10
|
+
|
|
11
|
+
from tina4_python.Debug import Debug
|
|
12
|
+
|
|
13
|
+
class Websocket:
|
|
14
|
+
"""
|
|
15
|
+
Websocket class which wraps simple_websocket library
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, request):
|
|
18
|
+
try:
|
|
19
|
+
self.server = importlib.import_module("simple_websocket")
|
|
20
|
+
self.request = request
|
|
21
|
+
|
|
22
|
+
except Exception as e:
|
|
23
|
+
Debug.error("Error creating Websocket, perhaps you need to install simple_websocket ?", e)
|
|
24
|
+
|
|
25
|
+
async def connection(self):
|
|
26
|
+
"""
|
|
27
|
+
Returns a websocket connection
|
|
28
|
+
:return:
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
if self.request.asgi_response:
|
|
32
|
+
connection = await self.server.AioServer.accept(asgi=self.request.transport)
|
|
33
|
+
else:
|
|
34
|
+
if os.name == "nt":
|
|
35
|
+
connection = await self.server.AioServer.accept(
|
|
36
|
+
sock=TransportSocket(self.request.transport.transport._sock), # not working properly
|
|
37
|
+
headers=self.request.headers
|
|
38
|
+
)
|
|
39
|
+
else:
|
|
40
|
+
connection = await self.server.AioServer.accept(
|
|
41
|
+
sock=self.request.transport.get_extra_info('socket').dup(),
|
|
42
|
+
headers=self.request.headers
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return connection
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
Debug.error("Could not establish a socket connection:", str(e))
|
|
49
|
+
return None
|