Разбор HTTP-ответа, захваченного tcpdump — объект имеет значение null, но после заголовка есть данные

Я пытаюсь проанализировать ответное сообщение HTTP из файла .pcap, захваченного tcpdump, с помощью pkts.io для анализа файла захвата и Apache httpcommons для анализа сообщения.

При разборе файла захвата я добавляю полезную нагрузку каждого пакета (полученного с помощью Packet.getPayload(), doc), который является частью сообщения в byte[] data.

Если я напечатаю new String(data, "UTF-8"), я получу это:

HTTP/1.1 200 OK
    Server: nginx
    Date: Fri, 10 Apr 2015 04:00:04 GMT
    Content-Type: text/html; charset=utf-8
    Transfer-Encoding: chunked
    Connection: keep-alive
    Keep-Alive: timeout=300
    Vary: Accept-Encoding
    Content-Encoding: gzip
    1dd
    ��������������S�n�0��+X_���
��q�b�a���������Ȓf�q��G�K�I��=���������χ/�rg�f�d"kʌ\�+1l���P
]�\^�@r�{�k��;pģ﷐�7�=t� `C+5qg�
...

Полный ответ на pastebin

Когда я пытаюсь разобрать HTTP-сообщение (код ниже), я получаю все заголовки в порядке, но resp.getEntity() возвращает null.

SessionInputBufferImpl inBuffer = new SessionInputBufferImpl(new HttpTransportMetricsImpl(), packet.getData().length);
InputStream inStream = new ByteArrayInputStream(packet.getData());
inBuffer.bind(inStream);
DefaultHttpResponseParser respParser = new DefaultHttpResponseParser(inBuffer);
HttpResponse resp = (HttpResponse) respParser.parse();

Куда я могу перейти отсюда, чтобы попытаться получить тело ответа в виде текста?


person Michelle    schedule 12.04.2015    source источник


Ответы (2)


При получении тела объекта вам нужно будет просмотреть как Transfer-Encoding, так и Content-Encoding и выполнить соответствующее декодирование. См. раздел 4 "Коды передачи" RFC 7230.

Посмотрите на классы в HttpComponents, такие как ChunkedInputStream (для фрагментированного Transfer-Encoding), и найдите код, который может распаковывать gzip-сжатый текст (для gzip Content-Encoding).

person Community    schedule 13.04.2015
comment
Оба они относятся только к содержанию сообщения, не так ли? (Т.е. я хотел бы обернуть InputStream, возвращаемый someHttpResponse.getEntity().getContent(), если сущность не была нулевой.) - person Michelle; 13.04.2015
comment
Если в ответе HTTP действительно нет пустой строки между Content-Encoding: gzip и 1dd, он искажен, так как между полями заголовка и телом должна быть пустая строка (CRLF). Если да, то не стоит ожидать, что его можно будет разобрать, хотя HttpComponents должен указывать на ошибку. Если в нем есть пустая строка, вы должны получить объект, и, если HttpComponents не распаковывает и не распаковывает его для вас, вам нужно будет подключить материал, чтобы он это сделал. - person ; 14.04.2015
comment
Хм. Я попытался получить содержимое вручную, и я вижу двойной CRLF и могу проанализировать его, используя поток массива байтов, завернутый в фрагментированный поток, завернутый в поток gzip. Но объект HttpResponse по-прежнему возвращает null для сущности. - person Michelle; 14.04.2015
comment
Отправляется ли ответ более чем в одном TCP-сегменте? Если да, то пересобираются ли TCP-сегменты вашим кодом или HttpComponents? Копирование вашего элемента pastebin и подсчет байтов в нем указывает на то, что в нем 4225 байтов, что, вероятно, не помещается в один сегмент TCP, поэтому он, предположительно, был повторно собран. Включает ли это все фрагменты? - person ; 15.04.2015
comment
Да, он пересобирается моим кодом, и да. Я использую тот же byte[] для HttpResponse, что и для своего кода, и я дважды проверил номера/содержимое пакетов, которые должны быть в сообщении, используя Wireshark. - person Michelle; 15.04.2015
comment
Тогда, вероятно, проблема либо с HttpComponents, либо с тем, как он используется. - person ; 15.04.2015
comment
@ Мишель, ты смогла извлечь тело ответа? Было бы здорово увидеть решение вашего вопроса. - person Ted; 21.08.2015

Мне не удалось заставить HttpResponse.getEntity() работать, поэтому мне пришлось самому анализировать ответ. Вот код, который я собрал. Он проходит через byte[], содержащий все содержимое ответа, в поисках пустой строки, разделяющей поля заголовка и тела, и копирует все после этого:

private byte[] getContent(byte[] message) {
    int start = -1;
    byte[] content = null;
    for (int i = 0; i < message.length; ++i) {
        if (start >= 0) {
            content[i-start] = message[i];
            continue;
        }
        System.out.print((char)message[i]);
        if (message[i] == (byte) 13 && message[i+1]==(byte)10 && message[i+2] == (byte) 13 && message[i+3]==(byte)10 ) { //CR
            start = i+4;
            content = new byte[message.length-(i+4)];
            i += 3;
        }
    }
    return content;
}

Затем, если в ответе есть Transfer-Encoding: chunked и Content-Encoding: gzip, я использовал ChunkedInputStream (из HttpComponents) и GZIPInputStream из java.util, чтобы вернуть фактическое содержимое.

byte[] content = getContent(packet.getData());
if (content.length > 0) {
    InputStream byteIS = new ByteArrayInputStream(content);
    SessionInputBufferImpl contentBuf = new SessionInputBufferImpl(new HttpTransportMetricsImpl(), content.length);
    contentBuf.bind(byteIS);

    ChunkedInputStream chunkedIS = new ChunkedInputStream(contentBuf);

    GZIPInputStream gzipIS = new GZIPInputStream(chunkedIS);

    while (gzipIS.available() != 0) {
        byte[] buf = new byte[128];
        gzipIS.read(buf);
        contentBuilder.append(new String(buf, "UTF-8"));
    }
    gzipIS.close();
    String contentString = contentBuilder.toString();
}
person Michelle    schedule 22.08.2015
comment
Спасибо за это. Настолько хромой, что не очевидно, как легко подготовить тело с помощью DefaultHttpResponseParser. - person Kyle; 10.07.2016