Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Развертка HTTP сервера в Java SE 
:(
    Опции темы
Platon
Дата 6.9.2008, 09:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1801
Регистрация: 25.4.2006

Репутация: 16
Всего: 40



Здравствтвуйте, уважаемые.

В очередной раз хочу принести пользу обществу и поделиться своей сегодняшней находкой: HTTP сервер на базе пакета от нашего Солнышка com.sun.net.httpserver

Введение.
Чем может не устраивать развертка веб-контейнеров, таких как Apache Tomcat? В первую очередь, они достаточно увесистые. Они расчитаны на обширный круг задач, навязывая многие дополнительные возможности, в общем созданы для полноценных веб-приложений.

Часто у нас возникает потребность в небольших HTTP серверах. К примеру, у нас имеется система, которая поддерживает 2 типа соединений: постоянные и HTTP запросы. Постоянные соединения - самая важная часть, лишь изредка необходимо запрашивать HTTP  страницы (статья о постоянных и HTTP соединениях для размышления). Это не веб приложение, и полнофункциональный веб-сервер нам ни к чему.

И как раз HttpServer - небольшой пакет классов для поднятия простого Embedded HTTP сервера. Самое приятное, что их можно найти в SDK6. JavaDoc и краткое руководство на английском языке есть на сайте Sun

Основная часть
Задача: вывести на страницу список заголовков, отправляемых браузером в кодировке UTF-8. Запрос к серверу http://localhost:8080/path/to/data.make

По традиции сразу выгружаю весь код

Код

package ru.vingrad.platon;

import com.sun.net.httpserver.*;
import java.net.InetSocketAddress;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;

public class TestHttpServer {
    private static final int THREADS_AMOUNT = 20;
    private static final int MAX_THREADS_AMOUNT = 2*THREADS_AMOUNT;
    private static final int QUEUE_CAPACITY = 1024;

    public static void main(String[] args) throws IOException {

        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);

        HttpContext c = server.createContext("/path/to/data.make", new HttpHandler() {
            public void handle(HttpExchange httpExchange) throws IOException {
                Writer w = new OutputStreamWriter(httpExchange.getResponseBody(), "UTF-8");
                httpExchange.getResponseHeaders().put("Content-Type", Arrays.asList("text/plain; charset=UTF-8"));
                httpExchange.sendResponseHeaders(200, 0);
                System.out.println(httpExchange.getRequestURI().getQuery());
                for (Map.Entry<String, List<String>> e : httpExchange.getRequestHeaders().entrySet()) {
                    w.write(e.getKey() + "\n");
                    for (String s : e.getValue())
                        w.write("    " + s + "\n");
                }
                w.write("Выход первой серии пятого сезона Доктор Хаус 16-го сентября");
                w.close();
            }
        });

        c.getFilters().add(new Filter() {
            public void doFilter(HttpExchange httpExchange, Chain chain) throws IOException {
                System.out.println("pre-filter");
                chain.doFilter(httpExchange);
                System.out.println("post-filter");
            }

            public String description() {
                return "short description";
            }
        });

        //server.setExecutor(null);
        server.setExecutor(new ThreadPoolExecutor(THREADS_AMOUNT, MAX_THREADS_AMOUNT, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(QUEUE_CAPACITY)));
        server.start();
    }
}


Вооружаемся секундомерами.

Код

HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);


Несложно догадаться что мы создаем HTTP сервер, который слушает порт 8080. 2-й параметр я не очень понял.
Цитата

backlog - the socket backlog. If this value is less than or equal to zero, then a system default value is used. 

Такое ощущение, что это локальный адрес соединения... Хорошо, что есть возможность дать на откуп программе установку этого параметра, поставив неположительное значение.

Привязываем строки запросов с обработчиками
Код

HttpContext c = server.createContext("/path/to/print-headers.do", new HttpHandler() {
            public void handle(HttpExchange httpExchange) throws IOException {
                //...
            }
        });

1-я строка связывает путь на сервере с обработчику. Иными словами, если будет запрошен заданный путь, то будет вызван привязанный к нему обработчик запроса HttpHandler. Интерфейс HttpHandler содержит всего 1 метод для определения, handle(HttpExchange httpExchange). Займемся этим. 
Класс HttpExchange содержит 6 важных методов, которые хорошо расписаны в документации:

Цитата

   1. getRequestMethod() to determine the command
   2. getRequestHeaders() to examine the request headers (if needed)
   3. getRequestBody() returns a InputStream for reading the request body. After reading the request body, the stream is close.
   4. getResponseHeaders() to set any response headers, except content-length
   5. sendResponseHeaders(int,long) to send the response headers. Must be called before next step.
   6. getResponseBody() to get a OutputStream to send the response body. When the response body has been written, the stream must be closed to terminate the exchange. 


Цитата

   1. getRequestMethod() для определения метода отправки
   2. getRequestHeaders() получить заголовки клиента (не перевод)
   3. getRequestBody() возвращает InputStream для чтения тела запроса. После чтения тела запроса, поток закрывается
   4. getResponseHeaders() получить и установить любые заголовки ответа, кроме content-length
   5. sendResponseHeaders(int,long) команда отправки заголовков. Должен вызываться перед следующим ходом. Первый аргумент статус страницы (200 - страница загружена, 404 - не найдена, 302 - перемещена), второй - длина тела ответа, если 0, то выставляется заголовок "Transfer-Encoding: chunked", т.е. тело неопределенной длины.
   6. getResponseBody() возвращает OutputStream для отправки тела ответа. Когда тело полностью отправлено клиенту, поток должен быть закрыт(!) чтобы разорвать обмен данными. 


Код

Writer w = new OutputStreamWriter(httpExchange.getResponseBody(), "UTF-8");
httpExchange.getResponseHeaders().put("Content-Type", Arrays.asList("text/plain; charset=UTF-8"));
httpExchange.sendResponseHeaders(200, 0);
System.out.println(httpExchange.getRequestURI().getQuery());
for (Map.Entry<String, List<String>> e : httpExchange.getRequestHeaders().entrySet()) {
    w.write(e.getKey() + "\n");
    for (String s : e.getValue())
        w.write("    " + s + "\n");
}
w.write("Выход первой серии пятого сезона Доктор Хаус 16-го сентября");
w.close();

По порядку
1. Оборачиваем OutputStream ответа в поток с кодировкой UTF-8.
2. Пробуем поэксперементировать с заголовками ответа (тип документа и его кодировка)
3. отправляем ответы клиенту 200 (страница найдена) и неизвестную длину посылки.
4. Отдельного разговора заслуживает метод httpExchange.getRequestURI() в качестве теста, можете набрать в браузере строку типа "/path/to/data.make?arg1=12&arg2=name"
Мы видим, что несмотря на то, что строка запроса уже не совпадает с /path/to/data.make тем не менее HttpServer может отделать часть запроса от передаваемых параметров и вызвать обработчик, привязанный к пути /path/to/data.make
5-9. отправляем полученные заголовки запроса обратно, но уже в теле ответа, а не в виде заголовков
10. Проверяем работает ли установленная нами кодировка, выводя русский текст.
11. Обязательно(!) закрываем поток ответа.

Работа с фильтрами
Код

c.getFilters().add(new Filter() {
    public void doFilter(HttpExchange httpExchange, Chain chain) throws IOException {
        System.out.println("pre-filter");
        chain.doFilter(httpExchange);
        System.out.println("post-filter");
    }

    public String description() {
        return "short description";
    }
});

Схема довольно проста. HttpContext, который мы создали на прошлом шаге, может быть предварительно и после обработан фильтрами.
Фильтры вызываются в порядке добовления их в цепь фильтров, получаемую методом c.getFilters().

Filter#description() - краткое описание фильтра.

Методы обработки одновременных запросов
Код

//server.setExecutor(null);
        server.setExecutor(new ThreadPoolExecutor(THREADS_AMOUNT, MAX_THREADS_AMOUNT, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(QUEUE_CAPACITY)));

Закоментированная строка имеет место быть для супер простых серверов с небольшой степенью одновременных запросов. Executor по умолчанию обрабатывает по одной задаче в момент времени. Такой подход конечно же неприемлем в боевых условиях. На сколько я понял, выполняется что-то подобное.

Код

class SimpleExecutor implements Executor {
   public void execute(Runnable command) {
      command.run();
   }
}


Второй строкой я привел боевой пример. Несколько потоков-работников будут параллельно обрабатывать запросы клиентов.

Вот жесткий тест на 1000 псевдоодновременных подключений:

Код

import java.io.IOException;
import java.net.*;
import java.util.*;

public class Go {

    private static int id;

    public static void main(String[] args) {
        Thread[] t = new Thread[1000];
        for (int i = 0; i < t.length; i++) {
            Thread thread = t[i] = new Thread(new Runnable() {

                public void run() {
                    int id;
                    synchronized(Go.class) {
                        id = Go.id;
                        Go.id++;
                    }
                    System.out.println("id = " + id);
                    for (int i = 0; i < 10; i++) {
                        try {
                            URLConnection conn = new URL("http://localhost:8080/path/to/data.make?id=" + id).openConnection();
                            for (Map.Entry<String, List<String>> e : conn.getHeaderFields().entrySet()) {
                                System.out.println(e.getKey());
                                for (String s : e.getValue())
                                    System.out.println("    " + s);
                            }
                            ((HttpURLConnection)conn).disconnect();
                        } catch (MalformedURLException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            thread.start();
        }
    }
}


Здесь могла бы быть ваша реклама
Программа определения позиций сайта SESpider

Это сообщение отредактировал(а) Platon - 21.9.2008, 11:57
PM MAIL ICQ   Вверх
COVD
Дата 6.9.2008, 15:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1655
Регистрация: 26.7.2005

Репутация: 17
Всего: 43



Я пока осилил только Введение ( остальное потом дочитаю ). Но драгоценные мысли уже на Введении пришли в голову smile . 

Embedded ... У нас, в начале, на сервере тоже все было в одном флаконе. Стоял Томкат, а под ним копошился собственно сервер. Сервер был "embedded" в Томкат, а не наоборот. Но не в этом суть. Нам Томкат был нужен в основном для http тунеллинга, т.е. для обслуживания на 80-го порту клиентов, защищенных файерволом. И тоже слегка смущало, что приходится возиться с Томкатом ради пары сервлетов.

Но со временем обнаружилось, что приложения на сервере имеют тенденцию расползаться. То, что бегало в одном флаконе, стало проситься бегать на разных машинах, да еще и размножаться. А развод - дело хлопотное. Поэтому  на сервере  решениям "embedded" и "в одном флаконе" возможно лучше сразу предпочесть отдельные приложения. А для коммуникации между ними можно использовать, например, RMI, или наладить обмен асинхронными сообщениями через  апачевский ActiveMQ 
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1086 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.