Build Proxy Reverse With Python | Proxy reverse in Python | Creación de un servidor web proxy en Python 3 | Crear servidor proxy en Python

El día de hoy se creará un Servidor Proxy en Python 3.8, basado en SOCKET, ayudando a centralizar todas las solicitudes entrantes en un solo punto, lo cual puede mejorar en cierto punto los niveles de seguridad de una aplicación. 

Al implementar o usar un proxy permite la apertura de un solo puerto, es decir, que se pueden tener a nivel de servidor muchas aplicaciones, pero solo se podrá acceder a ellas por medio del proxy.

El objetivo de esta publicación es crear un Servidor Proxy Web con las siguientes características:

  • Por cada petición entrante, el servidor crea una nueva conexión al servidor destino.
  • La solicitud entrante será enviada al servidor destino.
  • La solicitud entrante recibirá una respuesta por parte del servidor destino.
  • El Proxy aceptará muchas peticiones simultaneas.
  • El Proxy debe responder en los menores tiempos, es decir, manejar alto rendimiento y responder todas las peticiones.
  • El Proxy debe consumir pocos recursos máquina.

¿Qué es un Proxy?


A nivel general, son servidores que se usan como intermediario o puente entre el origen y el destino de una solicitud.


En acción:



providers/proxy.py

#!/usr/bin/python3
import os
import sys
import threading
import socket
import ssl

LISTEN = 200  # max
MAX_DATA_RECV = 999999  # bytes
BLOCKED = []
TIME_OUT = 20  # seconds

ALLOWED = {
    'ms-session': ('http', 'localhost', 8989, 'localhost')
}


class ProxyServer:

    def __init__(self, host='', port=8080):
        """
        inicializa el servidor socket
        """
        try:
            self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.server.bind((host, port))
            self.server.listen(LISTEN)
            print("listening [*] on {}:{}".format(host, port))
        except socket.error as e:
            print(e)
            if self.server:
                self.server.close()
            sys.exit(1)
        except Exception as e:
            print(e)
            if self.server:
                self.server.close()
            sys.exit(1)

    def run(self):
        """
        Se reciben y procesan las peticiones entrantes.
        """
        try:
            print("starting listening")
            while True:
                conn, client_addr = self.server.accept()
                request = conn.recv(MAX_DATA_RECV)
                backend_found = self.get_backend(request) # se busca el servidor backend con respecto a la url de entrada
                if not backend_found: # En el caso que no exista la url a procesar, se cierra el socket
                    conn.close()
                else:
                    #Se crea un hilo para procesar la petición y dar respecta al cliente.
                    threadx = threading.Thread(target=self.proxy_thread, args=(conn, client_addr, request, backend_found),)
                    threadx.start()
        except Exception as e:
            print(e)
            if self.server:
                self.server.close()
            sys.exit(1)

    def get_backend(self, request):
        """
        Busca el servidor back respecto a la url de procesamiento
        """
        if not request:
            return None
        first_line = str(request).split('\n')[0]
        url = first_line.split(' ')[1]
        port = None
        webserver = None
        protocol = None
        hostname = None
        for key in ALLOWED:
            if key in url:
                protocol = ALLOWED[key][0]
                webserver = ALLOWED[key][1]
                port = ALLOWED[key][2]
                hostname = ALLOWED[key][3]
                break
        return (protocol, webserver, port, hostname)

    def proxy_thread(self, conn, client_addr, request, backend_found):
        """
        Al iniciar el hilo, se invoca este metodo, valida si el back es http o https y redirecciona.
        """
        protocol = backend_found[0]
        if protocol == 'http':
            self.http(conn, client_addr, request, backend_found)
        elif protocol == 'https':
            self.https(conn, client_addr, request, backend_found)
        else:
            conn.close()
        

    def http(self, conn, client_addr, request, backend_found):
        """
        Procesa la peticion http y da respuesta al cliente
        """
        print("is HTTP")
        s = None
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.resolve(s, conn, request, backend_found)
        except socket.error as e:
            print(e)
            if s:
                s.close()
            if conn:
                conn.close()
        except Exception as e:
            print(e)
            if s:
                s.close()
            if conn:
                conn.close()
        finally:
            return "http"

    def https(self, conn, client_addr, request, backend_found):
        """
        Procesa la peticion http y da respuesta al cliente
        """
        print("is HTTPS")
        hostname = backend_found[3]

        server_cert = 'server.crt'
        client_cert = 'client.crt'
        client_key = 'client.key'

        ss = None
        s = None
        try:
            context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=server_cert)
            context.load_cert_chain(certfile=client_cert, keyfile=client_key)
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            ss = context.wrap_socket(s, server_side=False, server_hostname=hostname)
            self.resolve(ss, conn, request, backend_found)
        except socket.error as e:
            print(e)
            if ss:
                ss.close()
            if conn:
                conn.close()
        except Exception as e:
            print(e)
            if ss:
                ss.close()
            if conn:
                conn.close()
        finally:
            return "https"

    def resolve(self, s, conn, request, backend_found):
        """
        Resuelve la petición
        """
        try:
            webserver = backend_found[1]
            port = backend_found[2]

            s.settimeout(TIME_OUT)
            s.connect((webserver, port))
            print("SOCKET established. Peer: {}".format(s.getpeername()))
            s.send(request)
            while 1:
                data = s.recv(MAX_DATA_RECV)
                if (len(data) > 0):
                    conn.send(data)
                else:
                    break
            s.shutdown(socket.SHUT_RDWR)
            s.close()
            conn.close()
        except socket.error as e:
            print(e)
            if s:
                s.close()
            if conn:
                conn.close()
        except Exception as e:
            print(e)
            if s:
                s.close()
            if conn:
                conn.close()

Language: Python 3.8


app.py

#!/usr/bin/python3
import sys
from providers.proxy import ProxyServer

if __name__ == '__main__':
    try:
        server = ProxyServer(port = 8080)
        server.run()
    except KeyboardInterrupt as e:
        print ("Ctrl C - Stopping server", e)
        sys.exit(1)
    except Exception as e:
        print ("Stopping server", e)
        sys.exit(1)

Language: Python 3.8


Resultados:


Se tiene un servidor back de pruebas, el cual consiste en recibir los datos de un cliente y generar un identificador único.

Para estas pruebas el servidor socket recibirá la petición, la redirigirá al servidor backend, esperará la respuesta y posteriormente le responderá al cliente:


1. Petición directo al backend - puerto 8989:


Resultado:
Tiempos de respuestas: 11ms
Tamaño: 201b
Estado: 200 - OK

2. Petición por PROXY - Puerto 8080:


Resultado:
Tiempos de respuestas: 9ms
Tamaño: 201b
Estado: 200 - OK

Consumo de PROXY en cuanto a recursos:




Código fuente: CÓDIGO FUENTE AQUÍ


En el caso que se requiera ejecutar en background: nohup python3 -u ./app.py > ./output.log &


Notas finales:


El proxy opera con gran rendimiento y muy buenos tiempos de respuestas.

No olvides compartir y darle +1



Comentarios

Entradas más populares de este blog

Ejemplo Log4j 2 en JAVA | Log4j 2 en Springboot | Configuración Log4j 2 | Log4j 2 in SpringBoot| Example Log4j 2 in SpringBoot | Configuring Log4j 2

Python: Inyección de dependencias

GOlang con Docker | GOlang with Docker | GO con Docker | GO with Docker