Как получать HTTP-сообщения с помощью сокета

Я использую Socket класс для моего веб-клиента. Я не могу использовать HttpWebRequest поскольку он не поддерживает прокси-серверы socks. Поэтому я должен анализировать заголовки и обрабатывать кодировку chunked самостоятельно. Самое сложное для меня-определить длину контента, поэтому я должен читать его байт за байтом. Сначала я должен использовать ReadByte() чтобы найти последний заголовок (комбинация" rnrn"), затем проверьте, имеет ли тело кодировку передачи или нет. Если это так, я должен прочитать размер куска и т. д.:

public void ParseHeaders(Stream stream)
{
    while (true)
    {
        var lineBuffer = new List<byte>();
        while (true)
        {
            int b = stream.ReadByte();
            if (b == -1) return;
            if (b == 10) break;
            if (b != 13) lineBuffer.Add((byte)b);
        }
        string line = Encoding.ASCII.GetString(lineBuffer.ToArray());
        if (line.Length == 0) break;
        int pos = line.IndexOf(": ");
        if (pos == -1) throw  new VkException("Incorrect header format");
        string key = line.Substring(0, pos);
        string value = line.Substring(pos + 2);
        Headers[key] = value;
    }
}

но это подход имеет очень низкую производительность. Можете ли вы предложить лучшее решение? Возможно, некоторые примеры с открытым исходным кодом или библиотеки, которые обрабатывают http-запрос через сокеты (не очень большие и сложные, хотя я noob). Лучше всего было бы опубликовать ссылку на пример, который читает тело сообщения и правильно обрабатывает случаи, когда: содержимое имеет кодировку chunked, является gzip-или deflate - encoded, заголовок Content-Length опущен (сообщение заканчивается, когда соединение закрыто). Что-то вроде исходного кода HttpWebRequest класс.

Upd: Моя новая функция выглядит так:

int bytesRead = 0;
byte[] buffer = new byte[0x8000];
do
{
    try
    {
        bytesRead = this.socket.Receive(buffer);
        if (bytesRead <= 0) break;
        else
        {
            this.m_responseData.Write(buffer, 0, bytesRead);
            if (this.m_inHeaders == null) this.GetHeaders();
        }
    }
    catch (Exception exception)
    {
        throw new Exception("Read response failed", exception);
    }
}
while ((this.m_inHeaders == null) || !this.isResponseBodyComplete());

здесь GetHeaders() и isResponseBodyComplete() использовать m_responseData (MemoryStream) С уже полученными данными.

9 ответов


Я предлагаю вам не реализовывать это самостоятельно-протокол HTTP 1.1 достаточно сложен, чтобы сделать этот проект из нескольких человеко-месяцев.

вопрос в том, есть ли анализатор протокола HTTP-запросов для .NET? Этот вопрос был задан на SO, и в ответах вы увидите несколько предложений, включая исходный код для обработки HTTP-потоков.

преобразование необработанного HTTP-запроса в объект HTTPWebRequest

EDIT: Ротор код достаточно сложный и трудный для чтения / навигации в качестве веб-страниц. Но все же усилия по реализации добавления поддержки SOCKS намного ниже, чем реализация всего протокола HTTP самостоятельно. У вас будет что-то работающее в течение нескольких дней, самое большее, на что вы можете положиться, что основано на проверенной реализации.

запрос и ответ читаются/пишутся на NetworkStream, m_Transport, в Connection класса. Это используется в этих методы:

internal int Read(byte[] buffer, int offset, int size) 
//and
private static void ReadCallback(IAsyncResult asyncResult)

оба в http://www.123aspx.com/Rotor/RotorSrc.aspx?rot=42903

сокет создается в

private void StartConnectionCallback(object state, bool wasSignalled)

таким образом, вы можете изменить этот метод, чтобы создать сокет для вашего сервера socks, и сделать необходимое рукопожатие для получения внешнего соединения. Остальная часть кода может остаться прежней.

Я gammered эту информацию в 30 минут, глядя на страницы в интернете. Это должно идти намного быстрее, если вы загрузите их файлы в IDE. Это может показаться бременем, чтобы прочитать этот код - в конце концов, чтение кода намного сложнее, чем его написание, но вы делаете только небольшие изменения в уже установленной, рабочей системе.

чтобы убедиться, что изменения работают во всех случаях, будет разумно также проверить, когда соединение нарушено , чтобы гарантировать, что клиент повторно подключается с помощью того же метода, и поэтому восстанавливает соединение SOCKS и отправляет запрос SOCKS.


если проблема является узким местом с точки зрения ReadByte будучи слишком медленным, я предлагаю вам обернуть входной поток с StreamBuffer. Если проблема производительности, которую вы утверждаете, стоит дорого из-за небольших чтений, то это решит проблему для вас.

кроме того, вам не нужно это:

string line = Encoding.ASCII.GetString(lineBuffer.ToArray()); 

HTTP по дизайну требует, чтобы заголовок состоял только из символов ASCII. Вы действительно не хотите-или должны-превратить его в фактические строки .NET (которые Юникод.)

если вы хотите найти EOF заголовка HTTP, вы можете сделать это для хорошей производительности.

int k = 0;
while (k != 0x0d0a0d0a) 
{
    var ch = stream.ReadByte();
    k = (k << 8) | ch;
}

после строки \r\n\r\n это обнаружил k будет равна 0x0d0a0d0a


в большинстве (должны быть все) http-запросов должен быть заголовок content-length, который скажет вам, сколько байтов есть в теле запроса. Тогда это просто вопрос выделения соответствующего количества байтов и чтения этих байтов сразу.


хотя я склонен согласиться с mdma о попытке как можно больше избежать реализации собственного http-стека, один трюк, который вы могли бы рассмотреть,-это чтение из кусков потока среднего размера. Если вы делаете чтение и даете ему буфер, который больше, чем то, что доступно, он должен вернуть вам количество байтов, которые он прочитал. Это должно уменьшить количество системных вызовов и значительно ускорить вашу производительность. Вам все равно придется сканировать буферы, как и сейчас, хотя.


взглянуть на код другого клиента полезно (если не путать): http://src.chromium.org/viewvc/chrome/trunk/src/net/http/

в настоящее время я тоже делаю что-то подобное. Я нахожу лучший способ повысить эффективность клиента-использовать предоставленные функции асинхронных сокетов. Они довольно низкоуровневые и избавляются от напряженного ожидания и работы с потоками самостоятельно. Все они имеют Begin и End в названии метода. Но сначала я попробовал бы использовать блокировку, просто чтобы вы убрали семантику HTTP с пути. Тогда вы сможете работать над эффективностью. Помните: преждевременная оптимизация-это зло, поэтому заставьте ее работать, а затем оптимизируйте все!

кроме того: некоторые из вашей эффективности могут быть связаны с использованием ToArray(). Известно, что это немного дорого с точки зрения вычислений. Лучшим решением может быть сохранение промежуточных результатов в byte[] buffer и добавьте их в StringBuilder С правильным кодирование.

для данных gzipped или deflated, прочитайте все данные (имейте в виду, что вы можете не получить все данные при первом запросе. Следите за тем, сколько данных вы прочитали, и продолжайте добавлять в тот же буфер). Затем вы можете декодировать данные с помощью GZipStream(..., CompressionMode.Decompress).

Я бы сказал, что это не так сложно, как некоторые могут предполагать, вы просто должны быть немного авантюрным!


все ответы здесь о расширении сокета и / или TCPClient, похоже, упускают что - то действительно очевидное-что HttpWebRequest также является класс и поэтому может быть расширен.

вам не нужно писать свой собственный класс HTTP / socket. Вам просто нужно расширить HttpWebRequest с помощью пользовательского метода подключения. После подключения все данные являются стандартными HTTP и могут обрабатываться как обычно базовым классом.

public class SocksHttpWebRequest : HttpWebRequest

   public static Create( string url, string proxy_url ) {
   ... setup socks connection ...

   // call base HttpWebRequest class Create() with proxy url
   base.Create(proxy_url);
   }

рукопожатие носков не особенно сложный, поэтому, если у вас есть базовое понимание программирования сокетов, это не займет много времени, чтобы реализовать соединение. После этого HttpWebRequest может выполнять тяжелый подъем HTTP.


почему бы вам не прочитать до 2 новых строк, а затем просто захватить из строки? Производительность может быть хуже, но она все равно должна быть разумной:

Dim Headers As String = GetHeadersFromRawRequest(ResponseBinary)
   If Headers.IndexOf("Content-Encoding: gzip") > 0 Then

     Dim GzSream As New GZipStream(New MemoryStream(ResponseBinary, Headers.Length + (vbNewLine & vbNewLine).Length, ReadByteSize - Headers.Length), CompressionMode.Decompress)
ClearTextHtml = New StreamReader(GzSream).ReadToEnd()
End If                         

 Private Function GetHeadersFromRawRequest(ByVal request() As Byte) As String

        Dim Req As String = Text.Encoding.ASCII.GetString(request)
        Dim ContentPos As Integer = Req.IndexOf(vbNewLine & vbNewLine)

        If ContentPos = -1 Then Return String.Empty

        Return Req.Substring(0, ContentPos)
    End Function

Вы можете посмотреть на TcpClient класс System.Net, Это оболочка для сокета, которая упрощает основные операции.

оттуда вам придется прочитать протокол HTTP. Также будьте готовы сделать некоторые операции zip. Http 1.1 поддерживает GZip его содержимого и частичных блоков. Вам придется научиться разбирать их вручную.

основной Http 1.0 прост, протокол хорошо документирован онлайн, наше дружелюбное соседство Google может помочь вам с этим.


Я бы создал прокси-сервер SOCKS, который может туннелировать HTTP, а затем принимать запросы от HttpWebRequest и пересылать их. Я думаю, что это было бы намного проще, чем воссоздавать все, что делает HttpWebRequest. Вы можете начать с Privoxy, или просто свернуть свой собственный. Протокол прост и документирован здесь:

http://en.wikipedia.org/wiki/SOCKS

и на RFC, на которые они ссылаются.

Вы упомянули, что вы должны иметь много разных прокси-серверов-вы можете настроить локальный порт для каждого из них.