Постоянные соединения HTTP 1.1 с использованием сокетов в Java - PullRequest
4 голосов
/ 08 октября 2008

Допустим, у меня есть Java-программа, которая делает HTTP-запрос на сервере, использующий HTTP 1.1, и не закрывает соединение. Я делаю один запрос и читаю все данные, возвращенные из входного потока, который я привязал к сокету. Однако после выполнения второго запроса я не получаю ответа от сервера (или есть проблема с потоком - он больше не обеспечивает ввод). Если я делаю запросы по порядку (Запрос, запрос, чтение), он работает нормально, но (запрос, чтение, запрос, чтение) - нет.

Может ли кто-нибудь пролить свет на то, почему это может происходить? (Далее следуют фрагменты кода). Независимо от того, что я делаю, isr_reader.read () второго цикла чтения только когда-либо возвращает -1.

try{
        connection = new Socket("SomeServer", port);
        con_out = connection.getOutputStream();
        con_in  = connection.getInputStream();
        PrintWriter out_writer = new PrintWriter(con_out, false);
        out_writer.print("GET http://somesite HTTP/1.1\r\n");
        out_writer.print("Host: thehost\r\n");
        //out_writer.print("Content-Length: 0\r\n");
        out_writer.print("\r\n");
        out_writer.flush();

        // If we were not interpreting this data as a character stream, we might need to adjust byte ordering here.
        InputStreamReader isr_reader = new InputStreamReader(con_in);
        char[] streamBuf = new char[8192];
        int amountRead;
        StringBuilder receivedData = new StringBuilder();
        while((amountRead = isr_reader.read(streamBuf)) > 0){
            receivedData.append(streamBuf, 0, amountRead);
        }

// Response is processed here.

        if(connection != null && !connection.isClosed()){
            //System.out.println("Connection Still Open...");

        out_writer.print("GET http://someSite2\r\n");
        out_writer.print("Host: somehost\r\n");
        out_writer.print("Connection: close\r\n");
        out_writer.print("\r\n");
        out_writer.flush();

        streamBuf = new char[8192];
        amountRead = 0;
        receivedData.setLength(0);
        while((amountRead = isr_reader.read(streamBuf)) > 0 || amountRead < 1){
            if (amountRead > 0)
                receivedData.append(streamBuf, 0, amountRead);
        }
}
        // Process response here
    }

Ответы на вопросы: Да, я получаю фрагментированные ответы с сервера. Я использую необработанные сокеты из-за внешнего ограничения.

Извиняюсь за беспорядок кода - я переписывал его из памяти и, похоже, внес несколько ошибок.

Таким образом, консенсус в том, что я должен либо сделать (запросить, запросить, прочитать) и позволить серверу закрыть поток, как только я достигну конца, либо, если я сделаю (запросить, прочитать, запросить, прочитать), остановиться, прежде чем я нажму конец потока, так что поток не закрыт.

Ответы [ 5 ]

5 голосов
/ 09 октября 2008

Согласно вашему коду, единственный раз, когда вы достигнете операторов, относящихся к отправке второго запроса, - это когда сервер закрывает выходной поток (ваш входной поток) после получения / ответа на первый запрос.

Причина в том, что ваш код должен читать только первый ответ

while((amountRead = isr_reader.read(streamBuf)) > 0) {
  receivedData.append(streamBuf, 0, amountRead);
}

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

Проблема с HTTP-ответами состоит в том, что они не сообщают вам, сколько байтов нужно прочитать из потока до конца ответа. Это не имеет большого значения для ответов HTTP 1.0, поскольку сервер просто закрывает соединение после ответа, что позволяет вам получить ответ (строка состояния + заголовки + тело), ​​просто прочитав все до конца потока.

С постоянными соединениями HTTP 1.1 вы больше не можете просто читать все до конца потока. Сначала необходимо прочитать строку состояния и заголовки, построчно, а затем, основываясь на коде состояния, и заголовки (например, Content-Length) решают, сколько байтов нужно прочитать, чтобы получить тело ответа (если оно присутствует в все). Если вы сделаете все правильно, ваши операции чтения будут завершены до того, как соединение будет закрыто или произойдет тайм-аут, и вы прочтете именно тот ответ, который отправил сервер. Это позволит вам отправить следующий запрос и затем прочитать второй ответ точно так же, как и первый.

P.S. Запрос, запрос, чтение могут быть «работающими» в том смысле, что ваш сервер поддерживает конвейерную обработку запросов и, таким образом, получает и обрабатывает оба запроса, и в результате вы читаете оба ответа в один буфер как «первый» ответ.

P.P.S Убедитесь, что ваш PrintWriter использует кодировку US-ASCII. В противном случае, в зависимости от кодировки вашей системы, строка запроса и заголовки ваших HTTP-запросов могут быть неправильно сформированы (неправильная кодировка).

3 голосов
/ 21 июля 2010

Написание простого клиента http / 1.1, уважающего RFC, не такая сложная задача. Чтобы решить проблему блокирования доступа ввода-вывода при чтении сокета в java, вы должны использовать классы java.nio. SocketChannels дают возможность выполнять неблокирующий доступ ввода / вывода.

Это необходимо для отправки HTTP-запроса на постоянное соединение.

Кроме того, классы nio дадут лучшие результаты.

Мой стресс-тест дает следующие результаты:

  • HTTP / 1.0 (java.io) -> HTTP / 1.0 (java.nio) = + 20% быстрее

  • HTTP / 1.0 (java.io) -> HTTP / 1.1 (java.nio с постоянным соединением) = + 110% быстрее

0 голосов
/ 12 октября 2008

Написание собственного правильного клиента Реализация HTTP / 1.1 нетривиальна; Исторически сложилось так, что большинство людей, которых я видел, пытались сделать это неправильно. Их реализация обычно игнорирует спецификацию и выполняет то, что работает с одним конкретным тестовым сервером - в частности, они обычно игнорируют требование уметь обрабатывать фрагментированные ответы.

Писать собственный HTTP-клиент, вероятно, плохая идея, если у вас нет ОЧЕНЬ странных требований.

0 голосов
/ 08 октября 2008

Есть ли конкретная причина, по которой вы используете необработанные сокеты, а не соединение Java по URL или Commons HTTPClient ?

HTTP не легко получить право. Я знаю, что Commons HTTP Client может повторно использовать соединения, как вы пытаетесь это сделать.

Если для использования Sockets нет особой причины, я бы порекомендовал:)

0 голосов
/ 08 октября 2008

Убедитесь, что в вашем запросе есть Connection: keep-alive. Это может быть спорным вопросом, хотя.

Какой ответ возвращает сервер? Вы используете чанкованный перевод? Если сервер не знает размер тела ответа, он не может предоставить заголовок Content-Length и должен закрыть соединение в конце тела ответа, чтобы указать клиенту, что контент закончился. В этом случае поддержка активности не будет работать. Если вы генерируете контент на лету с помощью PHP, JSP и т. Д., Вы можете включить буферизацию вывода, проверить размер накопленного тела, нажать заголовок Content-Length и очистить буфер вывода.

...