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

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

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 через сокеты (не очень большой и сложный, хотя, я - новичок). Лучшее должно было бы отправить ссылку на пример, который читает тело сообщения и правильно обрабатывает случаи когда: содержание имеет закодированный разделенный на блоки, является gzip-, или выкачайте - закодированный, заголовок Довольной Длины опущен (концы сообщения, когда соединение закрывается). Что-то как исходный код класса 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) с уже полученными данными.

8
задан Poma 10 June 2010 в 05:41
поделиться

9 ответов

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

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

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

РЕДАКТИРОВАТЬ: Код ротора достаточно сложен, и его трудно читать / перемещаться по веб-страницам. Но все же усилия по внедрению поддержки 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)

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

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

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

9
ответ дан 5 December 2019 в 17:35
поделиться

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

1
ответ дан 5 December 2019 в 17:35
поделиться

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

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

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

-1
ответ дан 5 December 2019 в 17:35
поделиться

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

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

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

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

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

0
ответ дан 5 December 2019 в 17:35
поделиться

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

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

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

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

-1
ответ дан 5 December 2019 в 17:35
поделиться

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

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

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

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

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

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

Когда используется строка \ r \ n \ r \ n , k будет равняться 0x0d0a0d0a

2
ответ дан 5 December 2019 в 17:35
поделиться

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

0
ответ дан 5 December 2019 в 17:35
поделиться

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

Вам не нужно писать свой собственный класс HTTP/сокета. Вам просто нужно расширить 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);
   }

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

0
ответ дан 5 December 2019 в 17:35
поделиться

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

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
0
ответ дан 5 December 2019 в 17:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: