Руководство по написанию своего плагина к Sioln's Sharp Proxy

Обзор классов
Пример написания для C#
Как избежать общих ошибок.
Скачать библиотеку классов
Скачать проекты примеров

Обзор Классов:

SharpProxyService.SharpProxyPlugin

Класс, описывающий библиотеку(плагин), подключаемый к прокси. Плагины автоматически подключаются из папки Plugins. Ваш плагин должен быть унаследован от этого класса.

public abstract void AfterProxyRequestDisposed(SharpProxyService.ProxyRequest DisposedRequest) Вызывается после завершения обслуживания запроса. Предназначено в первую очередь для DATA MINER типа плагинов.
public abstract void AfterServerDisconnected(SharpProxyService.ProxyRequest CurrentRequest) Вызывается после того, как удалённый сервер разорвал соединение. Из-за Keep-Alive может быть не вызван вообще, т.к. соединение разорвёт клиент. Предполагается использование там, где нужно иметь гарантию отсутствия разрыва соединения, как следствие сторонних причин.
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult BeforeSendDataToClient(byte[] BYTES, int BytesCount, SharpProxyService.ProxyRequest CurrentProxyRequest) Вызывается когда информация от удалённого сервера получена, но ещё не передана клиенту.
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult BeforeSendDataToServer(byte[] BYTES, int BytesCount, SharpProxyService.ProxyRequest CurrentProxyRequest) Вызывается перед тем как отправить данные запроса серверу.
public abstract void LoadPluginSettings(string CWD) Вызывается когда ядро прокси загрузило и инициализировало плагин. Предполагается загрузка каких-то настроек плагина. CWD - например C:\SharpProxy
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult OnClientConnected(System.Net.Sockets.Socket ClientSocket) Вызывается, когда происходит соединение нового клиента.
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult OnClientDataArrived(byte[] BYTES, int BytesCount, SharpProxyService.ProxyRequest CurrentProxyRequest) Вызывается когда поступают данные от клиента. Тут есть два ньюанса для тех, кто собирается как-то ассоциировать данные с ProxyRequest.
1.
На текущий момент CurrentProxyRequest может быть null, т.к. мы должны сначала пролучить \r\n\r\n от клиента и только потом сформируем ProxyRequest
2.
Те данные, которые мы получили могут оказаться частью следующего запроса клиента.
public abstract void OnClientDisconnected(System.Net.Sockets.Socket ClientSocket Вызывается когда клиент закрывает соединение.
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult OnFtpDataArrived(byte[] BYTES, int BytesCount, SharpProxyService.ProxyRequest CurrentProxyRequest Вызывается когда поступают данные от FTP сервера. (Имеется в виду именно данные, т.к. они идут по другому сокету и через OnServerDataArrived их не поймать)
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult OnNewProxyRequestBuild(SharpProxyService.ProxyRequest CurrentProxyRequest Вызывается когда построен очередной ProxyRequest - если нужно что-то поменять в заголовках, хосте, порту, имени файла - меняйте в этот момент.
public abstract void OnPlug(System.Collections.Generic.List<SharpProxyPlugin> TheAllLoadedPlugins Вызывается перед LoadPluginSettings - предварительная инициализация. TheAllLoadedPlugins - коллекция плагинов, с которыми придётся работать. Тут вы можете проверить наличие критических несовместимых плагинов или, наоборот, каких-то необходимых. Например, если мы ACL плагин, который высылает запрос авторизации, то неплохо бы проверить, что есть хотя бы один плагин AUTHORIZATOR, иначе, кто нам будет расшифровывать ответ клиента?
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult OnProxyRequestDisposing(SharpProxyService.ProxyRequest CurrentProxyRequest После завершения обслуживания ProxyRequest - стадия для добавления в ExtraLogData запроса какой-то своей информации.
public abstract SharpProxyService.SharpProxyPlugin.PluginVoidResult OnServerDataArrived(byte[] BYTES, int BytesCount, SharpProxyService.ProxyRequest CurrentProxyRequest Вызывается когда поступают данные от сервера.
public abstract void OnUnPlug(SharpProxyService.SharpProxyPlugin.UnplugReason TheReason Вызывается перед тем, как ядро перестанет передавать управление плагину. Уберите здесь весь мусор, уничтожьте формы, если есть и т.д. в общем, это ваш персональный Dispose ;)
public abstract void Show_Setting_For_User_To_Edit(System.Windows.Forms.Form TheMDIParent_Form Вызывается когда пользователь хочет посмотреть/изменить настройки плагина. TheMDIParentForm должна быть назначена родителем MDI вашей формы.
public abstract string PluginName { get; }  Просто верните имя плагина.
public abstract System.Collections.Hashtable PluginSettings { get; }  Эту таблицу предполагается использовать для обмена настройками между плагинами. Я её ещё нигде не использовал, но на всякий случай, предусмотрел. Если не использутете - возвращайте null.
public abstract int PluginVersion { get; }  Версия плагина. 1,2,3... N
public abstract SharpProxyService.SharpProxyPlugin.PluginType TheType { get; }  Тип плагина. От типа напрямую зависит очерёдность плагина в вызове методов. (Индекс очередёности может быть прочитан в логе плагинов прокси)

SharpProxyService.ProxyRequest

Класс, описывающий запрос к прокси серверу.

public long CurrentSpeedLimit { set; get; } Ограничение скорости закачки с сервера (bps) Можно менять когда угодно.
public string FileName { set; get; } То что будет отправлено серверу после <GET|POST> и между HTTP/1.X, т.е. / или /index.html или /somedir/somesrcipt?params
public string Headers { get; } То, что мы отправим серверу после соединения. Формируется из заголовков, протокола, имени файла и  т.д.
public string Host { set; get; } Хост сервера, с которым мы соединимся/соединились.
public string Method { set; get; } Метод: GET, POST, CONNECT
public int Port { set; get; } Порт сервера.
public string Proto { set; get; } Протокол: http, ftp.
public string Protocol { set; get; } HTTP/1.0, HTTP/1.1 ..
public string URL { set; get; } Формируется только из данных, полученных от клиента. Т.е. если вы измените host, filename или что-то ещё, URL останется неизменным. Просто, справочная информация.
public long WholeLength { get; } Длина заголовков + цифра указанная в Content-Length. Используется ядром.
public System.Collections.Generic.List<ProxyRequest.Authorization> AUTHORIZATION Список авторизаций. Если плагин AUTHORIZATOR получил данные о пользователе и они верны он должен добавить в этот список новый объект.
public long BytesDownloaded Сколько мы скачали с сервера.
public long BytesUploaded Сколько мы отправили на сервер.
public System.Net.IPAddress ClientIP IP адрес клиента.
public System.Net.Sockets.Socket ClientSocket Сокет клиента. Если не хватает функционала PluginVoidResult - пользуйтесь этим полем.
public System.Collections.Generic.Queue<string> CommandsNeedToSentToFTP Команды которые будут отправлены FTP серверу для получения файла. По умолчанию это "USER anonymous\r\n", "PASS anonymous@SharpProxy.ru\r\n", "PASV\r\n"
public System.Collections.Hashtable ExtraLogData Таблица для дополнительной информации по запросу. Если вам есть что добавить к стандартной информации - пишите сюда. Ключи и значения должны быть строковыми. Отсюда плагины типа DATA MINER будут брать дополнительную информацию.
public System.Collections.Hashtable HeadersFields Собственно, заголовки. Все ключи капитализированы. Например HeadersFields["CONTENT-LENGTH:"]
public int HeadersLength Длина заголовков отсылаемых серверу.
public bool IsDisposed Если истина - запрос считается обслуженным. (руками не менять)
public System.Collections.Hashtable PluginData Служит для хранения промежуточной информации для плагина(так называемых StateObjects).

SharpProxyService.SharpProxyInnerService

Класс, описывающий встраиваемую службу прокси сервера. Службы не обслуживают запросы к HTTP прокси, они напрямую расширяют сетевой функционал сервера. Пример службы - Port Mapping или простенький SMTP сервер.

public abstract void OnStart(string CWD) Вызывается пристарте службы. В качестве параметра - рабочий директорий прокси. Например c:\\SharpPRoxy
public abstract void OnStop() Вызывается при остановке - освобождаем ресурсы здесь.
public abstract void Show_Setting_For_User_To_Edit(System.Windows.Forms.Form TheMDIParent_Form) Вызывается когда пользователь хочет посмотреть/изменить настройки службы. TheMDIParentForm должна быть назначена родителем MDI вашей формы.
public abstract string ServiceName { get; } Вернуть имя службы
public abstract int ServiceVersion { get; } Вернуть версию.


Пример плагина написания на C#

  1. Для написания плагина нам понадобится эта библиотека (она входит в комплект прокси). В библиотеке описаны классы: SharpProxyService.ProxyRequest - от него будет наследован наш класс плагина и SharpProxyService.SharpProxyPlugin - этот класс описывает запрос прокси сервера.
  2. Итак, поехали: File->New->Project:
  3. Подключаем библиотеку AbstractClass.dll к проекту.
  4. Изменяем default namespace проекта (по желанию - мне, например, так удобнее) на SharpProxyService
  5. Ответственный момент :) Наследуемся от SharpProxyPlugin:
     

    namespace SharpProxyService

    {

    public class SamplePlugin:SharpProxyPlugin

    {

  6. Дальше делаем override для всех методов и свойств нашего класса.
     

    public override void OnPlug(List<SharpProxyPlugin> TheAllLoadedPlugins){}

     

  7. Если возникают вопросы по тому для чего нужен тот или иной метод - смотрим в Object Browser или в AbstractClass.XML
  8. От плагина так же требуется уметь показать свои настройки пользователю для редактирования. Для этого есть метод:
     

    public override void Show_Setting_For_User_To_Edit(System.Windows.Forms.Form TheMDIParent_Form)

    Он должен создать окно и назначить ему родителя MDI - TheMDIParent_Form переданную в параметрах.

  9. Собственно функционал.
    Я избрал для примера  подмену заголовков клиента. Конкретно - User-Agent.
  10. Итак, перехватываем создание нового ProxyRequest и меняем User-Agent:
     
    public override PluginVoidResult OnNewProxyRequestBuild(ProxyRequest CurrentProxyRequest)

    {

    if (CurrentProxyRequest.HeadersFields.ContainsKey("USER-AGENT:"))

    {

    CurrentProxyRequest.ExtraLogData.Add("OLD USER-AGENT:", (string)CurrentProxyRequest.HeadersFields["USER-AGENT:"]); //На память логеру

    CurrentProxyRequest.HeadersFields["USER-AGENT:"] = MyConfig.UserAgent; // Вот здесь мы подменяем если есть

    }

    else

    {

    CurrentProxyRequest.HeadersFields.Add("USER-AGENT:", MyConfig.UserAgent); // А здесь добавляем если не было

    CurrentProxyRequest.ExtraLogData.Add("OLD USER-AGENT:", "WAS NOT SET"); //На память логеру

    }

    return DoNothing;

    }

     

    Что делает наш код:

    Проверяет - есть ли вообще такое поле в заголовках. Хранилищем заголовков у нас служит хэш-таблица CurrentProxyRequest.HeadersFields, причём все ключи в ней - капитализированные строки. Если нет - создаёт такой заголовок. Подменяет. И оставляет на откуп плагину логеру информацию в хэш-таблице CurrentProxyRequest.ExtraLogData о том, какой был на самом деле заголовок.

    return DoNothing; - дело в том, что часть методов нашего плагина должна возвращать т.н. PluginVoidResult - результат обработки. Он указывает ядру нужно ли записать что-то в лог плагина, нужно ли что-то отправить клиенту(например, какие-то HTTP коды). Но, т.к. большинство методов ничего подобного не делают, мы в самом классе плагина создали переменную

    PluginVoidResult DoNothing = new PluginVoidResult();

    Которая эквивалентна "продолжить работу без каких либо сторонних действий" для ядра.

    CurrentProxyRequest.HeadersFields["USER-AGENT:"] = MyConfig.UserAgent;

    Что за MyConfig.UserAgent? MyConfig - это публичный статический экземпляр класса Config. Через него плагин узнаёт свои настройки и эти же настройки можно менять через GUI в RunTime. Это один из вариантов реализации настроек.

     

Прилагаю пару готовых простеньких плагинов и одну службу.

 

Как избежать общих ошибок:

1. Проверяйте CurrentProxyRequest на значение null в событиях поступления данных от клиента. Т.к. вполне нормальна ситуация, когда от клиента поступают данные, но запрос ещё не сформирован.

2. Исключения плагинов сыпятся в лог плагинов - хоть какое-то средство диагностики.

3. Рекомендую обращаться к данным плагина в запросе таким образом:

if (CurrentProxyRequest.PluginData.ContainsKey(this.PluginName))

{

Config.StateObject SO = (Config.StateObject)CurrentProxyRequest.PluginData[this.PluginName];

...

Чем это лучше? Тем, что прокси не даст загрузить два плагина с эквивалентным PluginName и вы можете быть уверены, что ваши данные не испортят.


 

 

Hosted by uCoz