Я экспериментирую с заголовком RFC Ожидать в java HttpURLConnection, который отлично работает, за исключением одной детали.
Существует 5-секундный период ожидания между отправкой тела Режим фиксированной длины или между каждым фрагментом в Режим потоковой передачи фрагментов.
Вот клиентский класс
public static void main(String[] args)throws Exception
{
HttpURLConnection con=(HttpURLConnection)new URL("http://192.168.1.2:2000/ActionG").openConnection();
//for 100-Continue logic
con.setRequestMethod("POST");
con.setRequestProperty("Expect", "100-Continue");
//responds to 100-continue logic
con.setDoOutput(true);
con.setChunkedStreamingMode(5);
con.getOutputStream().write("Hello".getBytes());
con.getOutputStream().flush();
con.getOutputStream().write("World".getBytes());
con.getOutputStream().flush();
con.getOutputStream().write("123".getBytes());
con.getOutputStream().flush();
//decode response and response body/error if any
System.out.println(con.getResponseCode()+"/"+con.getResponseMessage());
con.getHeaderFields().forEach((key,values)->
{
System.out.println(key+" = "+values);
System.out.println("=================== = ");
});
try(InputStream is=con.getInputStream()){System.out.println(new String(is.readAllBytes()));}
catch(Exception ex)
{
ex.printStackTrace(System.err);
InputStream err=con.getErrorStream();
if (err!=null)
{
try(err){System.err.println(new String(is.readAllBytes()));}
catch(Exception ex2){throw ex2;}
}
}
con.disconnect();
}
Я загружаю 3 чанка. На стороне сервера принимается 5 пакетов данных
Все заголовки. ответьте 100 Продолжить
3 куска. Для каждого фрагмента ответьте 100 Продолжить
Последний фрагмент [длина 0]. ответить 200 ОК
Вот тестовый сервер
final class TestServer
{
public static void main(String[] args)throws Exception
{
try(ServerSocket socket=new ServerSocket(2000,0,InetAddress.getLocalHost()))
{
int count=0;
try(Socket client=socket.accept())
{
int length;
byte[] buffer=new byte[5000];
InputStream is=client.getInputStream();
OutputStream os=client.getOutputStream();
while((length=is.read(buffer))!=-1)
{
System.out.println(++count);
System.out.println(new String(buffer,0,length));
System.out.println("========= = ");
if (count<5)
{
os.write("HTTP/1.1 100 Continue\r\n\r\n".getBytes());
os.flush();
}
else
{
os.write("HTTP/1.1 200 Done\r\nContent-Length:0\r\n\r\n".getBytes());
os.flush();
break;
}
}
}
}
}
}
Выход:
1
POST /ActionG HTTP/1.1
Expect: 100-Continue
User-Agent: Java/17.0.2
Host: 192.168.1.2:2000
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
========== //5 seconds later
2
5
Hello
========== //5 seconds later
3
5
World
========== //5 seconds later
//this and the last chunk come seperatly but with no delay
4
3
123
==========
5
0
==========
Я проверил каждый метод тайм-аута в моем объекте подключения.
System.out.println(con.getConnectTimeout());
System.out.println(con.getReadTimeout());
Оба возвращают 0
Так откуда же взялась эта 5-секундная задержка?
Я использую JDK 17.0.2 с Windows 10
3 Chunks. For each chunk respond with 100 Continue
Expect: 100-Continue
работает не так. Код вашего сервера совершенно не соответствует тому, что вы пытаетесь сделать. На самом деле ваш серверный код совершенно не подходит для HTTP-сервера в целом. Он даже не пытается анализировать HTTP-протокол. Ни заголовки HTTP, ни фрагменты HTTP, ничего. Есть ли причина, по которой вы не используете реальную реализацию HTTP-сервера, такую как собственный HttpServer
Java?
При использовании Expect: 100-Continue
клиент должен отправить ТОЛЬКО заголовки запроса, а затем ОСТАНОВИТЬСЯ И ЖДАТЬ несколько секунд, чтобы увидеть, отправляет ли сервер ответ 100
или нет:
Если сервер отвечает 100
, клиент может завершить запрос, отправив тело запроса, а затем получить окончательный ответ.
Если сервер отвечает чем-либо, кроме 100
, клиент может немедленно завершить свою работу, не отправляя тело запроса вообще.
Если ответ не получен, клиент может завершить запрос, отправив тело запроса и получив окончательный ответ.
Весь смысл Expect: 100-Continue
заключается в том, чтобы клиент запрашивал разрешение перед отправкой большого тела запроса. Если серверу не нужно тело (например, заголовки описывают неудовлетворительные условия и т. д.), клиенту не нужно тратить усилия и пропускную способность на отправку тела запроса, который будет просто отклонен.
HttpURLConnection
имеет встроенную поддержку для обработки ответов 100
, но оговорки см. в Как дождаться ответа Expect 100-continue в Java с помощью HttpURLConnection. Также см. JDK-8012625: Неправильная обработка HTTP/1.1 "Ожидание: 100-продолжить" в HttpURLConnection.
Но ваш серверный код, как показано, нуждается в серьезной переработке для обработки HTTP должным образом, не говоря уже об обработке запросов должным образом, разбитых на фрагменты.
Код клиента выглядит нормально, по большей части. Проблема на стороне сервера. Как я уже сказал, ваш серверный код совершенно не подходит для HTTP и должен быть переписан, чтобы фактически анализировать протокол HTTP, вы не можете избежать этого. Пока вы не исправите это, ваша проблема с 5-секундной задержкой неактуальна, поскольку у вас неисправная тестовая среда.
Так что спасибо @Remy Lebeau за понимание того, как правильно анализировать этот специальный заголовок. Я заметил, что после создания базового синтаксического анализатора и после правильного ответа на заголовки chunked[Transfer-Encoding:chunked header] и потоковой передачи фиксированной длины[Content-Length header], что иногда мой клиент все еще зависал и время от времени ждал 5 секунд, а иногда и работать без проблем.
После нескольких часов отладки класса com.sun.www.http.HttpURLСоединение я понял, что еще один недостаток был больше не на стороне сервера, а на стороне клиента кода. Особенно этот бит
con.getOutputStream().write("Hello".getBytes());
con.getOutputStream().flush();
con.getOutputStream().write("World".getBytes());
con.getOutputStream().flush();
con.getOutputStream().write("123".getBytes());
con.getOutputStream().flush();
Я ошибочно предположил, что получитьвыходнойпоток() в этом классе будет кэшировать возвращаемый поток вывода, но на самом деле он каждый раз возвращает новый поток вывода.
Поэтому мне пришлось изменить код на этот
OutputStream os=client.getOutputStream();
os.write("Hello".getBytes());
os.flush();
os.write("World".getBytes());
os.flush();
os.write("123".getBytes());
os.flush();
Это, наконец, решило все мои проблемы. Работает для потоковой передачи фрагментами и фиксированной длины
Как Я сказал в Моем вопросе. Я экспериментирую с этим заголовком. Я никогда не собирался ничего анализировать или использовать какой-либо существующий сервер, поскольку я хотел проанализировать, как выглядят пакеты запроса и ответа, и поэтому я создал MRE, чтобы продемонстрировать проблему. В отчете об ошибке было сказано, что проблема решена, но я не смог найти код, как ее решить. Если бы вы могли опубликовать этот код здесь для отладки, я отмечу это как принятый ответ.