whoopapi 0.0.1__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.
- whoopapi/__init__.py +8 -0
- whoopapi/constants.py +319 -0
- whoopapi/logging.py +21 -0
- whoopapi/parsers/__init__.py +0 -0
- whoopapi/parsers/http_body.py +251 -0
- whoopapi/parsers/http_headers.py +160 -0
- whoopapi/protocol_handlers/__init__.py +3 -0
- whoopapi/protocol_handlers/http.py +162 -0
- whoopapi/protocol_handlers/websocket.py +239 -0
- whoopapi/responses.py +11 -0
- whoopapi/utilities.py +289 -0
- whoopapi/wrappers.py +193 -0
- whoopapi-0.0.1.dist-info/METADATA +17 -0
- whoopapi-0.0.1.dist-info/RECORD +17 -0
- whoopapi-0.0.1.dist-info/WHEEL +5 -0
- whoopapi-0.0.1.dist-info/licenses/LICENSE +22 -0
- whoopapi-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,160 @@
|
|
1
|
+
import re
|
2
|
+
from urllib.parse import parse_qs, urlparse
|
3
|
+
|
4
|
+
|
5
|
+
def strip_string(string: str):
|
6
|
+
return string.strip()
|
7
|
+
|
8
|
+
|
9
|
+
# def parse_header_line_(header_line):
|
10
|
+
# """
|
11
|
+
# Parses an HTTP header line with multiple components.
|
12
|
+
# Handles headers like:
|
13
|
+
# Content-Type: text/html; charset=utf-8
|
14
|
+
# Cache-Control: no-cache, no-store, must-revalidate
|
15
|
+
# """
|
16
|
+
#
|
17
|
+
# pattern = r"""
|
18
|
+
# ^(:?[^:\s]+) # Header name (group 1) - can start with colon for pseudo-headers
|
19
|
+
# \s*:\s* # Colon with optional whitespace
|
20
|
+
# ([^\x00]*) # Value (group 2) - no NUL characters allowed
|
21
|
+
# ((?:\s*[;,]\s*[^\x00;,]+)*)? # Additional parameters (group 3, optional)
|
22
|
+
# \s*$ # Trailing whitespace
|
23
|
+
# """
|
24
|
+
#
|
25
|
+
# match = re.fullmatch(pattern, header_line, re.VERBOSE)
|
26
|
+
# if not match:
|
27
|
+
# return None
|
28
|
+
#
|
29
|
+
# header_name = match.group(1).strip().lower()
|
30
|
+
# primary_value = match.group(2).strip()
|
31
|
+
#
|
32
|
+
# is_pseudo = header_name.startswith(":")
|
33
|
+
# params = {} if not is_pseudo else None
|
34
|
+
#
|
35
|
+
# if not is_pseudo and match.group(3):
|
36
|
+
# param_pairs = re.split(r"\s*[;,]\s*", match.group(3).strip())
|
37
|
+
# for pair in param_pairs:
|
38
|
+
# if "=" in pair:
|
39
|
+
# key, value = pair.split("=", 1)
|
40
|
+
# params[key.strip().lower()] = (
|
41
|
+
# value.strip()
|
42
|
+
# ) # Param names also lowercase
|
43
|
+
# elif pair:
|
44
|
+
# params[pair.lower()] = True
|
45
|
+
#
|
46
|
+
# return {
|
47
|
+
# "name": header_name,
|
48
|
+
# "value": primary_value,
|
49
|
+
# "params": params,
|
50
|
+
# "is_pseudo": is_pseudo,
|
51
|
+
# }
|
52
|
+
|
53
|
+
|
54
|
+
def parse_header_line(header_line):
|
55
|
+
"""
|
56
|
+
Parses an HTTP header line with multiple components.
|
57
|
+
Handles headers like:
|
58
|
+
Content-Type: text/html; charset=utf-8
|
59
|
+
Cache-Control: no-cache, no-store, must-revalidate
|
60
|
+
"""
|
61
|
+
|
62
|
+
pattern = r"""
|
63
|
+
^(:?[^:\s]+) # Header name (group 1) - can start with colon for pseudo-headers
|
64
|
+
\s*:\s* # Colon with optional whitespace
|
65
|
+
([^\x00;,]*) # Primary value (group 2) - stops at ; or ,
|
66
|
+
\s* # Optional whitespace
|
67
|
+
(?: # Non-capturing group for parameters
|
68
|
+
[;,]\s* # Parameter separator (; or ,) with optional whitespace
|
69
|
+
([^\x00]*) # All parameters (group 3)
|
70
|
+
)?
|
71
|
+
\s*$ # Trailing whitespace
|
72
|
+
"""
|
73
|
+
|
74
|
+
match = re.fullmatch(pattern, header_line, re.VERBOSE)
|
75
|
+
if not match:
|
76
|
+
return None
|
77
|
+
|
78
|
+
header_name = match.group(1).strip().lower()
|
79
|
+
primary_value = match.group(2).strip()
|
80
|
+
params_string = match.group(3).strip() if match.group(3) else ""
|
81
|
+
|
82
|
+
is_pseudo = header_name.startswith(":")
|
83
|
+
params = {} if not is_pseudo else None
|
84
|
+
|
85
|
+
# Parse parameters
|
86
|
+
if not is_pseudo and params_string:
|
87
|
+
# Split on semicolons or commas, but be careful with quoted values
|
88
|
+
param_pairs = re.split(r"\s*[;,]\s*", params_string)
|
89
|
+
for pair in param_pairs:
|
90
|
+
if not pair:
|
91
|
+
continue
|
92
|
+
if "=" in pair:
|
93
|
+
key, value = pair.split("=", 1)
|
94
|
+
# Remove quotes if present
|
95
|
+
value = value.strip()
|
96
|
+
if (value.startswith('"') and value.endswith('"')) or (
|
97
|
+
value.startswith("'") and value.endswith("'")
|
98
|
+
):
|
99
|
+
value = value[1:-1]
|
100
|
+
params[key.strip().lower()] = value
|
101
|
+
elif pair:
|
102
|
+
params[pair.lower()] = True
|
103
|
+
|
104
|
+
return {
|
105
|
+
"name": header_name,
|
106
|
+
"value": primary_value,
|
107
|
+
"params": params,
|
108
|
+
"is_pseudo": is_pseudo,
|
109
|
+
}
|
110
|
+
|
111
|
+
|
112
|
+
def parse_headers(data: bytes):
|
113
|
+
ENCODING = "utf-8"
|
114
|
+
HEADER_BREAK = "\r\n"
|
115
|
+
stringified = str(data, ENCODING)
|
116
|
+
entries = stringified.split(HEADER_BREAK)
|
117
|
+
start_line = entries[0].strip()
|
118
|
+
split_start_line = start_line.split(" ")
|
119
|
+
request_info = {}
|
120
|
+
|
121
|
+
if len(split_start_line) > 2:
|
122
|
+
request_info["method"] = strip_string(split_start_line[0])
|
123
|
+
|
124
|
+
request_path = strip_string(split_start_line[1])
|
125
|
+
parsed_path = urlparse(url=request_path)
|
126
|
+
request_info["path"] = parsed_path.path
|
127
|
+
|
128
|
+
query_params = parse_qs(parsed_path.query)
|
129
|
+
request_info["query_params"] = {
|
130
|
+
k: v[0] if len(v) == 1 else v for k, v in query_params.items()
|
131
|
+
}
|
132
|
+
|
133
|
+
split_version_info = strip_string(split_start_line[2]).split("/")
|
134
|
+
request_info["protocol"] = strip_string(strip_string(split_version_info[0]))
|
135
|
+
request_info["protocol_version"] = strip_string(split_version_info[1])
|
136
|
+
|
137
|
+
else:
|
138
|
+
raise Exception("Invalid headers.")
|
139
|
+
|
140
|
+
entries = entries[1:]
|
141
|
+
headers = {}
|
142
|
+
header_params = {}
|
143
|
+
|
144
|
+
for entry in entries:
|
145
|
+
parsed_line = parse_header_line(header_line=entry)
|
146
|
+
if not parsed_line:
|
147
|
+
continue
|
148
|
+
|
149
|
+
key = parsed_line["name"]
|
150
|
+
value = parsed_line["value"]
|
151
|
+
details = parsed_line["params"]
|
152
|
+
header_params[key] = details
|
153
|
+
|
154
|
+
headers[key] = value
|
155
|
+
|
156
|
+
return {
|
157
|
+
"request_info": request_info,
|
158
|
+
"headers": headers,
|
159
|
+
"header_params": header_params,
|
160
|
+
}
|
@@ -0,0 +1,162 @@
|
|
1
|
+
import inspect
|
2
|
+
import os
|
3
|
+
from typing import Any, Callable, Optional
|
4
|
+
|
5
|
+
from ..constants import HttpContentTypes, HttpHeaders, HttpStatusCodes
|
6
|
+
from ..logging import LOG_ERROR, LOG_INFO
|
7
|
+
from ..responses import DEFAULT_404_PAGE
|
8
|
+
from ..wrappers import HttpRequest, HttpResponse
|
9
|
+
|
10
|
+
|
11
|
+
class RequestHandler:
|
12
|
+
def __init__(self):
|
13
|
+
self.route = ""
|
14
|
+
|
15
|
+
def get_handler_for_method_(self, method: str) -> Callable[[HttpRequest], Any]:
|
16
|
+
handler_ = {
|
17
|
+
"get": self.get,
|
18
|
+
"post": self.post,
|
19
|
+
"put": self.put,
|
20
|
+
"patch": self.patch,
|
21
|
+
"delete": self.delete,
|
22
|
+
}.get(method, None)
|
23
|
+
|
24
|
+
if not handler_:
|
25
|
+
raise Exception(f"Unable to get handler for method : {method}.")
|
26
|
+
|
27
|
+
return handler_
|
28
|
+
|
29
|
+
def get(self, request: HttpRequest) -> Optional[Any]:
|
30
|
+
pass
|
31
|
+
|
32
|
+
def post(self, request: HttpRequest) -> Optional[Any]:
|
33
|
+
pass
|
34
|
+
|
35
|
+
def put(self, request: HttpRequest) -> Optional[Any]:
|
36
|
+
pass
|
37
|
+
|
38
|
+
def patch(self, request: HttpRequest) -> Optional[Any]:
|
39
|
+
pass
|
40
|
+
|
41
|
+
def delete(self, request: HttpRequest) -> Optional[Any]:
|
42
|
+
pass
|
43
|
+
|
44
|
+
|
45
|
+
def path_matches_route(path: str, route: str):
|
46
|
+
route_ = route.strip("/ ")
|
47
|
+
path_ = path.strip("/ ")
|
48
|
+
return path_.startswith(route_)
|
49
|
+
|
50
|
+
|
51
|
+
def handle_http_client_request(
|
52
|
+
request: HttpRequest,
|
53
|
+
middlewares: list[Callable],
|
54
|
+
http_routes: list[tuple[str, Callable | RequestHandler]],
|
55
|
+
log_handler=True,
|
56
|
+
):
|
57
|
+
for action in middlewares:
|
58
|
+
action(request)
|
59
|
+
|
60
|
+
response = None
|
61
|
+
wrapped_response = None
|
62
|
+
request_method = request.method.lower()
|
63
|
+
request_path = request.path
|
64
|
+
request_protocol = request.protocol
|
65
|
+
response_code = HttpStatusCodes.C_200
|
66
|
+
|
67
|
+
handler_found = False
|
68
|
+
for route, handler_function in http_routes:
|
69
|
+
if isinstance(handler_function, RequestHandler):
|
70
|
+
handler = handler_function
|
71
|
+
handler.route = route
|
72
|
+
handler_function_ = handler_function.get_handler_for_method_(
|
73
|
+
method=request_method
|
74
|
+
)
|
75
|
+
|
76
|
+
elif inspect.isclass(handler_function) and issubclass(
|
77
|
+
handler_function, RequestHandler
|
78
|
+
):
|
79
|
+
handler = handler_function()
|
80
|
+
handler.route = route
|
81
|
+
handler_function_ = handler.get_handler_for_method_(method=request_method)
|
82
|
+
|
83
|
+
elif inspect.isfunction(handler_function):
|
84
|
+
handler_function_ = handler_function
|
85
|
+
|
86
|
+
else:
|
87
|
+
raise Exception(
|
88
|
+
"Invalid websocket handler. Must be Class_(RequestHandler), or instance of."
|
89
|
+
)
|
90
|
+
|
91
|
+
if path_matches_route(path=request_path, route=route):
|
92
|
+
handler_found = True
|
93
|
+
|
94
|
+
try:
|
95
|
+
response = handler_function_(request)
|
96
|
+
|
97
|
+
except Exception as e:
|
98
|
+
LOG_ERROR(e)
|
99
|
+
response = HttpResponse()
|
100
|
+
response.set_status_code(HttpStatusCodes.C_500)
|
101
|
+
response.set_html(DEFAULT_404_PAGE)
|
102
|
+
response_code = response.status_code
|
103
|
+
|
104
|
+
break
|
105
|
+
|
106
|
+
if isinstance(response, HttpResponse):
|
107
|
+
response_code = response.status_code
|
108
|
+
wrapped_response = response
|
109
|
+
|
110
|
+
elif isinstance(response, str):
|
111
|
+
text_response = HttpResponse()
|
112
|
+
text_response.set_header(HttpHeaders.CONTENT_TYPE, HttpContentTypes.TEXT_PLAIN)
|
113
|
+
text_response.set_body(response)
|
114
|
+
wrapped_response = text_response
|
115
|
+
|
116
|
+
elif isinstance(response, dict) or isinstance(response, list):
|
117
|
+
json_response = HttpResponse()
|
118
|
+
json_response.set_json(response)
|
119
|
+
wrapped_response = json_response
|
120
|
+
|
121
|
+
elif (not handler_found) or (not response):
|
122
|
+
response = HttpResponse()
|
123
|
+
response.set_status_code(HttpStatusCodes.C_404)
|
124
|
+
response.set_html(DEFAULT_404_PAGE)
|
125
|
+
response_code = response.status_code
|
126
|
+
wrapped_response = response
|
127
|
+
|
128
|
+
if log_handler:
|
129
|
+
log_message = f"{request_method.upper()} {request_protocol.upper()}://{request.host}{request_path} {response_code}"
|
130
|
+
LOG_INFO(log_message)
|
131
|
+
|
132
|
+
return wrapped_response
|
133
|
+
|
134
|
+
|
135
|
+
class StaticFileHandler(RequestHandler):
|
136
|
+
def __init__(self, directories: list[str] = None):
|
137
|
+
super().__init__()
|
138
|
+
self.directories = directories or []
|
139
|
+
|
140
|
+
def get_file_path(self, path: str):
|
141
|
+
file_path = path[len(self.route) + 1 :]
|
142
|
+
|
143
|
+
for directory in self.directories:
|
144
|
+
file_path = os.path.join(directory, file_path)
|
145
|
+
if os.path.exists(file_path):
|
146
|
+
return file_path
|
147
|
+
|
148
|
+
return None
|
149
|
+
|
150
|
+
def get(self, request: HttpRequest):
|
151
|
+
file_path = self.get_file_path(request.path)
|
152
|
+
if file_path:
|
153
|
+
file = open(file_path, "rb")
|
154
|
+
data = file.read()
|
155
|
+
file.close()
|
156
|
+
response = HttpResponse()
|
157
|
+
response.set_file(f"{file_path.split(os.path.sep)[-1]}", data)
|
158
|
+
|
159
|
+
return response
|
160
|
+
|
161
|
+
else:
|
162
|
+
return None
|
@@ -0,0 +1,239 @@
|
|
1
|
+
import base64 as BASE64
|
2
|
+
import hashlib as HASHLIB
|
3
|
+
import inspect
|
4
|
+
import socket as SOCKET
|
5
|
+
import struct as STRUCT
|
6
|
+
from typing import Callable
|
7
|
+
|
8
|
+
from ..constants import (
|
9
|
+
DEFAULT_STRING_ENCODING,
|
10
|
+
WEBSOCKET_ACCEPT_SUFFIX,
|
11
|
+
HttpHeaders,
|
12
|
+
HttpStatusCodes,
|
13
|
+
)
|
14
|
+
from ..logging import LOG_INFO
|
15
|
+
from ..wrappers import HttpRequest, HttpResponse
|
16
|
+
|
17
|
+
|
18
|
+
def mask_data(mask: bytes, data: bytes):
|
19
|
+
masked_data = b""
|
20
|
+
|
21
|
+
for index in range(0, len(data)):
|
22
|
+
x = data[index]
|
23
|
+
y = mask[index % 4]
|
24
|
+
masked_data = masked_data + STRUCT.pack("B", x ^ y)
|
25
|
+
|
26
|
+
return masked_data
|
27
|
+
|
28
|
+
|
29
|
+
def send_websocket_message(socket: SOCKET.socket, message: bytes | str):
|
30
|
+
frame_size = 64
|
31
|
+
# mask=RANDOM.randbytes(4)
|
32
|
+
payloads = []
|
33
|
+
index = 0
|
34
|
+
|
35
|
+
while True:
|
36
|
+
end = index + frame_size
|
37
|
+
if end >= len(message):
|
38
|
+
end = len(message)
|
39
|
+
payloads.append(message[index:end])
|
40
|
+
|
41
|
+
break
|
42
|
+
|
43
|
+
else:
|
44
|
+
payloads.append(message[index:end])
|
45
|
+
|
46
|
+
index = end
|
47
|
+
|
48
|
+
frames = []
|
49
|
+
for payload_index in range(0, len(payloads)):
|
50
|
+
payload = payloads[payload_index]
|
51
|
+
if isinstance(payload, str):
|
52
|
+
payload = bytes(payload, DEFAULT_STRING_ENCODING)
|
53
|
+
# payload=mask_data(mask,payload)
|
54
|
+
fin_bit = 0x8000 if payload_index >= len(payloads) - 1 else 0x0000
|
55
|
+
rsv_bits = 0x0000
|
56
|
+
|
57
|
+
if payload_index == 0:
|
58
|
+
opcode_bits = 0x0200 if isinstance(message, bytes) else 0x0100
|
59
|
+
|
60
|
+
else:
|
61
|
+
opcode_bits = 0x0000
|
62
|
+
# opcode_bits=0x0200 if payload_index==0 else 0x0000
|
63
|
+
# mask_bit=0x0080
|
64
|
+
|
65
|
+
mask_bit = 0x0000
|
66
|
+
header = fin_bit | rsv_bits | opcode_bits | mask_bit | len(payload)
|
67
|
+
frame = STRUCT.pack(">H", header)
|
68
|
+
# frame=frame+mask+payload
|
69
|
+
frame = frame + payload
|
70
|
+
frames.append(frame)
|
71
|
+
|
72
|
+
for frame in frames:
|
73
|
+
socket.sendall(frame)
|
74
|
+
|
75
|
+
|
76
|
+
def read_websocket_message(socket: SOCKET.socket):
|
77
|
+
buffer = b""
|
78
|
+
|
79
|
+
while True:
|
80
|
+
header = socket.recv(2)
|
81
|
+
if header and len(header) == 2:
|
82
|
+
header = STRUCT.unpack(">H", header)[0]
|
83
|
+
fin = header >> 15
|
84
|
+
# opcode = (header << 4) >> 16
|
85
|
+
# rsv = (header << 1) >> 13
|
86
|
+
mask = (header << 8) >> 15
|
87
|
+
payload_length = (header << 9) >> 9
|
88
|
+
data_mask = None
|
89
|
+
frame_data = None
|
90
|
+
|
91
|
+
if mask > 0:
|
92
|
+
data_mask = socket.recv(4)
|
93
|
+
if not data_mask:
|
94
|
+
pass
|
95
|
+
|
96
|
+
if payload_length > 0:
|
97
|
+
frame_data = socket.recv(payload_length)
|
98
|
+
if frame_data:
|
99
|
+
if mask and data_mask:
|
100
|
+
decoded_frame_data = mask_data(data_mask, frame_data)
|
101
|
+
buffer = buffer + decoded_frame_data
|
102
|
+
|
103
|
+
else:
|
104
|
+
buffer = buffer + frame_data
|
105
|
+
|
106
|
+
else:
|
107
|
+
pass
|
108
|
+
|
109
|
+
if fin > 0:
|
110
|
+
break
|
111
|
+
|
112
|
+
else:
|
113
|
+
pass
|
114
|
+
|
115
|
+
return buffer
|
116
|
+
|
117
|
+
|
118
|
+
class WebsocketHandler:
|
119
|
+
def __init__(self):
|
120
|
+
self.route = ""
|
121
|
+
self.DEFAULT_ENCODING = "utf-8"
|
122
|
+
self.socket = None
|
123
|
+
self.running = False
|
124
|
+
|
125
|
+
def set_socket(self, socket: SOCKET.socket):
|
126
|
+
self.socket = socket
|
127
|
+
|
128
|
+
def close(self):
|
129
|
+
try:
|
130
|
+
self.socket.close()
|
131
|
+
self.on_close()
|
132
|
+
|
133
|
+
except Exception as e:
|
134
|
+
LOG_INFO(e)
|
135
|
+
|
136
|
+
def run(self, timeout=None):
|
137
|
+
if not self.socket:
|
138
|
+
raise Exception("Socket not set.")
|
139
|
+
|
140
|
+
while True:
|
141
|
+
try:
|
142
|
+
message = read_websocket_message(self.socket)
|
143
|
+
if message:
|
144
|
+
self.on_message(message)
|
145
|
+
|
146
|
+
except Exception as e:
|
147
|
+
self.on_error(e)
|
148
|
+
self.close()
|
149
|
+
|
150
|
+
def send(self, message: str | bytes):
|
151
|
+
try:
|
152
|
+
send_websocket_message(self.socket, message)
|
153
|
+
|
154
|
+
except Exception as e:
|
155
|
+
self.on_error(e)
|
156
|
+
self.close()
|
157
|
+
|
158
|
+
def on_connect(self, request: HttpRequest):
|
159
|
+
pass
|
160
|
+
|
161
|
+
def on_message(self, message: bytes):
|
162
|
+
pass
|
163
|
+
|
164
|
+
def on_close(self):
|
165
|
+
pass
|
166
|
+
|
167
|
+
def on_error(self, exception):
|
168
|
+
pass
|
169
|
+
|
170
|
+
|
171
|
+
def path_matches_route(path: str, route: str):
|
172
|
+
route_ = route.strip("/ ")
|
173
|
+
path_ = path.strip("/ ")
|
174
|
+
return path_.startswith(route_)
|
175
|
+
|
176
|
+
|
177
|
+
def handle_websocket_client_request(
|
178
|
+
socket: SOCKET.socket,
|
179
|
+
request: HttpRequest,
|
180
|
+
middlewares: list[Callable],
|
181
|
+
websocket_routes: list[tuple[str, Callable | WebsocketHandler]],
|
182
|
+
):
|
183
|
+
request_path = request.path
|
184
|
+
handler_found = False
|
185
|
+
|
186
|
+
for action in middlewares:
|
187
|
+
action(request)
|
188
|
+
|
189
|
+
for route, handler_function in websocket_routes:
|
190
|
+
if isinstance(handler_function, WebsocketHandler):
|
191
|
+
handler = handler_function
|
192
|
+
|
193
|
+
elif inspect.isclass(handler_function) and issubclass(
|
194
|
+
handler_function, WebsocketHandler
|
195
|
+
):
|
196
|
+
handler = handler_function()
|
197
|
+
|
198
|
+
else:
|
199
|
+
raise Exception(
|
200
|
+
"Invalid websocket handler. Must be Class_(WebsocketHandler), or instance of."
|
201
|
+
)
|
202
|
+
|
203
|
+
if path_matches_route(path=request_path, route=route):
|
204
|
+
handler_found = True
|
205
|
+
handler.route = route
|
206
|
+
handler.set_socket(socket)
|
207
|
+
handler.on_connect(request)
|
208
|
+
handler.run(timeout=1000)
|
209
|
+
|
210
|
+
if not handler_found:
|
211
|
+
socket.close()
|
212
|
+
|
213
|
+
|
214
|
+
def generate_websocket_accept_key(websocket_key: str):
|
215
|
+
accept_key = f"{websocket_key}{WEBSOCKET_ACCEPT_SUFFIX}"
|
216
|
+
accept_key = HASHLIB.sha1(accept_key.encode()).digest()
|
217
|
+
accept_key = BASE64.b64encode(accept_key).decode("utf-8")
|
218
|
+
|
219
|
+
return accept_key
|
220
|
+
|
221
|
+
|
222
|
+
def perform_websocket_handshake(socket: SOCKET.socket, headers: dict):
|
223
|
+
websocket_key = headers.get(HttpHeaders.SEC_WEBSOCKET_KEY, "")
|
224
|
+
# websocket_version = headers.get(CONSTANTS.HttpHeaders.SEC_WEBSOCKET_VERSION, 13)
|
225
|
+
|
226
|
+
if websocket_key:
|
227
|
+
accept_key = generate_websocket_accept_key(websocket_key)
|
228
|
+
response = HttpResponse()
|
229
|
+
response.set_header(HttpHeaders.CONNECTION, "Upgrade")
|
230
|
+
response.set_header(HttpHeaders.UPGRADE, "websocket")
|
231
|
+
response.set_header(HttpHeaders.SEC_WEBSOCKET_ACCEPT, accept_key)
|
232
|
+
response.set_status_code(HttpStatusCodes.C_101)
|
233
|
+
result = response.build()
|
234
|
+
socket.sendall(result)
|
235
|
+
|
236
|
+
return True
|
237
|
+
|
238
|
+
else:
|
239
|
+
return False
|
whoopapi/responses.py
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
DEFAULT_404_PAGE = """
|
2
|
+
<div style="display:flex;flex-direction:row;justify-content:center;align-items:center;margin:50px">
|
3
|
+
<h1><b>404 Error! This resource is not available.</b></h1>
|
4
|
+
</div>
|
5
|
+
"""
|
6
|
+
|
7
|
+
DEFAULT_500_PAGE = """
|
8
|
+
<div style="display:flex;flex-direction:row;justify-content:center;align-items:center;margin:50px">
|
9
|
+
<h1><b>500 Error! Internal server error.</b></h1>
|
10
|
+
</div>
|
11
|
+
"""
|