Во-первых, действительно трудно понять, что именно здесь происходит не так: «Иногда он делает хороший запрос, иногда нет». на самом деле не описывает, что происходит, когда возникает проблема !!
Тем не менее, я все еще был в состоянии понять, что происходит для вас.
Как вы уже сказали, вы ищете самое простое решение, которое будет работать согласованно, поэтому я буду избегать всего ненужного и не затрагивающего эффективность вашего кода. Кроме того, я сначала дам вам ответ, а затем опишу причину проблемы (это долго, но стоит прочитать:)
Решение
Простой ответ на вашу проблему заключается в том, что вам необходимо выполнить синтаксический анализ протокола HTTP, чтобы выяснить, все ли данные были отправлены клиентом, а не полагаться на то, что возвращает available()
или read()
. Сколько это PITA зависит от того, насколько полно вы хотите поддерживать протокол HTTP. Поддерживать запросы GET довольно просто. Немного сложнее поддерживать POST, которые определяют длину контента. Гораздо сложнее поддерживать «другие» типы кодирования (например, chunked или multipart / byteranges, см. http://tools.ietf.org/html/rfc2616#section-4.4).
В любом случае, я предполагаю, что вы просто пытаетесь заставить работать GET, поэтому для этого вы должны знать, что заголовки и тела HTTP разделены "пустой строкой", разделителем строк HTTP является \ r \ n и что ПОЛУЧАЕТ, не имеет тела. Поэтому клиент завершил отправку запроса GET, когда он передает \ r \ n \ r \ n.
Какой-то код, подобный этому, должен последовательно обрабатывать GET для вас (код не проверен, но он должен довести вас как минимум до 90%):
def readClientData(Socket clientSocket) {
def actualBuffer = new StringBuilder()
def eof = false;
def emptyLine = ['\r', '\n', '\r', '\n']
def lastEmptyLineChar = 0
InputStream inStream = clientSocket.inputStream
while(!eof) {
def available = inStream.available()
println "available data $available"
// try to read all available bytes
def buffer = new byte[available]
def bytesRead = inStream.read(buffer,0,available)
// check for empty line:
// * iterate through the buffer until the first element of emptyLine is found
// * continue iterating through buffer checking subsequent elements of buffer with emptyLine while consecutive elements match
// * if any element in buffer and emptyLine do not match, start looking for the first element of emptyLine again as the iteration through buffer continues
// * if the end of emptyLine is reached and matches with buffer, then the emptyLine has been found
for( int i=0; i < bytesRead && !eof; i++ ) {
if( buffer[i] == emptyLine[lastEmptyLineChar] ){
lastEmptyLineChar++
eof = lastEmptyLineChar >= emptyLine.length()
}
else {
lastEmptyLineChar = 0
}
}
// changed this so that you avoid any encoding issues
actualBuffer << new String(buffer, 0, bytesRead, Charset.forName("US-ASCII"))
}
return actualBuffer.toString()
}
Для POST вам нужно добавить к этому, также ища строку «Content-length:» и анализируя значение после этого. Это значение представляет собой размер тела HTTP (то есть бит, который следует после / r / n / r / n конца метки заголовка) в восьмеричных числах . Поэтому, когда вы сталкиваетесь с концом заголовка, вам просто нужно посчитать это число восьмеричных байтов, и вы знаете, что запрос POST завершил передачу.
Вам также необходимо определить тип запроса (GET, POST и т. Д.) - это можно сделать, проверив символы, переданные перед первым пробелом.
Проблема
Ваша проблема в том, что ваша функция readClientData
не всегда считывает все данные, отправленные клиентом. В результате вы иногда отправляете частичный запрос на сервер и возвращаете какую-то ошибку. При замене
должны быть напечатаны неполные запросы на стандартный вывод.
println(new String(buffer))
с
println(avaliable)
в функции readClientData
.
Почему это происходит? Это потому, что available () сообщает вам только то, что в данный момент доступно для чтения из InputStream, а не то, отправил ли клиент все данные, которые он собирается отправить. InputStream, по своей природе, никогда не может фактически сказать, будет ли больше данных (исключение составляет, если нет больше базовых данных для чтения - например, сокет закрыт, конец массива или файла имеет достигнуто и т. д. - это только время чтения () вернет -1 (т. е. EOF)). Вместо этого, код более высокого уровня должен решить, следует ли ему читать больше данных из потока, и он принимает это решение на основе правил для конкретного приложения, которые применяются к данным для конкретного приложения, читаемым InputStream.
В этом случае приложением является HTTP, поэтому вам нужно понять основы протокола HTTP, прежде чем вы начнете работать (cmeerw, вы были на правильном пути).
Когда клиент отправляет HTTP-запрос, клиент открывает сокет для сервера и отправляет запрос. Клиент только закрывает сокет в результате тайм-аута, или при отключении основного сетевого подключения, или в ответ на действие пользователя, которое требует, чтобы сокет был закрыт (приложение закрыто, страница обновлена, остановлен кнопка нажата и т.д.). В противном случае, после отправки запроса, он просто ждет, пока сервер отправит ответ. Как только сервер отправил ответ, сервер закрывает соединение [1].
Там, где ваш код успешно выполняется, данные предоставляются клиентом достаточно быстро и достаточно последовательно, так что InputStream получает дополнительные данные между вашим вызовом read()
и вашим последующим вызовом available()
на следующей итерации цикла (запомните что InputStream
предоставляется с данными "параллельно" к вашему коду, который вызывает его метод read()
). Теперь в другом случае, когда ваш код дает сбой, данные InputStream
еще не были предоставлены, поэтому, когда ваш код вызывает available()
, InputStream
правильно возвращает 0, так как больше никаких данных не было предоставлено, так как вы вызвали read()
и поэтому он имеет 0 байтов для вас read()
. Это условие гонки, о котором говорит Джонатан.
В вашем коде предполагается, что, когда available()
возвращает 0, все данные были отправлены клиентом, хотя на самом деле иногда это происходит, а иногда нет (поэтому иногда вы получаете «хороший запрос», а иногда нет). :.)
Поэтому вам нужно что-то лучше, чем available()
, чтобы определить, отправил ли клиент все данные.
Проверка EOF при вызове read()
(см. Ответ R4an [2]) также не подходит. Должно быть понятно, почему это так - единственный раз, когда read()
должен возвращать EOF (-1), это когда сокет закрыт. Это не должно происходить, пока вы не перенаправили запрос целевому прокси, не получили ответ и не отправили этот ответ клиенту, но мы знаем, что он также может быть исключен клиентом. На самом деле вы наблюдаете такое поведение при запуске примера кода - прокси зависает до тех пор, пока в браузере не будет нажата кнопка остановки, в результате чего клиент преждевременно закроет соединение.
Правильный ответ, который вы теперь знаете, - это выполнить синтаксический анализ HTTP и использовать его для определения состояния соединения.
Примечания
[1] Это не является доказательством концептуального прокси, но поскольку оно уже было затронуто, если HTTP-соединение «keep-alive», сервер будет держать соединение открытым и ждать другого запроса от клиента
[2] В этом коде есть ошибка, из-за которой readClientData искажает данные:
byte[] buffer = new byte[16 * 1024];
while((bytesRead = inStream.read(buffer)) >= 0) { // -1 on EOF
def bytesRead = inStream.read(buffer,0,bytesRead);
actualBuffer << new String(buffer)
}
Второй вызов inStream.read()
полностью перезаписывает данные, прочитанные первым вызовом inStream.read()
. Также здесь переопределяется bytesRead (недостаточно знакомый с Groovy, чтобы знать, будет ли это ошибкой). Эта строка должна либо читать:
bytesRead = bytesRead + inStream.read(buffer,bytesRead,buffer.length()-bytesRead);
или быть полностью удаленным.