100 строк кода: Прокси-сервер на Python

Что такое прокси-сервер?

Прокси-сервер (proxy server) – это сервер, исполняющий роль посредника между клиентом и целевым сервером. Прокси-сервер действует «от лица» клиента и, в зависимости от поставленной задачи, может выполнять различные преобразования данных. На рисунке ниже показана логика работы прокси-сервера:

Мы поставили перед собой задачу разработать прокси-сервер, используя только стандартные библиотеки Python. Перед началом разработки были сформулированы следующие критерии функциональности приложения:

  • Каждое новое соединение клиента с прокси-сервером, должно инициировать новое соединение с целевым сервером.
  • Каждый пакет данных, приходящий на прокси-сервер от клиента, должен пересылаться целевому серверу.
  • Каждый пакет данных, приходящий на прокси-сервер от целевого сервера, должен пересылаться соответствующему клиенту.
  • Поддержка работы с несколькими клиентами.
  • Высокая скорость.
  • Малый объем потребляемых ресурсов.

Ниже представлен код готового приложения:

#!/usr/bin/python

# This is a simple port-forward / proxy, written using only the default python

# library. If you want to make a suggestion or fix something you can contact-me

# at voorloop_at_gmail.com

# Distributed over IDC(I Don't Care) license

import socket

import select

import time

import sys



# Changing the buffer_size and delay, you can improve the speed and bandwidth.

# But when buffer get to high or delay go too down, you can broke things

buffer_size = 4096

delay = 0.0001

forward_to = ('smtp.zaz.ufsk.br', 25)



class Forward:

    def __init__(self):

        self.forward = socket.socket(socket.AF_INET, socket.SOCK_STREAM)



    def start(self, host, port):

        try:

            self.forward.connect((host, port))

            return self.forward

        except Exception, e:

            print e

            return False



class TheServer:

    input_list = []

    channel = {}



    def __init__(self, host, port):

        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(200)



    def main_loop(self):

        self.input_list.append(self.server)

        while 1:

            time.sleep(delay)

            ss = select.select

            inputready, outputready, exceptready = ss(self.input_list, [], [])

            for self.s in inputready:

                if self.s == self.server:

                    self.on_accept()

                    break



                self.data = self.s.recv(buffer_size)

                if len(self.data) == 0:

                    self.on_close()

                    break

                else:

                    self.on_recv()



    def on_accept(self):

        forward = Forward().start(forward_to[0], forward_to[1])

        clientsock, clientaddr = self.server.accept()

        if forward:

            print clientaddr, "has connected"

            self.input_list.append(clientsock)

            self.input_list.append(forward)

            self.channel[clientsock] = forward

            self.channel[forward] = clientsock

        else:

            print "Can't establish connection with remote server.",

            print "Closing connection with client side", clientaddr

            clientsock.close()



    def on_close(self):

        print self.s.getpeername(), "has disconnected"

        #remove objects from input_list

        self.input_list.remove(self.s)

        self.input_list.remove(self.channel[self.s])

        out = self.channel[self.s]

        # close the connection with client

        self.channel[out].close()  # equivalent to do self.s.close()

        # close the connection with remote server

        self.channel[self.s].close()

        # delete both objects from channel dict

        del self.channel[out]

        del self.channel[self.s]



    def on_recv(self):

        data = self.data

        # here we can parse and/or modify the data before send forward

        print data

        self.channel[self.s].send(data)



if __name__ == '__main__':

        server = TheServer('', 9090)

        try:

            server.main_loop()

        except KeyboardInterrupt:

            print "Ctrl C - Stopping server"

            sys.exit(1)

Пояснение

Класс Forward

Устанавливает соединение между прокси-сервером и целевым сервером.

Класс TheServer

Основной класс приложения.

Метод TheServer.main_loop()

Список input_list содержит все доступные сокеты, управление которыми осуществляется с помощью select.select(). Первым в список добавляется серверный сокет самого прокси-сервера. Каждое новое подключение к этому сокету будет инициировать вызов метода on_accept().

Если текущий сокет из списка inputready, возвращенного select.select(), не предполагает новое соединение, значит, мы имеем дело с входящими данными (возможно от целевого сервера, возможно от клиента). Если размер данных равен нулю, значит, это запрос на закрытие соединения, в противном случае пакет необходимо переслать в соответствующее место назначения.

Метод TheServer.on_accept()

Этот метод устанавливает новое соединение с целевым сервером, а также принимает подключение текущего клиента. Оба сокета добавляются в список input_list для дальнейшей обработки в методе main_loop(). Оба сокета также сохраняются в словаре channel для сопоставления конечных точек назначения (клиент <=> целевой сервер).

Метод TheServer.on_recv()

 Этот метод выполняет обработку данных (при необходимости) и пересылает их по месту назначения (клиент <= прокси-сервер => целевой сервер).

Метод TheServer.on_close()

Данный метод закрывает соединение между прокси-сервером и целевым сервером, а также между прокси-сервером и клиентом. Соответствующие объекты удаляются.

Заключение

Перед вами полноценный прокси-сервер. Как видите, компактное приложение решает все поставленные задачи.

Источник

Перевод Станислава Петренко

комментария 3

  1. Sasha:

    Ошибка на 39 строке

Добавить комментарий

Ваш адрес email не будет опубликован.

закрыть

Поделиться

Отправить на почту
закрыть

Вход

закрыть

Регистрация

+ =