Skip to content

Commit 7879e6f

Browse files
committed
2 parents 00a3a29 + b919e63 commit 7879e6f

File tree

7 files changed

+135
-74
lines changed

7 files changed

+135
-74
lines changed
775 Bytes
Binary file not shown.
Binary file not shown.
Binary file not shown.

src/client/client_cli.py

Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,3 @@
1-
# import argparse
2-
# import json
3-
# import sys
4-
# from http_client import final_request
5-
# from http_response import HTTPResponse
6-
# from http_parser import categorize_args
7-
# from exceptions import InvalidHeaderFormat
8-
9-
# def main(sys_args):
10-
11-
# parser = argparse.ArgumentParser(description="HTTP Client CLI", add_help=False)
12-
# parser.add_argument("-m", "--method", required=True, help="Metodo HTTP (GET,POST,DELETE)")
13-
# parser.add_argument("-u", "--url", required=True, help="URL de la solicitud")
14-
# parser.add_argument("-h", "--header", type=str, default="{}", help="Encabezado JSON de al solicitud (e.g., '{\"User-Agent\": \"device\"}')")
15-
# parser.add_argument("-d", "--data", type=str, default="", help="Cuerpo de la solicitud")
16-
17-
# # Parse arguments
18-
# args = parser.parse_args(categorize_args(sys_args))
19-
20-
# # Prepare headers from JSON string
21-
# try:
22-
# headers = json.loads(args.header)
23-
# except json.JSONDecodeError:
24-
# raise InvalidHeaderFormat("❌ Error:Formato de encabezado inválido. Por favor, proporcione un JSON válido.")
25-
26-
# # Make the HTTP request
27-
# response: HTTPResponse = final_request(method=args.method, url=args.url, headers=headers, body=args.data)
28-
29-
# # Prepare output JSON format
30-
# final_response = {
31-
# "status": response.code,
32-
# "body": response.get_body_bytes().decode('utf-8') # Assuming body is in bytes and needs to be decoded
33-
# }
34-
35-
# # Print output as JSON
36-
# print(json.dumps(final_response, indent=2))
37-
38-
# if __name__ == "__main__":
39-
# main(sys.argv[1:])
40-
411
import argparse
422
import json
433
import sys
@@ -48,24 +8,33 @@
488

499
def run_client(method, url, headers=None, body=""):
5010
"""
51-
Función que ejecuta una solicitud HTTP y devuelve la respuesta.
11+
Función que ejecuta una solicitud HTTP y devuelve la respuesta formateada.
5212
5313
:param method: Método HTTP (GET, POST, etc.)
5414
:param url: URL de la solicitud
5515
:param headers: Diccionario de encabezados HTTP
5616
:param body: Cuerpo de la solicitud (opcional)
57-
:return: Diccionario con el código de estado y el cuerpo de la respuesta
17+
:return: Respuesta HTTP formateada como string
5818
"""
5919
headers = headers or {}
6020

6121
try:
6222
response: HTTPResponse = final_request(method=method, url=url, headers=headers, body=body)
63-
return {
64-
"status": response.code,
65-
"body": response.get_body_bytes().decode("utf-8") # Decodificar el cuerpo de la respuesta
66-
}
23+
24+
# Construcción de la respuesta en formato HTTP
25+
response_text = f"HTTP/1.1 {response.code} {response.reason}\n"
26+
27+
# Agregar encabezados al formato
28+
for key, value in response.headers.items():
29+
response_text += f"{key}: {value}\n"
30+
31+
response_text += "\n" # Línea vacía entre encabezados y cuerpo
32+
response_text += response.get_body_bytes().decode("utf-8") # Cuerpo de la respuesta
33+
34+
return response_text
6735
except Exception as e:
68-
return {"status": "error", "body": str(e)}
36+
return f"HTTP/1.1 500 Internal Server Error\nError: {str(e)}"
37+
6938

7039
def main(sys_args):
7140
parser = argparse.ArgumentParser(description="HTTP Client CLI", add_help=False)
@@ -80,7 +49,7 @@ def main(sys_args):
8049
try:
8150
headers = json.loads(args.header)
8251
except json.JSONDecodeError:
83-
raise InvalidHeaderFormat("❌ Error: Invalid header format. Please provide valid JSON.")
52+
raise InvalidHeaderFormat("❌ Error:Formato de encabezado inválido. Por favor, proporcione un JSON válido.")
8453

8554
response: HTTPResponse = final_request(method=args.method, url=args.url, headers=headers, body=args.data)
8655

src/client/http_client.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
11
import socket
2+
import json
3+
import ssl
24
from http_parser import parse_http_url,parse_http_response
35
from exceptions import NotConnection
46

57
# GLOBAL VARIABLES
68
_versionHttp = 'HTTP/1.1'
7-
default_port = 80
9+
default_http_port = 80
10+
default_https_port = 443
811

912
class HttpClient:
1013

11-
def __init__(self,host, port :int, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, blocksize = 8192):
14+
def __init__(self,host, port :int,use_https,timeout=socket._GLOBAL_DEFAULT_TIMEOUT, blocksize = 8192):
1215

1316
self.host = host
1417
self.port = port
18+
self.use_https = use_https
1519
self.timeout = timeout
1620
self.mySocket = None
1721
self.blocksize = blocksize
1822

1923
# se hace uso del protocolo TCP/IP
2024
def connect (self):
2125
# se establece una conexion TCP con el servidor
22-
self.mySocket = socket.create_connection((self.host,self.port),self.timeout)
26+
raw_mySocket = socket.create_connection((self.host,self.port),self.timeout)
2327

28+
if self.use_https:
29+
# si es https se envuelve el socket en una conexion TLS/SSL
30+
context = ssl.create_default_context()
31+
self.mySocket = context.wrap_socket(raw_mySocket,server_hostname=self.host)
32+
33+
else: # si es HTTP
34+
self.mySocket = raw_mySocket
2435

2536
def request(self,method,url,body="", headers= None):
2637
# construye y envia una solicitud HTTP
@@ -70,8 +81,9 @@ def final_request (method="GET",url="/",headers = None,body =""):
7081
# extraer la informacion de la URL
7182
host,port,path,query = parse_http_url(url)
7283

84+
use_https = (port == default_https_port) or url.startswith("https://")
7385
# crear una instancia de HttpClient para establecer una conexion
74-
conn = HttpClient(host,port)
86+
conn = HttpClient(host,port,use_https)
7587

7688
data = None
7789

@@ -90,3 +102,49 @@ def final_request (method="GET",url="/",headers = None,body =""):
90102
# devuelve la respuesta parseada
91103
return data
92104

105+
106+
if __name__ == "__main__":
107+
108+
# Case 1 : My API - DownTrack
109+
host = "http://localhost:5217"
110+
endpoint = "/api/Authentication/register"
111+
112+
body = json.dumps({
113+
"id":33590,
114+
"name": "User_335",
115+
"userName": "username_33590",
116+
"email": "example3@gmail.com",
117+
"password": "Password_333!",
118+
"userRole": "Technician",
119+
"specialty": "mechanic",
120+
"salary": 19090,
121+
"expYears": 10,
122+
"departamentId": 1,
123+
"sectionId": 1
124+
})
125+
126+
headers = {
127+
"Content-Type": "application/json"
128+
}
129+
130+
response = final_request("POST", f"{host}{endpoint}", headers=headers, body=body)
131+
132+
print("Código de estado:", response.code)
133+
print("Encabezados:", response.headers)
134+
print("Cuerpo:", response.body[:500])
135+
136+
# Case 2: HTTPS
137+
138+
response = final_request("GET","https://reqres.in/api/users?page=2", headers={}, body="")
139+
140+
print("Código de estado:", response.code)
141+
print("Encabezados:", response.headers)
142+
print("Cuerpo:", response.body[:500])
143+
144+
# Case 3: HTTPS
145+
response = final_request("GET","https://jsonplaceholder.typicode.com", headers={}, body="")
146+
147+
print("Código de estado:", response.code)
148+
print("Encabezados:", response.headers)
149+
print("Cuerpo:", response.body[:500])
150+

src/client/http_client_gui.py

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,55 @@ def __init__(self, parent):
99
ctk.set_default_color_theme("blue")
1010
self.root = parent
1111
self.root.title("HTTP Client")
12-
self.root.geometry("600x550")
12+
self.root.geometry("700x650") # Aumentar tamaño para historial
1313
self.root.grid_columnconfigure(1, weight=1)
1414

15+
# Estilo general
16+
font_style = ("Arial", 14)
17+
18+
# Historial de solicitudes
19+
self.history = []
20+
1521
# URL
16-
ctk.CTkLabel(parent, text="URL:").grid(row=0, column=0, padx=10, pady=5, sticky="w")
17-
self.url_entry = ctk.CTkEntry(parent, width=400)
22+
ctk.CTkLabel(parent, text="🌐 URL:", font=font_style).grid(row=0, column=0, padx=10, pady=5, sticky="w")
23+
self.url_entry = ctk.CTkEntry(parent, width=500, font=font_style)
1824
self.url_entry.grid(row=0, column=1, padx=10, pady=5, sticky="ew")
1925

2026
# Method
21-
ctk.CTkLabel(parent, text="Method:").grid(row=1, column=0, padx=10, pady=5, sticky="w")
27+
ctk.CTkLabel(parent, text="📡 Method:", font=font_style).grid(row=1, column=0, padx=10, pady=5, sticky="w")
2228
self.method_var = ctk.StringVar(value="GET")
23-
self.method_menu = ctk.CTkOptionMenu(parent, variable=self.method_var, values=["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "CONNECT", "TRACE", "LINK", "UNLINK", "CUSTOM"], command=self.toggle_body_state)
29+
self.method_menu = ctk.CTkOptionMenu(parent, variable=self.method_var,
30+
values=["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "CONNECT", "TRACE", "LINK", "UNLINK", "CUSTOM"],
31+
command=self.toggle_body_state, font=font_style)
2432
self.method_menu.grid(row=1, column=1, padx=10, pady=5, sticky="ew")
2533

2634
# Headers
27-
ctk.CTkLabel(parent, text="Headers (JSON):").grid(row=2, column=0, padx=10, pady=5, sticky="w")
28-
self.headers_entry = ctk.CTkEntry(parent, width=400)
35+
ctk.CTkLabel(parent, text="📝 Headers (JSON):", font=font_style).grid(row=2, column=0, padx=10, pady=5, sticky="w")
36+
self.headers_entry = ctk.CTkTextbox(parent, width=500, height=60, font=font_style)
2937
self.headers_entry.grid(row=2, column=1, padx=10, pady=5, sticky="ew")
3038

3139
# Body
32-
ctk.CTkLabel(parent, text="Body (JSON):").grid(row=3, column=0, padx=10, pady=5, sticky="w")
33-
self.body_text = ctk.CTkTextbox(parent, width=400, height=100)
40+
ctk.CTkLabel(parent, text="📦 Body (JSON):", font=font_style).grid(row=3, column=0, padx=10, pady=5, sticky="w")
41+
self.body_text = ctk.CTkTextbox(parent, width=500, height=100, font=font_style)
3442
self.body_text.grid(row=3, column=1, padx=10, pady=5, sticky="ew")
3543

3644
# Send Button
37-
self.send_button = ctk.CTkButton(parent, text="Send", command=self.send_request)
38-
self.send_button.grid(row=4, column=1, pady=10)
45+
self.send_button = ctk.CTkButton(parent, text="🚀 Send Request", command=self.send_request, font=font_style)
46+
self.send_button.grid(row=4, column=1, pady=15)
3947

4048
# Response
41-
ctk.CTkLabel(parent, text="Response:").grid(row=5, column=0, padx=10, pady=5, sticky="w")
42-
self.response_text = ctk.CTkTextbox(parent, width=400, height=150, state="disabled")
49+
ctk.CTkLabel(parent, text="📩 Response:", font=font_style).grid(row=5, column=0, padx=10, pady=5, sticky="w")
50+
self.response_text = ctk.CTkTextbox(parent, width=500, height=200, font=font_style, state="disabled")
4351
self.response_text.grid(row=5, column=1, padx=10, pady=5, sticky="ew")
4452

53+
# Historial de solicitudes
54+
ctk.CTkLabel(parent, text="📜 Request History:", font=font_style).grid(row=6, column=0, padx=10, pady=5, sticky="w")
55+
self.history_var = ctk.StringVar(value="No history yet")
56+
self.history_menu = ctk.CTkOptionMenu(parent, variable=self.history_var, values=["No history yet"], font=font_style)
57+
self.history_menu.grid(row=6, column=1, padx=10, pady=5, sticky="ew")
58+
self.load_history_button = ctk.CTkButton(parent, text="🔄 Load Request", command=self.load_request, font=font_style)
59+
self.load_history_button.grid(row=7, column=1, pady=10)
60+
4561
self.toggle_body_state("GET")
4662

4763
def toggle_body_state(self, method):
@@ -55,29 +71,47 @@ def toggle_body_state(self, method):
5571
def send_request(self):
5672
url = self.url_entry.get()
5773
method = self.method_var.get()
58-
headers = self.headers_entry.get()
74+
headers = self.headers_entry.get("1.0", "end").strip()
5975
body = self.body_text.get("1.0", "end").strip()
60-
76+
6177
try:
6278
headers = json.loads(headers) if headers else {}
6379
except json.JSONDecodeError:
64-
messagebox.showerror("Error", "Headers must be valid JSON.")
80+
messagebox.showerror("Error", "Headers must be valid JSON.\nExample:\n{\"Content-Type\": \"application/json\"}")
6581
return
66-
82+
6783
try:
6884
body = json.loads(body) if body else ""
6985
except json.JSONDecodeError:
7086
messagebox.showerror("Error", "Body must be valid JSON.")
7187
return
72-
88+
7389
try:
74-
response = client_cli.run_client(method, url, headers, body)
90+
response_text = client_cli.run_client(method, url, headers, body)
91+
92+
# Guardar en historial
93+
self.history.append((url, method, json.dumps(headers, indent=2), json.dumps(body, indent=2)))
94+
self.history_menu.configure(values=[f"{i+1}: {h[1]} {h[0]}" for i, h in enumerate(self.history)])
95+
self.history_var.set(f"{len(self.history)}: {method} {url}")
96+
7597
self.response_text.configure(state="normal")
7698
self.response_text.delete("1.0", "end")
77-
self.response_text.insert("end", f"Status: {response['status']}\n\n{response['body']}")
99+
self.response_text.insert("end", response_text)
78100
self.response_text.configure(state="disabled")
79101
except Exception as e:
80102
messagebox.showerror("Error", f"Request failed: {e}")
103+
104+
def load_request(self):
105+
index = int(self.history_var.get().split(":")[0]) - 1
106+
if 0 <= index < len(self.history):
107+
url, method, headers, body = self.history[index]
108+
self.url_entry.delete(0, "end")
109+
self.url_entry.insert(0, url)
110+
self.method_var.set(method)
111+
self.headers_entry.delete("1.0", "end")
112+
self.headers_entry.insert("end", headers)
113+
self.body_text.delete("1.0", "end")
114+
self.body_text.insert("end", body)
81115

82116
if __name__ == "__main__":
83117
root = ctk.CTk()

src/client/http_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
def parse_http_url(url):
77
# Expresión regular para parsear la URL
8-
regex = r'^(http://)?([^:/\s]+)(?::(\d+))?(/[^?\s]*)?(\?[^#\s]*)?$'
8+
regex = r'^(https?://)?([^:/\s]+)(?::(\d+))?(/[^?\s]*)?(\?[^#\s]*)?$'
99
match = re.match(regex, url)
1010

1111
if not match:
1212
raise UrlIncorrect(f"Invalid URL: {url}")
1313

1414
scheme = match.group(1) or "http://" # Si no tiene esquema, asignamos "http://"
1515
host = match.group(2)
16-
port = match.group(3) or 80 # Si no tiene puerto, asignamos el puerto por defecto 80
16+
port = match.group(3) or (443 if scheme.startswith("https") else 80) # Si no tiene puerto, asignamos el puerto por defecto 80
1717
path = match.group(4) or "/" # Si no tiene path, asignamos "/"
1818
query = match.group(5) or "" # Si no tiene query, dejamos como cadena vacía
1919

0 commit comments

Comments
 (0)