2015-11-28 2 views
0

Предупреждение: Это вопрос, на который я ответил. Недавно я столкнулся с этой проблемой и счел это очень сложным, хотя это звучит очень просто. Я решил опубликовать полный вопрос и ответ с примерами кода для будущих посетителей. Почему мой сокет сервера висит на чтении тела HTTP-запроса?


У меня есть ServerSocket, который прослушивает соединения на порт 12005. Он реализован в простейшей образом:

ServerSocket ss = new ServerSocket(12005); 
while(true) { 
    executorService.submit(new SocketProcessor(ss.accept()); 
} 

Здесь SocketProcessor просто Runnable, который обрабатывает входящие соединения:

@Override 
public void run() { 
    try { 
     String msg = IOUtils.toString(s.getInputStream()); 
     // process msg here 
    } finally { 
     s.close(); 
    } 
}  

проблема в том, что выполнение зависает по методу IOUtils.toString(InputStream is). Я думал, что стандартное решение будет работать лучше, поэтому я заменил его

BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); 
String line; 
while ((line = in.readLine()) != null) { 
    System.out.println(line); 
} 

Выход был что-то вроде:

POST/HTTP/1.1 
Host: http://localhost:12005 
Content-Type: application/json 
//other headers 
        <--- empty line 
        <--- here it hangs 

Если я прервать соединение на стороне вызывающего абонента, можно увидеть новую строку на выходе - на самом деле это HTTP тело:

{"name":"alex", "hobby":"football"} 

Я попробовал два различных реализации запроса отправителя и оба они висели, когда я пытался читать HTTP тела. Это означает, что проблема именно в моем серверном сокете.
Что не так с этой реализацией из учебников?

ответ

0

Мне посчастливилось найти объяснение на форуме reddit.

Короче говоря, структура HTTP POST сообщения является следующая

start-line CRLF 
header-field-1 CRLF 
header-field-2 CRLF 
header-field-N CRLF 
CRLF 
message-body 

Как вы можете видеть, что нет символа новой строки после того, как тело сообщения. Из-за этого метод readLine() не работает, как мы и ожидали. Я думаю, что метод Apache IOUtils.toString() сталкивается с той же проблемой. Чтобы устранить эту проблему, следующее решение предлагается:

  1. найти заголовок Content-Length и прочитать его значение N
  2. чтения точные N байт после заголовка раздела находится над

Вот пример кода:

BufferedReader br = new BufferedReader(new InputStreamReader(is)); 
boolean headersFinished = false; 
int contentLength = -1; 

while (!headersFinished) { 
    String line = br.readLine(); 
    headersFinished = line.isEmpty(); 

    if (line.startsWith("Content-Length:")) { 
     String cl = line.substring("Content-Length:".length()).trim(); 
     contentLength = Integer.parseInt(cl); 
    } 
} 

// validate contentLength value 
char[] buf = new char[contentLength]; //<-- http body is here 
br.read(buf); 
+0

В конце 'start-line' есть' CRLF'. Не все методы запросов имеют «тело сообщения» (в первую очередь «GET» и «HEAD»). А для тех, кто это делает, серверы HTTP 1.1 должны поддерживать запросы «chunked», которые не используют заголовок «Content-Length», чтобы указать, где находится конец «тела сообщения». –

+0

Проблема заключается в * вашей * реализации от HTTP, которая не соответствует RFC 2616. Не имеет ничего общего с «реализацией сокета». – EJP

+0

@EJP, в чем смысл обращения к этому вопросу с помощью кода C? Когда я googled «http post hangs/http body/socket», я не нашел ответа. – AdamSkywalker

Смежные вопросы