Эффективная пересылка HTTP-запросов с помощью IHttpAsyncHandler - PullRequest
3 голосов
/ 21 марта 2012

Я разрабатываю фронт-контроллер HTTP на основе паттерна Мартина Фаулера ( ссылка ). В моем случае контроллер имеет следующие обязанности: - Unmarshall инкапсулированные данные - авторизовать запрос - Логирование - переадресация / пересылка запроса на другой сервер

В голову пришли следующие возможные решения: - (синхронно) IHttpHandler, пересылка запроса с помощью класса WebClient или HttpWebRequest - (асинхронный) IHttpListener (решение не IIS) - (асинхронный) IHttpAsyncHandler

Идеальная ситуация, когда FC может обрабатывать множество одновременных запросов (> 10000 TPS) без загрузки процессора.

Для тестирования решений я создал небольшую платформу, в которой 3 клиента делают запросы, фронт-контроллер расположен посередине и 2 сервера, которые отвечают на запросы, переданные FC. Фреймворк тестирует 3 сценария, во-первых, он тестирует с быстрыми ответами с небольшими полезными нагрузками, во-вторых: быстрые ответы с большими полезными нагрузками (> 100 КБ) и, наконец, его тесты с медленными ответами (> 3 секунды) и небольшими полезными нагрузками.

Количество транзакций в секунду (TPS) падает до предельного минимума (<25 TPS) с последним тестом с синхронными обработчиками HTTP. Я думаю, это связано с тем, что обработчик блокирует поток, когда ожидает ответа. Чтобы преодолеть эту проблему, я начал реализовывать асинхронный обработчик (см. Код ниже). Проблема в том, что это просто не работает. </p>

Последнее (непроверенное) решение, которое пришло на ум, - это использование класса HttpListener. Предполагая, что это более легкое решение с точным контролем зерна для параллелизма. Я видел пример реализации с использованием RX-фреймворка Хосе Ф. Романиелло ( ссылка ).

У меня такой вопрос: почему не работает код обработчика? Это самый эффективный способ сделать это? или я должен быть в пользу решения HttpListener.

Асинхронный код HTTP-обработчика:

public class ForwardRequestHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        var uri = GetForwardUriFor(context.Request.Url.PathAndQuery);

        var proxy = HttpWebRequest.Create(uri) as HttpWebRequest;
        return proxy.BeginGetResponse(new AsyncCallback(EndProcessRequest), new ForwardedRequestContext(context, proxy));
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        var proxy = result.AsyncState as ForwardedRequestContext;
        proxy.TransferResponse(result);
    }

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        throw new NotSupportedException();
    }

    private Uri GetForwardUriFor(string path)
    {
        var loadbalancer = new RoundRobinLoadBalancer();
        var endpoint = loadbalancer.GetRandomEndPoint();

        return new Uri(
            string.Format("http://{0}{1}", endpoint, path)
        );
    }
}

public class ForwardedRequestContext
{
    private readonly HttpContext context;
    private readonly HttpWebRequest forwarder;

    public ForwardedRequestContext(HttpContext context, HttpWebRequest forwarder)
    {
        this.context = context;
        this.forwarder = forwarder;
    }

    public void TransferResponse(IAsyncResult ar)
    {
        var response = GetResponse();

        var result = forwarder.EndGetResponse(ar);
        response.StatusCode = 200;
        response.ContentType = result.ContentType;
        response.AddHeader("Content-Length", result.ContentLength.ToString());

        result.GetResponseStream().CopyTo(response.OutputStream);
        response.Flush();

        result.Close();
    }

    private HttpResponse GetResponse()
    {
        return context.Response;
    }
}

Ответы [ 2 ]

6 голосов
/ 28 ноября 2012

через return proxy.BeginGetResponse(new AsyncCallback(EndProcessRequest), ... вы создаете новый обратный вызов, и ASP.net не получает информацию о завершении запроса, поэтому не может остановить обработку запроса (это, по крайней мере, то, что я наблюдал во многих одновременных запросах).).Поэтому используйте вместо этого обратный вызов в качестве аргумента.

return proxy.BeginGetResponse(cb, ...

Cheers

0 голосов
/ 04 мая 2012

Одним из возможных решений этой проблемы может быть использование ARR для выполнения переадресации.

Вот пример выполнения этого на Azure:

https://github.com/richorama/AzureARR

...