POST сюрприз

Рубрика: Java | 2 February 2009, 14:09 | juriy

Сейчас, в мире web 2.0 самый модный buzzword это давно уже не ajax. Асинхронные запросы на страничках настолько прочно вошли в нашу жизнь, что воспринимаются как должное. Более модный нынче термин – Comet. Comet – технология общения клиента с сервером, похожая на Ajax, за тем исключением, что Comet держит HTTP подключение открытым. Что это означает? К примеру, сервер может в любой момент оповестить клиента о событии, не дожидаясь, пока клиент спросит сам.
По такому принципу, к примеру, работает Google Talk из браузера.
Это преамбула.

На выходных я решил реализовать такое подключение из Java-апплета к серверу. Я хотел, чтобы _оба_ канала (и канал для запросов и канал для ответов) были постоянно открыты. Таким образом задержка передачи сообщений была бы идентична задержке TCP/IP подключения.

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

Вот классический код POST запроса к серверу. Уверен, каждый хоть раз да писал такой:


URL url = new URL("http://somehost.com:8080/do");
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStream out = conn.getOutputStream();
out.write(data);
out.flush();

InputStream is = conn.getInputStream();
processOutput(is)
out.close();
is.close();

Поскольку я держал подключение открытым и ответы сервера меня не интересовали (они приходили в другом потоке), код приобрел такой вид:


URL url = new URL("http://myserver:8080/do");
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStream out = conn.getOutputStream();

while (работаем) {
	data = // получить данные из очереди, или спать, если очередь пуста
	out.write(data);	

	// Вот это нельзя забыть, а то вдруг данные закешируются :-)
	out.flush();
}

Каково же было мое удивление, когда я понял, что такой код вообще ничего не отправляет! Оказалось, что URLConnection кеширует на клиенте _все_ данные до момента отправки. Самое грустное в этой истории то, что отключить этот “функционал” изысками вроде setUseCaches(false) не выходит.

Неприятное следствие, которое я подтвердил экспериментом. При попытке передать через POST файл, вы элементарно можете получить Error, если размер файла больше оставшегося места в heap. Ведь все содержимое файла будет вначале сохранено в памяти.

Способа полностью обойти эту коварную особенность, похоже, нет. Прелесть URLConnection’а заключается в том, что он позволяет неявно использовать настройки прокси пользователя. То есть, он идеален для апплетов: нет необходимости подписывать апплет, нет необходимости просить пользователя: “выбирите прокси, его тип, введите логин и пароль”. Большинство конечных пользователей просто закроют окно, появись такой диалог на экране.

Если прокси не является проблемой, можно использовать HTTPClient или написать “мини HTTP” используя “чистый” сокет, как показал Rex Young:


URI uri = new URI("http://localhost:8080/FullDuplexHttp/");
Socket socket = new Socket(uri.getHost(), uri.getPort());
OutputStream out = socket.getOutputStream();
out.write(("POST " + path + " HTTP/1.1\r\n").getBytes());
out.write("User-Agent: FullDuplexHttp\r\n".getBytes());
out.write(("Host: " + host + ":" + port + "\r\n").getBytes());
out.write("Transfer-Encoding: chunked\r\n".getBytes());
out.write("\r\n".getBytes());

// Теперь можно отправлять данные

Или же использовать пул открытых подключений к серверу и использовать одно подключение для отправки одного сообщения. IMHO этот вариант легко убьет сервер количеством бесполезных соединений.

Вот полезные ссылки по теме для интересующихся:
http://www.innovation.ch/java/HTTPClient/fullduplex.html – описание области проблемы и вариантов рещения – довольно интересное чтиво.
http://weblogs.java.net/blog/rexyoung/archive/2008/09/turn_a_http_con_1.html – блог Rex Young, который реализовал этот функционал, используя сокеты и chunked transfer encoding.

Комментариев: 12

12 Responses to “POST сюрприз”

Сюда ссылаются:

Комментарии:

  1. Andrew Dashin

    dwr такое уже давно умеет.

  2. juriy

    Умеет. DWR на стороне клиента это JavaScript. Я не знаю, как получить в JS постоянно открытый канал от клиента к серверу, но раз DWR умеет, то наверное можно. Жаль что, апплет ограничен настолько, что не может того, что может браузер.

  3. Stepan Koltsov

    Какую задачу ты пытаешься решить? Нет никакой проблемы на каждый фрагмент данных делать новый запрос на сервер.

    А comet — это в первую очередь поведение, при котором ответ от сервера приходит не сразу, а спустя минуту (например). Всё остальное (когда данные запроса или ответа посылаются по кускам) — нафиг не нужно.

  4. Juriy

    Пытаюсь минимизировать задержки при передаче. Некоторые прокси режут не-HTTP траффик, поэтому надо извращаться, чтобы приложением смогло воспользоваться максимальное количество пользователей. Приложение передает данные в реальном времени. Так как ты описал у меня сейчас работает. Задержка вполне ощутима. Не критична, но ощутима. А я хочу чтобы было как в TCP/IP при постоянном подключении :-)

  5. corsair

    COMET в первую очередь – средство чистящее универсальное, 400г, ассорти

  6. juriy

    Ну да, а Ajax это город и футбольный клуб. Про Java вообще молчу – и кофе и остров и мотоцикл :-))

  7. Max

    jetty continuations могуть быть использованы и в servlet 3.0 будет async servlet.

  8. Juriy

    На стороне сервера так и делаю: использую Jetty Continuations. Еще я слышал, что Tomcat умеет делать нечто подобное, используя собственный API.

  9. stfalcon

    Ajax это тоже моющее средство. Именно поэтому comet назвали comet’ом – каламбур такой :)

  10. Snowcore

    Здорово, нужно будет и мне на выходных попробовать!

  11. Swed

    вообще для JS – Commet “типа” реализован в Dojo
    поройтесь )))
    вообще задачи для использования Commet довольно специфичны.
    Чаты, Говорилки ….. и т.д.

    И используется по принципы Видео стриминга – Для трансляции одних данных в несколько потоков.

    вот тогда Commet действительно нужен, все остальные задачи проще и быстрее решить обычным Ajax`ом.

    ИМХО =))))

Leave a Reply