ghttp-tool 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.
- ghttp/__init__.py +14 -0
- ghttp/client.py +134 -0
- ghttp/parsearg.py +43 -0
- ghttp/server.py +119 -0
- ghttp/simple_client.py +196 -0
- ghttp/simple_server.py +85 -0
- ghttp/transfer_encoding.py +26 -0
- ghttp_tool-0.0.1.dist-info/METADATA +37 -0
- ghttp_tool-0.0.1.dist-info/RECORD +12 -0
- ghttp_tool-0.0.1.dist-info/WHEEL +5 -0
- ghttp_tool-0.0.1.dist-info/licenses/LICENSE +0 -0
- ghttp_tool-0.0.1.dist-info/top_level.txt +1 -0
ghttp/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GHTTP By GPGStudio created for easy work with http,
|
|
3
|
+
See everything in "README"
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .parsearg import parsearg, parse, unparse
|
|
7
|
+
from .server import server, tserver
|
|
8
|
+
from .client import client
|
|
9
|
+
from .transfer_encoding import read as transfer_encoding
|
|
10
|
+
from .simple_client import get, head, post, put, delete, connect, options, trace, patch, Session, parseurl
|
|
11
|
+
from .simple_server import serve
|
|
12
|
+
|
|
13
|
+
__all__ = ['parsearg','parse','unparse','server','tserver','client','transfer_encoding','get','head','post','put','delete','connect','options','trace','patch','Session','serve','parseurl']
|
|
14
|
+
__version__ = '0.0.1'
|
ghttp/client.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from .transfer_encoding import read as read_encoding
|
|
2
|
+
import socket
|
|
3
|
+
|
|
4
|
+
def read(s):
|
|
5
|
+
try:
|
|
6
|
+
data = s.recv(65500)
|
|
7
|
+
while b'\r\n\r\n' not in data:
|
|
8
|
+
chunk = s.recv(65500)
|
|
9
|
+
if chunk == b'':
|
|
10
|
+
break
|
|
11
|
+
data += chunk
|
|
12
|
+
except: data = b''
|
|
13
|
+
if not data == b'':
|
|
14
|
+
data_split = data.find(b'\r\n\r\n')
|
|
15
|
+
header, dta = data[:data_split], data[data_split+4:]
|
|
16
|
+
headers = {}
|
|
17
|
+
h = header.split(b'\r\n')
|
|
18
|
+
answer = h[0].split(b' ',1)
|
|
19
|
+
answer = answer[0].decode(), answer[1].decode()
|
|
20
|
+
for i in h[1:]:
|
|
21
|
+
key, value = i.split(b':',1)
|
|
22
|
+
key = key.decode(errors='replace')
|
|
23
|
+
value = value.lstrip(b' ').decode(errors='replace')
|
|
24
|
+
headers[key.lower()] = int(value) if value.isdigit() else value
|
|
25
|
+
if 'transfer-encoding' in headers:
|
|
26
|
+
dta = read_encoding(s,dta,headers['transfer-encoding'])
|
|
27
|
+
elif 'content-length' in headers:
|
|
28
|
+
toread = headers['content-length']
|
|
29
|
+
toread = int(toread) - len(dta)
|
|
30
|
+
while toread > 0:
|
|
31
|
+
chunk = s.recv(toread if toread < 65500 else 65500)
|
|
32
|
+
dta += chunk
|
|
33
|
+
toread -= len(chunk)
|
|
34
|
+
else:
|
|
35
|
+
s.settimeout(0.5)
|
|
36
|
+
while True:
|
|
37
|
+
try: chunk = s.recv(65500)
|
|
38
|
+
except: break
|
|
39
|
+
if chunk == b'':
|
|
40
|
+
break
|
|
41
|
+
dta += chunk
|
|
42
|
+
s.settimeout(5.0)
|
|
43
|
+
return answer, dta, headers
|
|
44
|
+
else:
|
|
45
|
+
return [None, None], None, {}
|
|
46
|
+
|
|
47
|
+
def send(s, method='GET', path='/', ver='1.1', data=b'', headers={}, without_len=False, encoding='utf-8'):
|
|
48
|
+
if type(data)==str:
|
|
49
|
+
data = data.encode(encoding=encoding)
|
|
50
|
result = f"{method} {path} HTTP/{ver}\x0d\x0a"
|
|
51
|
+
for key, value in headers.items():
|
|
1
52
|
result += f"{key}: {value}\x0d\x0a"
|
|
2
53
|
if not without_len:
|
|
3
54
|
if not "\ncontent-length: " in result.lower():
|
|
4
55
|
result += f"Content-Length: {len(data)}\x0d\x0a"
|
|
5
56
|
result += "\x0d\x0a"
|
|
6
57
|
data = result.encode() + data
|
|
7
58
|
s.sendall(data)
|
|
59
|
+
|
|
60
|
+
def send_raw(conn, data):
|
|
61
|
+
if type(data)==bytes:
|
|
62
|
+
conn.sendall(data)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("data must be bytes!")
|
|
65
|
+
|
|
66
|
+
def read_chunk(conn):
|
|
67
|
+
try:
|
|
68
|
+
chunk_size_hex = b''
|
|
69
|
+
while True:
|
|
70
|
+
char = conn.recv(1)
|
|
71
|
+
if not char:
|
|
72
|
+
return None
|
|
73
|
+
if char == b'\n':
|
|
74
|
+
break
|
|
75
|
+
chunk_size_hex += char
|
|
76
|
+
if chunk_size_hex.endswith(b'\r'):
|
|
77
|
+
chunk_size_hex = chunk_size_hex[:-1]
|
|
78
|
+
try:
|
|
79
|
+
chunk_size = int(chunk_size_hex, 16)
|
|
80
|
+
except ValueError:
|
|
81
|
+
return None
|
|
82
|
+
if chunk_size == 0:
|
|
83
|
+
conn.recv(2)
|
|
84
|
+
return (b'', 0)
|
|
85
|
+
data = b''
|
|
86
|
+
while len(data) < chunk_size:
|
|
87
|
+
remaining = chunk_size - len(data)
|
|
88
|
+
chunk = conn.recv(min(remaining, 8192))
|
|
89
|
+
if not chunk:
|
|
90
|
+
return None
|
|
91
|
+
data += chunk
|
|
92
|
+
conn.recv(2)
|
|
93
|
+
return data, chunk_size
|
|
94
|
+
except Exception:
|
|
95
|
+
return None, None
|
|
96
|
+
|
|
97
|
+
def send_chunk(conn, data):
|
|
98
|
+
if type(data)==bytes:
|
|
99
|
+
conn.sendall(hex(len(data))[2:].encode() + b'\r\n' + data + b'\r\n')
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError("data must be bytes!")
|
|
102
|
+
|
|
103
|
+
def read_raw(s, data=None):
|
|
104
|
+
try: return s.recv(data)
|
|
105
|
+
except: return None
|
|
106
|
+
|
|
107
|
+
def close(s):
|
|
108
|
+
try: s.close()
|
|
109
|
+
except: pass
|
|
110
|
+
|
|
111
|
+
class client:
|
|
112
|
+
def __init__(self, VER='1.1'):
|
|
113
|
+
self.ver = VER
|
|
114
|
+
self.s = None
|
|
115
|
+
pass
|
|
116
|
+
def connect(self, IP='localhost', PORT=80, CUSTOM_SOCK=None):
|
|
117
|
+
try: self.s.close()
|
|
118
|
+
except: pass
|
|
119
|
+
try:
|
|
120
|
+
if CUSTOM_SOCK is None:
|
|
121
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
122
|
+
else:
|
|
123
|
+
s = CUSTOM_SOCK
|
|
124
|
+
s.settimeout(5.0)
|
|
125
|
+
s.connect((IP,PORT))
|
|
126
|
+
self.s = s
|
|
127
|
+
except: pass
|
|
128
|
+
def read(self):
|
|
129
|
+
return read(self.s)
|
|
130
|
+
def send(self, method='GET', path='/', data=b'', headers={}, without_len=False):
|
|
131
|
+
send(self.s, method, path, self.ver, data, headers, without_len)
|
|
132
|
+
def send_raw(self, data):
|
|
133
|
+
send_raw(self.s, data)
|
|
134
|
+
def send_chunk(self, data):
|
|
135
|
+
send_chunk(self.s, data)
|
|
136
|
+
def read_chunk(self):
|
|
137
|
+
return read_chunk(self.s)
|
|
138
|
+
def read_raw(self, data=None):
|
|
139
|
+
return read_raw(self.s, data)
|
|
140
|
+
def close(self):
|
|
141
|
+
close(self.s)
|
|
142
|
+
|
ghttp/parsearg.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
def parse(data, safe='1234567890-+_.~qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'):
|
|
2
|
+
value = ''
|
|
3
|
+
for i in data.encode():
|
|
4
|
+
if bytes([i]) in safe.encode():
|
|
5
|
+
value += bytes([i]).decode()
|
|
6
|
+
else:
|
|
7
|
+
value += f'%{bytes([i]).hex().upper()}'
|
|
8
|
+
return value
|
|
9
|
+
def unparse(data):
|
|
10
|
+
for i in range(0,256):
|
|
11
|
+
data = data.replace(f'%{bytes([i]).hex()}',chr(i)).replace(f'%{bytes([i]).hex().upper()}',chr(i))
|
|
12
|
+
return data
|
|
13
|
+
def parsearg(data,continius=None,encoding='utf-8',safe='1234567890-+_.~qwertyuiopasdfghjklzxcvbnm'):
|
|
14
|
+
if isinstance(continius, dict):
|
|
15
|
+
toret = '?'
|
|
16
|
+
for key, _value in continius.items():
|
|
17
|
+
value = parse(_value)
|
|
18
|
+
toret += f'{key}={value}&'
|
|
19
|
+
return data+toret[:-1] if toret[-1] == '&' else data
|
|
20
|
+
if isinstance(data, str) and continius is None:
|
|
21
|
+
if '?' in data:
|
|
22
|
+
link, args = data.split('?',1)
|
|
23
|
+
args_dict = {}
|
|
24
|
+
for i in args.split('&'):
|
|
25
|
+
key, value = i.split('=',1)
|
|
26
|
+
for i in range(0,256):
|
|
27
|
+
value = value.replace(f'%{bytes([i]).hex()}',chr(i)).replace(f'%{bytes([i]).hex().upper()}',chr(i))
|
|
28
|
+
value = value.encode('latin-1')
|
|
29
|
+
try: value = value.decode(encoding=encoding)
|
|
30
|
+
except: pass
|
|
31
|
+
if isinstance(value,str):
|
|
32
|
+
if value.lower() == 'true':
|
|
33
|
+
value = True
|
|
34
|
+
elif value.lower() == 'false':
|
|
35
|
+
value = False
|
|
36
|
+
elif value.isdigit():
|
|
37
|
+
value = int(value)
|
|
38
|
+
elif value.replace('.', '').isdigit():
|
|
39
|
+
value = float(value)
|
|
40
|
+
args_dict[key] = value
|
|
41
|
+
return link, args_dict
|
|
42
|
+
else:
|
|
43
|
+
return data, {}
|
ghttp/server.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from .transfer_encoding import read as read_encoding
|
|
2
|
+
import threading
|
|
3
|
+
import socket
|
|
4
|
+
|
|
5
|
if type(data)==str:
|
|
6
|
+
data = data.encode(encoding=encoding)
|
|
7
|
+
if type(status_code)==bytes:
|
|
8
|
+
status_code = status_code.decode('latin-1')
|
|
1
9
|
close = False
|
|
2
10
|
result = f"HTTP/{ver} {status_code}\x0d\x0a"
|
|
3
11
|
for key, value in headers.items():
|
|
4
12
|
if key.lower()=='connection' and value.lower() == 'close':
|
|
5
13
|
close = True
|
|
6
14
|
result += f"{key}: {value}\x0d\x0a"
|
|
15
|
+
if key.lower() == 'connection' and value.lower() == 'close':
|
|
16
|
+
close = True
|
|
17
|
+
if not without_con:
|
|
7
18
|
if not "\nconnection: " in result.lower():
|
|
8
19
|
result += f"Connection: close\x0d\x0a"
|
|
9
20
|
close = True
|
|
10
21
|
if not without_len:
|
|
11
22
|
if not "\ncontent-length: " in result.lower():
|
|
12
23
|
result += f"Content-Length: {len(data)}\x0d\x0a"
|
|
13
24
|
result += "\x0d\x0a"
|
|
14
25
|
data = result.encode() + data
|
|
15
26
|
conn.sendall(data)
|
|
16
27
|
if close:
|
|
17
28
|
conn.close()
|
|
29
|
+
|
|
18
30
|
try:
|
|
19
31
|
data = conn.recv(65500)
|
|
20
32
|
while b'\r\n\r\n' not in data:
|
|
21
33
|
chunk = conn.recv(65500)
|
|
22
34
|
if chunk == b'':
|
|
23
35
|
break
|
|
24
36
|
data += chunk
|
|
25
37
|
except: data = b''
|
|
26
38
|
if not data == b'':
|
|
27
39
|
data_split = data.find(b'\r\n\r\n')
|
|
28
40
|
header, dta = data[:data_split], data[data_split+4:]
|
|
29
41
|
headers = {}
|
|
30
42
|
h = header.split(b'\r\n')
|
|
31
43
|
if not require_ver:
|
|
32
44
|
question = h[0][:h[0].rfind(b' HTTP')].split(b' ',1)
|
|
33
45
|
question.append(None)
|
|
34
46
|
else:
|
|
35
47
|
question = h[0].split(b' ',1)
|
|
36
48
|
question = question[0], *question[1].rsplit(b' ',1)
|
|
37
49
|
for i in h[1:]:
|
|
38
50
|
key, value = i.split(b':',1)
|
|
39
51
|
key = key.decode(errors='replace')
|
|
40
52
|
value = value.lstrip(b' ').decode(errors='replace')
|
|
41
53
|
headers[key.lower()] = int(value) if value.isdigit() else value
|
|
54
|
+
if 'transfer-encoding' in headers:
|
|
55
|
+
dta = read_encoding(s,dta,headers['transfer-encoding'])
|
|
56
|
+
elif 'content-length' in headers:
|
|
42
57
|
toread = headers['content-length']
|
|
43
58
|
toread = int(toread) - len(dta)
|
|
44
59
|
while toread > 0:
|
|
45
60
|
chunk = conn.recv(toread if toread < 65500 else 65500)
|
|
61
|
+
dta += chunk
|
|
62
|
+
toread -= len(chunk)
|
|
46
63
|
if len(question) == 3:
|
|
47
64
|
if not binary_question:
|
|
48
65
|
question = question[0].decode(), question[1].decode(), question[2].decode()
|
|
49
66
|
else:
|
|
50
67
|
raise ValueError(f"Question is strange.. {question}")
|
|
51
68
|
return question, dta, headers
|
|
52
69
|
else:
|
|
53
70
|
return [None, None, None], None, {}
|
|
71
|
+
|
|
72
|
+
def send_raw(conn, data):
|
|
73
|
+
if type(data)==bytes:
|
|
74
|
+
conn.sendall(data)
|
|
75
|
+
else:
|
|
76
|
+
raise ValueError("data must be bytes!")
|
|
77
|
+
|
|
78
|
+
def read_chunk(conn):
|
|
79
|
+
try:
|
|
80
|
+
chunk_size_hex = b''
|
|
81
|
+
while True:
|
|
82
|
+
char = conn.recv(1)
|
|
83
|
+
if not char:
|
|
84
|
+
return None
|
|
85
|
+
if char == b'\n':
|
|
86
|
+
break
|
|
87
|
+
chunk_size_hex += char
|
|
88
|
+
if chunk_size_hex.endswith(b'\r'):
|
|
89
|
+
chunk_size_hex = chunk_size_hex[:-1]
|
|
90
|
+
try:
|
|
91
|
+
chunk_size = int(chunk_size_hex, 16)
|
|
92
|
+
except ValueError:
|
|
93
|
+
return None
|
|
94
|
+
if chunk_size == 0:
|
|
95
|
+
conn.recv(2)
|
|
96
|
+
return (b'', 0)
|
|
97
|
+
data = b''
|
|
98
|
+
while len(data) < chunk_size:
|
|
99
|
+
remaining = chunk_size - len(data)
|
|
100
|
+
chunk = conn.recv(min(remaining, 8192))
|
|
101
|
+
if not chunk:
|
|
102
|
+
return None
|
|
103
|
+
data += chunk
|
|
104
|
+
conn.recv(2)
|
|
105
|
+
return data, chunk_size
|
|
106
|
+
except Exception:
|
|
107
|
+
return None, None
|
|
108
|
+
|
|
109
|
+
def read_raw(conn, data=None):
|
|
110
|
+
try: return conn.recv(data)
|
|
111
|
+
except: return None
|
|
112
|
+
|
|
113
|
+
def close(conn):
|
|
114
|
+
try: conn.close()
|
|
115
|
+
except: pass
|
|
116
|
+
|
|
117
|
+
def send_chunk(conn, data):
|
|
118
|
+
if type(data)==bytes:
|
|
119
|
+
conn.sendall(hex(len(data))[2:].encode() + b'\r\n' + data + b'\r\n')
|
|
120
|
+
else:
|
|
121
|
+
raise ValueError("data must be bytes!")
|
|
122
|
+
|
|
123
|
+
class tserver():
|
|
124
|
+
def __init__(self, HOST='127.0.0.1', PORT=80, VER='1.1', precon=None, preaddr=None):
|
|
125
|
+
if precon == None:
|
|
126
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
127
|
+
s.bind((HOST, PORT))
|
|
128
|
+
s.listen()
|
|
129
|
+
s.settimeout(5.0)
|
|
130
|
+
self.s = s
|
|
131
|
+
else:
|
|
132
|
+
self.s = None
|
|
133
|
+
self.conn = precon
|
|
134
|
+
self.addr = preaddr
|
|
135
|
+
self.ver = VER
|
|
136
|
+
def accept(self, get_raw=False):
|
|
137
|
+
if self.s == None:
|
|
138
|
+
raise ValueError("While using precon, not allowed method accept!")
|
|
139
|
+
else:
|
|
140
|
+
conn, addr = self.s.accept()
|
|
141
|
+
conn.settimeout(5.0)
|
|
142
|
+
self.conn, self.addr = conn, addr
|
|
143
|
+
if not get_raw:
|
|
144
|
+
return addr
|
|
145
|
+
else:
|
|
146
|
+
return conn, addr
|
|
147
|
+
def send(self, data, headers={}, status_code="200 OK", without_len=False, without_con=False):
|
|
148
|
+
send(self.conn, self.ver, data, headers, status_code, without_len, without_con)
|
|
149
|
+
def read(self, binary_question=False, require_ver=True):
|
|
150
|
+
return read(self.conn, binary_question, require_ver)
|
|
151
|
+
def send_raw(self, data):
|
|
152
|
+
send_raw(self.conn, data)
|
|
153
|
+
def send_chunk(self, data):
|
|
154
|
+
send_chunk(self.conn, data)
|
|
155
|
+
def read_chunk(self):
|
|
156
|
+
return read_chunk(self.conn)
|
|
157
|
+
def read_raw(self, count=None):
|
|
158
|
+
return read_raw(self.conn, count)
|
|
159
|
+
def close(self):
|
|
160
|
+
close(self.conn)
|
|
161
|
+
|
|
162
|
+
def server(WRAPPER, HOST='127.0.0.1', PORT=80, VER='1.1', CUSTOM_SOCK=None, ARGS=()):
|
|
163
|
+
serv = tserver(HOST, PORT, VER)
|
|
164
|
+
if CUSTOM_SOCK is not None:
|
|
165
|
+
try: serv.close()
|
|
166
|
+
except: pass
|
|
167
|
+
serv.s = CUSTOM_SOCK
|
|
168
|
+
while True:
|
|
169
|
+
serv.s.settimeout(None)
|
|
170
|
+
conn, addr = serv.accept(get_raw=True)
|
|
171
|
+
s = tserver(HOST, PORT, VER, precon=conn, preaddr=addr)
|
|
172
|
+
t = threading.Thread(target=WRAPPER, daemon=True, args=(s,addr,*ARGS))
|
|
173
|
+
t.start()
|
ghttp/simple_client.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from .parsearg import parsearg
|
|
2
|
+
from .client import client
|
|
3
|
+
import json as json_mod
|
|
4
|
+
import socket
|
|
5
|
+
import ssl
|
|
6
|
+
|
|
7
|
+
class _clresponse:
|
|
8
|
+
def __init__(self,data,code,text,headers):
|
|
9
|
+
self.text = data.decode(encoding='latin-1')
|
|
10
|
+
self.binary = data
|
|
11
|
+
self.status = code
|
|
12
|
+
self.text_status = text
|
|
13
|
+
self.headers = headers
|
|
14
|
+
def json(self):
|
|
15
|
+
return json.loads(self.text)
|
|
16
|
+
|
|
17
|
+
def _send_with_retry(s, method, path, headers, payload, ip, port, getsock, max_retries=2):
|
|
18
|
+
for attempt in range(max_retries):
|
|
19
|
+
try:
|
|
20
|
+
s.send(method, path, headers=headers, without_len=True, data=payload)
|
|
21
|
+
return s
|
|
22
|
+
except Exception:
|
|
23
|
+
if attempt == max_retries - 1:
|
|
24
|
+
raise ValueError('Timeout')
|
|
25
|
+
s.close()
|
|
26
|
+
if getsock:
|
|
27
|
+
s = client()
|
|
28
|
+
s.connect(ip, port)
|
|
29
|
+
else:
|
|
30
|
+
s = client()
|
|
31
|
+
s.connect(ip, port)
|
|
32
|
+
return s
|
|
33
|
+
|
|
34
|
+
def _receive_with_retry(s, ip, port, getsock, max_retries=2):
|
|
35
|
+
for attempt in range(max_retries):
|
|
36
|
+
try:
|
|
37
|
+
answer, data, headers = s.read()
|
|
38
|
+
return answer, data, headers
|
|
39
|
+
except Exception:
|
|
40
|
+
if attempt == max_retries - 1:
|
|
41
|
+
raise ValueError('Timeout')
|
|
42
|
+
s.close()
|
|
43
|
+
if getsock:
|
|
44
|
+
s = client()
|
|
45
|
+
s.connect(ip, port)
|
|
46
|
+
else:
|
|
47
|
+
s = client()
|
|
48
|
+
s.connect(ip, port)
|
|
49
|
+
return None, None, None
|
|
50
|
+
|
|
51
|
+
def parseurl(url):
|
|
52
|
+
https = False
|
|
53
|
+
if 'https://' in url:
|
|
54
|
+
https = True
|
|
55
|
+
address = url.replace('http://', '').replace('https://', '')
|
|
56
|
+
address = address.split('/', 1)
|
|
57
|
+
addr, path = address[0], '/' if len(address) == 1 else '/' + address[1]
|
|
58
|
+
ip, port = addr.split(':', 1) if len(addr.split(':', 1)) == 2 else (addr, 80 if not https else 443)
|
|
59
|
+
port = int(port)
|
|
60
|
+
return ip, port, https, path
|
|
61
|
+
|
|
62
|
+
def _get(url, data, json, headers, params, method, sock=None, close=True, getsock=False, usehost=True):
|
|
63
|
+
ip, port, https, path = parseurl(url)
|
|
64
|
+
if data is not None:
|
|
65
|
+
payload = data
|
|
66
|
+
elif json is not None:
|
|
67
|
+
payload = json_mod.dumps(json).encode()
|
|
68
|
+
else:
|
|
69
|
+
payload = b''
|
|
70
|
+
dfheaders = {}
|
|
71
|
+
if usehost:
|
|
72
|
+
dfheaders['Host'] = ip
|
|
73
|
+
if not payload == b'':
|
|
74
|
+
dfheaders['Content-Length'] = str(len(payload))
|
|
75
|
+
dfheaders.update(headers)
|
|
76
|
+
if sock is None:
|
|
77
|
+
s = client()
|
|
78
|
+
if https:
|
|
79
|
+
context = ssl.create_default_context()
|
|
80
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
81
|
+
ssl_sock = context.wrap_socket(sock, server_hostname=ip)
|
|
82
|
+
s.connect(ip, port, ssl_sock)
|
|
83
|
+
else:
|
|
84
|
+
s.connect(ip, port)
|
|
85
|
+
else:
|
|
86
|
+
s = sock
|
|
87
|
+
s = _send_with_retry(s, method, path, dfheaders, payload, ip, port, getsock)
|
|
88
|
+
answer, data, headers = _receive_with_retry(s, ip, port, getsock)
|
|
89
|
+
if data is None:
|
|
90
|
+
s.close()
|
|
91
|
+
s = client()
|
|
92
|
+
s.connect(ip, port)
|
|
93
|
+
s = _send_with_retry(s, method, path, dfheaders, payload, ip, port, getsock)
|
|
94
|
+
answer, data, headers = _receive_with_retry(s, ip, port, getsock)
|
|
95
|
+
if data is None:
|
|
96
|
+
raise ValueError('Timeout')
|
|
97
|
+
status = answer[1].split(' ', 1)
|
|
98
|
+
status_code = int(status[0])
|
|
99
|
+
status_text = status[1] if len(status) == 2 else None
|
|
100
|
+
if close:
|
|
101
|
+
s.close()
|
|
102
|
+
response = _clresponse(data, status_code, status_text, headers)
|
|
103
|
+
return (s, response) if getsock else response
|
|
104
|
+
|
|
105
|
+
def get(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
106
|
+
return _get(url,data,json,headers,params,'GET', usehost=usehost)
|
|
107
|
+
|
|
108
|
+
def post(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
109
|
+
return _get(url,data,json,headers,params,'POST', usehost=usehost)
|
|
110
|
+
|
|
111
|
+
def delete(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
112
|
+
return _get(url,data,json,headers,params,'DELETE', usehost=usehost)
|
|
113
|
+
|
|
114
|
+
def put(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
115
|
+
return _get(url,data,json,headers,params,'PUT', usehost=usehost)
|
|
116
|
+
|
|
117
|
+
def head(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
118
|
+
return _get(url,data,json,headers,params,'HEAD', usehost=usehost)
|
|
119
|
+
|
|
120
|
+
def connect(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
121
|
+
return _get(url,data,json,headers,params,'CONNECT', usehost=usehost)
|
|
122
|
+
|
|
123
|
+
def options(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
124
|
+
return _get(url,data,json,headers,params,'OPTIONS', usehost=usehost)
|
|
125
|
+
|
|
126
|
+
def trace(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
127
|
+
return _get(url,data,json,headers,params,'TRACE', usehost=usehost)
|
|
128
|
+
|
|
129
|
+
def patch(url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
130
|
+
return _get(url,data,json,headers,params,'PATCH', usehost=usehost)
|
|
131
|
+
|
|
132
|
+
class Session:
|
|
133
|
+
def __init__(self):
|
|
134
|
+
self.s = None
|
|
135
|
+
self.url = None
|
|
136
|
+
|
|
137
|
+
def reload(self):
|
|
138
|
+
self.close()
|
|
139
|
+
self.s = None
|
|
140
|
+
self.url = None
|
|
141
|
+
|
|
142
|
+
def close(self):
|
|
143
|
+
try: self.s.close()
|
|
144
|
+
except: pass
|
|
145
|
+
|
|
146
|
+
def check_url(self, url):
|
|
147
|
+
ip, port, https, path = parseurl(url)
|
|
148
|
+
_url = f'{ip}:{port}'
|
|
149
|
+
if not _url == self.url:
|
|
150
|
+
self.url = _url
|
|
151
|
+
self.reload()
|
|
152
|
+
|
|
153
|
+
def get(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
154
|
+
self.check_url(url)
|
|
155
|
+
self.s, answer = _get(url,data,json,headers,params,'GET',self.s,False,True,usehost)
|
|
156
|
+
return answer
|
|
157
|
+
|
|
158
|
+
def post(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
159
|
+
self.check_url(url)
|
|
160
|
+
self.s, answer = _get(url,data,json,headers,params,'POST',self.s,False,True,usehost)
|
|
161
|
+
return answer
|
|
162
|
+
|
|
163
|
+
def delete(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
164
|
+
self.check_url(url)
|
|
165
|
+
self.s, answer = _get(url,data,json,headers,params,'DELETE',self.s,False,True,usehost)
|
|
166
|
+
return answer
|
|
167
|
+
|
|
168
|
+
def put(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
169
|
+
self.check_url(url)
|
|
170
|
+
self.s, answer = _get(url,data,json,headers,params,'PUT',self.s,False,True,usehost)
|
|
171
|
+
return answer
|
|
172
|
+
|
|
173
|
+
def head(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
174
|
+
self.check_url(url)
|
|
175
|
+
self.s, answer = _get(url,data,json,headers,params,'HEAD',self.s,False,True,usehost)
|
|
176
|
+
return answer
|
|
177
|
+
|
|
178
|
+
def connect(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
179
|
+
self.check_url(url)
|
|
180
|
+
self.s, answer = _get(url,data,json,headers,params,'CONNECT',self.s,False,True,usehost)
|
|
181
|
+
return answer
|
|
182
|
+
|
|
183
|
+
def options(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
184
|
+
self.check_url(url)
|
|
185
|
+
self.s, answer = _get(url,data,json,headers,params,'OPTIONS',self.s,False,True,usehost)
|
|
186
|
+
return answer
|
|
187
|
+
|
|
188
|
+
def trace(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
189
|
+
self.check_url(url)
|
|
190
|
+
self.s, answer = _get(url,data,json,headers,params,'TRACE',self.s,False,True,usehost)
|
|
191
|
+
return answer
|
|
192
|
+
|
|
193
|
+
def patch(self,url='localhost:80', data=None, json=None, headers={}, params={}, usehost=True):
|
|
194
|
+
self.check_url(url)
|
|
195
|
+
self.s, answer = _get(url,data,json,headers,params,'PATCH',self.s,False,True,usehost)
|
|
196
|
+
return answer
|
ghttp/simple_server.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from .parsearg import parsearg
|
|
2
|
+
from .server import server
|
|
3
|
+
import subprocess
|
|
4
|
+
import json
|
|
5
|
+
import ssl
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
def serve(IP='localhost',PORT=80,PATH='./',silent=False,keep_alive=False,SSL=False,ssl_cert='cert.pem', ssl_key='key.pem'):
|
|
9
|
+
if SSL:
|
|
10
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
11
|
+
context.load_cert_chain(ssl_cert, ssl_key)
|
|
12
|
+
if PATH.endswith('/'):
|
|
13
|
+
PATH = PATH[:-1]
|
|
14
|
+
def wrapper(cl,addr,SSL,context):
|
|
15
|
+
if SSL:
|
|
16
|
+
try: cl.conn = context.wrap_socket(cl.conn, server_side=True)
|
|
17
|
+
except: pass
|
|
18
|
+
cl.conn.settimeout(30.0)
|
|
19
|
+
while True:
|
|
20
|
+
try:
|
|
21
|
+
question, data, headers = cl.read()
|
|
22
|
+
if data == None:
|
|
23
|
+
if not silent:
|
|
24
|
+
print(f'[{':'.join([str(i) for i in addr])}]: Closed',flush=True)
|
|
25
|
+
cl.close()
|
|
26
|
+
break
|
|
27
|
+
if question[0] == 'GET':
|
|
28
|
+
cur_dir, args = parsearg(question[1])
|
|
29
|
+
while cur_dir.find('..') != -1:
|
|
30
|
+
cur_dir = cur_dir.replace( '..', '.')
|
|
31
|
+
directory = PATH+cur_dir
|
|
32
|
+
if not directory.endswith('/') and os.path.exists(directory) and os.path.isdir(directory):
|
|
33
|
+
directory += '/'
|
|
34
|
+
hasindex = False
|
|
35
|
+
if os.path.exists(directory) and os.path.isdir(directory):
|
|
36
|
+
files = os.listdir(directory)
|
|
37
|
+
for i in files:
|
|
38
|
+
if i == 'index.html' or i == 'index.py':
|
|
39
|
+
hasindex = True
|
|
40
|
+
if hasindex:
|
|
41
|
+
if os.path.exists(f'{directory}index.py'):
|
|
42
|
+
try:
|
|
43
|
+
output = subprocess.check_output([f'{os.path.abspath(directory)}/index.py', json.dumps(args)], shell=True, stderr=subprocess.DEVNULL)
|
|
44
|
+
cl.send(output,headers={"Connection": "keep-alive" if keep_alive else "close"})
|
|
45
|
+
except:
|
|
46
|
+
cl.send(b'',status_code='500 Internal Server Error',headers={"Connection": "keep-alive" if keep_alive else "close"})
|
|
47
|
+
elif os.path.exists(f'{directory}index.html'):
|
|
48
|
+
with open(f'{directory}index.html','rb') as f:
|
|
49
|
+
cl.send(f.read(),headers={"Content-Type": "text/html; charset=utf-8", "Connection": "keep-alive" if keep_alive else "close"})
|
|
50
|
+
if not silent:
|
|
51
|
+
print(f'[{':'.join([str(i) for i in addr])}]: 200 OK {cur_dir}',flush=True)
|
|
52
|
+
elif os.path.exists(directory):
|
|
53
|
+
if not silent:
|
|
54
|
+
print(f'[{':'.join([str(i) for i in addr])}]: 200 OK {cur_dir}',flush=True)
|
|
55
|
+
if os.path.isdir(directory):
|
|
56
|
+
result = f'<h1>List of {cur_dir}</h1><a href="..">↩</a><br>'
|
|
57
|
+
for i in files:
|
|
58
|
+
result += f'<a href="{directory}{i}">{i}</a><br>'
|
|
59
|
+
cl.send(result.encode(),headers={"Content-Type": "text/html; charset=utf-8", "Connection": "keep-alive" if keep_alive else "close"})
|
|
60
|
+
else:
|
|
61
|
+
if directory.rsplit('.',1)[-1] == 'py':
|
|
62
|
+
try:
|
|
63
|
+
output = subprocess.check_output([f'{os.path.abspath(directory)}', json.dumps(args)], shell=True, stderr=subprocess.DEVNULL)
|
|
64
|
+
cl.send(output,headers={"Connection": "keep-alive" if keep_alive else "close"})
|
|
65
|
+
except:
|
|
66
|
+
cl.send(b'',status_code='500 Internal Server Error',headers={"Connection": "keep-alive" if keep_alive else "close"})
|
|
67
|
+
else:
|
|
68
|
+
with open(directory,'rb') as f:
|
|
69
|
+
cl.send(f.read(),{"Connection": "keep-alive" if keep_alive else "close"})
|
|
70
|
+
else:
|
|
71
|
+
if not silent:
|
|
72
|
+
cl.send(b'',status_code='404 Not Found', headers={"Connection": "keep-alive" if keep_alive else "close"})
|
|
73
|
+
print(f'[{':'.join([str(i) for i in addr])}]: 404 Not Found {cur_dir}',flush=True)
|
|
74
|
+
else:
|
|
75
|
+
if not silent:
|
|
76
|
+
cl.send(b'',status_code='400 Method Not Support', headers={"Connection": "keep-alive" if keep_alive else "close"})
|
|
77
|
+
print(f'[{':'.join([str(i) for i in addr])}]: 400 Method Not Support',flush=True)
|
|
78
|
+
except:
|
|
79
|
+
if not silent:
|
|
80
|
+
print(f'[{':'.join([str(i) for i in addr])}]: Error',flush=True)
|
|
81
|
+
cl.close()
|
|
82
|
+
break
|
|
83
|
+
if not silent:
|
|
84
|
+
print(f"Runned server at {IP}:{PORT}",flush=True)
|
|
85
|
+
server(wrapper,IP,PORT,ARGS=(SSL,context if SSL else None))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
def _read_chunked(data, full_data, s):
|
|
2
|
+
header_end = data.find(b'\r\n')
|
|
3
|
+
if header_end == -1:
|
|
4
|
+
return data, full_data, False
|
|
5
|
+
header = data[:header_end]
|
|
6
|
+
try: toread = int(header.decode(), 16)
|
|
7
|
+
except: pass
|
|
8
|
+
if toread == 0:
|
|
9
|
+
return b'', full_data, True
|
|
10
|
+
rest = data[header_end + 2:]
|
|
11
|
+
while len(rest) < toread + 2:
|
|
12
|
+
rest += s.recv(min(65500, toread + 2 - len(rest)))
|
|
13
|
+
full_data += rest[:toread]
|
|
14
|
+
return rest[toread + 2:], full_data, False
|
|
15
|
+
|
|
16
|
+
def read(s, data=b'', encoding='chunked'):
|
|
17
|
+
if encoding == 'chunked':
|
|
18
|
+
full_data = b''
|
|
19
|
+
while True:
|
|
20
|
+
header_end = data.find(b'\r\n')
|
|
21
|
+
if header_end != -1:
|
|
22
|
+
data, full_data, done = _read_chunked(data, full_data, s)
|
|
23
|
+
if done:
|
|
24
|
+
return full_data
|
|
25
|
+
else:
|
|
26
|
+
data += s.recv(65500)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ghttp-tool
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Multifunctional HTTP Lib
|
|
5
|
+
Author-email: GPGStudio <gpgstudio.original@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/GPGSTUDIO/GHTTP
|
|
8
|
+
Project-URL: Repository, https://github.com/GPGSTUDIO/GHTTP
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/GPGSTUDIO/GHTTP/issues
|
|
10
|
+
Keywords: HTTP,TCP,Ethernet,Internet,Lan,Wan,
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: black; extra == "dev"
|
|
24
|
+
Requires-Dist: flake8; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# **Multifunction HTTP lib!**!
|
|
28
|
+
|
|
29
|
+
Installing:
|
|
30
|
+
```python
|
|
31
|
+
pip install ghttp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
See examples in https://www.gpgstudio.ru/pypi/ghttp/examples
|
|
35
|
+
|
|
36
|
+
In latest version:
|
|
37
|
+
> Created
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ghttp/__init__.py,sha256=jY2T6M4ThweV6kFF0qjAuqHRGFUrZMhS1RcieVxli4U,619
|
|
2
|
+
ghttp/client.py,sha256=L64nw63zKk-n5mKhfR_EeYVVh_mmFJ7PiS0ON8C1dLk,4584
|
|
3
|
+
ghttp/parsearg.py,sha256=vU1d2gkBu87asrpclvTQmLzse3fY7ZACwiNnAeLJ7fM,1892
|
|
4
|
+
ghttp/server.py,sha256=xXgFx5l-PVHXRXNKmAHhBqktr6MZM6u4IzeWEEcYF1o,6202
|
|
5
|
+
ghttp/simple_client.py,sha256=MAyw7YC8PSGWhlJxeb1Sw3wnCG4odbw-_vkEJsdfubo,7794
|
|
6
|
+
ghttp/simple_server.py,sha256=MbndQlobpUBbbC3ZP4P8_-OASs5DrICB9ZFnNUPHXmk,5129
|
|
7
|
+
ghttp/transfer_encoding.py,sha256=uQrYaAMUkrS84uI7DlCtnAf4sPq_8bQAIwbc2eruEoM,900
|
|
8
|
+
ghttp_tool-0.0.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
ghttp_tool-0.0.1.dist-info/METADATA,sha256=g1HOmApis7R-pFQKfbsanD63RuPG7kxJ-r3nvAfLTxY,1210
|
|
10
|
+
ghttp_tool-0.0.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
11
|
+
ghttp_tool-0.0.1.dist-info/top_level.txt,sha256=WAMhcBd3d2OyGry1vK6PI28CY45g-p5Xg6GaR27LXP0,6
|
|
12
|
+
ghttp_tool-0.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ghttp
|