Я разрабатываю фронт-контроллер 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;
}
}