В процессе автоматизации деятельности предприятия при помощи 1С: Предприятие часто
возникают задачи интеграции и обмена с оборудованием и другими сторонними информационными
системами, например, банками, веб-сайтами, информационными системами партнеров.
Традиционно 1С: Предприятие выступает в качестве потребителя услуг, и реже – в качестве
поставщика. До этого момента у разработчиков популярными технологиями при получении
информации от 1C были COM и веб-сервисы, которые появились только в версии 8.1.
У обеих технологий (COM и веб-сервисы) есть свои минусы. Главные недостатки заключаются
в следующем. COM-технология позволяет в каждый момент времени выполнять только один
запрос. Обращение возможно только внутри своей локальной сети. Обработка одновременных
запросов для COM возможна, но требует затрат на организацию пула соединений и отладку
многопоточного приложения. Веб-сервисы же сложны по настройке и негибкие для программирования:
жестко привязаны к SOAP-стандартам. Подключение устройств, обменивающихся простыми
пакетами, работающих по http-протоколу, невозможно.
Новая идея организации веб-сервера внутри 1С, предложенная в статье, опирается на
проверенную временем богатую функциональность .Net Framework. Решение на основе
идеи лишены недостатков COM и веб-сервисов 1С. По сравнению с COM http-сервер можно
использовать вне локальной сети, поддерживается одновременная обработка нескольких
запросов. По сравнению с веб-сервисами 1С решение на базе http-сервера обладает
большей гибкостью, так как программист волен сам выбирать формат ответа сервера
(в том числе HTML, JSON, графические изображения, RSS и т.д.), а также контролировать
при ответе URL-адрес, идентификацию пользователей, коды ошибок, куки, кодировку,
осуществлять кеширование. Настройка же http-сервера внутри 1С сводится к простому
запуску внешней обработки.
Описание примера
Пример, приложенный к статье, состоит из двух файлов: http-сервера (внешняя обработка
1С 8.2 HttpServer82) и тестового приложения эмуляции одновременных запросов к серверу
(внешняя обработка 1С 8.2 TestHttpServer82). Обе обработки выполнены на основе управляемых
форм. По умолчанию оба приложения настроены на работу с портом 8082.
Сервер и тестовое приложение разработаны на 1С: Предприятие 8.2 и используют .Net
Framework 4.0 и компонент Elisy .Net Bridge 4. Соответственно, для работы примера
требуется установленный .Net Framework 4.0 и версия библиотеки Elisy .Net Bridge
v.4.0.2.0 и выше. Elisy .Net Bridge позволяет гармонично использовать классы и технологии
.Net Framework на 1С, ведущую роль оставляя 1С.
Для проверки работы достаточно запустить внешнюю обработку HttpServer82.epf из 1С: Предприятие
8.2. Если у вас Windows с включенным UAC, то запустить 1С: Предприятие необходимо
под администратором, иначе у приложения не будет достаточно прав на прослушивание
запросов.
Внешняя обработка позволяет задать порт, по которому будет осуществляться прослушивание
и число создаваемых потоков для обработки одновременных запросов. По умолчанию установлен
порт 8082 и 50 потоков.
После нажатия на кнопку Старт сервер переходит в рабочее состояние и осуществляет
обработку запросов на заданный порт. Например, теперь можно обратиться из вашего
браузера по адресу localhost:8082 и открыть
страницу, которую вернет сервер. В запросе же можно передавать параметры, например,
так: localhost:8082/test?x=1
Для проверки сервера в многопоточном режиме придумана внешняя обработка TestHttpServer82.epf,
которая осуществляет одновременный запуск запросов в цикле. В основу обработки для
организации параллельной работы положена замечательная технология PLINQ (Parallel
LINQ) из .Net framework 4.
Запускать тестовую обработку TestHttpServer82.epf следует из отдельного от сервера
сеанса 1С: Предприятие, иначе одновременный запуск в одном сеансе двух обработок
приведет к зависанию. В качестве параметров тестового примера выступают адрес запроса,
число одновременных запросов и число циклов. По умолчанию запуск алгоритма приведет
к 3 циклам обращений по адресу localhost:8082
с 20 одновременными запросами (надо отметить, что число одновременных запросов ограничено
числом ядер процессора).
Принцип работы
.Net framework предлагает своим разработчикам класс HttpListener, отвечающий за прослушивание http-протокола. Используя HttpListener,
вы можете создать прослушивание http-трафика, которое отвечает на http-запросы.
Вы можете использовать этот класс только на операционных системах Windows XP SP2
или Windows Server 2003 и выше. Попытка использования класса на более ранних системах
вызовет исключение.
Ниже приведен пример кода для 1С, который инициализирует объект типа HttpListener,
настраивая его на прослушивание всех URL по порту 8082. При запуске в 1С работа
программы приостанавливается, пока не последует запрос на порт, например, из браузера.
Как только вы пошлете из браузера запрос, например, 127.0.0.1:8082/ 1С вернет описанную в программе html-строку.
За что ответственен каждый участок кода, можно понять из комментариев, приведенных
внутри исходного кода.
ПодключитьВнешнююКомпоненту("Elisy.NetBridge4");
AddIn = New("AddIn.ElisyNetBridge4");
net = AddIn.GetNet();
Если НЕ net.GetStatic("System.Net.HttpListener","IsSupported") Тогда
Сообщить("Для класса HttpListener нужна ОС Windows XP SP2/2003 и выше");
Возврат;
КонецЕсли;
listener = net.New("System.Net.HttpListener");
listener.Prefixes.Add("http://*:8082/");
listener.Start();
Сообщить("Прослушивание...");
//Метод GetContext блокирует выполнение программы пока ждет запрос.
context = listener.GetContext();
request = context.Request;
// Получить объект ответа
response = context.Response;
// Создать ответ - HTML-строку
responseString = "Ответ от HttpListener";
buffer = net.GetStatic("System.Text.Encoding", "UTF8").GetBytes(responseString);
// Получить поток ответа и записать ответ в него.
response.ContentLength64 = buffer.Length;
output = response.OutputStream;
output.Write(buffer,0,buffer.Length);
// Необходимо закрыть выходной поток и остановить прослушивание
output.Close();
listener.Stop();
Это самый простой пример. Главный смысл простейшего примера в том, что 1С превращается
в полнофункциональный http-сервер, а 1С-программист получает инструмент для гибкой
настройки ответа с сервера. Он может вернуть его в любом формате (html, рисунок
или JSON), затребовать идентификацию пользователя или вообще вернуть ошибку. Но
вместе с тем есть недостатки: блокирование всего приложения, обработка только одного
запроса. Пример к статье содержит более сложный код, за счет чего устранены недостатки
простейшего примера из листинга.
От простого к сложному
В готовый пример вошел видоизмененный код работы с HttpListener, в котором вызов
метода listener.GetContext() заменен вызовом асинхронных аналогов listener.BeginGetContext()
и listener.EndGetContext(). Кроме этого создаются N отдельных потоков и заложена
синхронизация между потоками с вызовом кода-обработки запроса на стороне 1С: Предприятие.
Достоинством предлагаемой реализации http-сервера является возможность одновременной
обработки N запросов в разных потоках с передачей логики обработки в метод формы
1С: Предприятие. Пример при каждом запросе возвращает html с описанием источника
и URL запроса. Решение очень гибкое, так как все доработки можно делать прямо из
конфигуратора 1С. Никакие дополнительные проекты (например, C# или VB.Net) не задействованы.
Пример условно можно разделить на 2 части: код, который выполняется на стороне 1С: Предприятие
и код, который выполняется на стороне .Net framework. При этом .Net framework взял
на себя все, что нельзя реализовать средствами 1С: Предприятие, например, создание
и синхронизацию потоков.
Http-сервер оформлен в виде управляемой формы. При нажатии на кнопку Старт происходит
создание объектов классов HttpServer и Helper. Оба класса описываются на C# в макете
обработки ИсходныйКод и компилируются «на лету» в обработчике ПриОткрытии формы.
Класс Helper отвечает за перенаправление .Net-события в функцию ОбработатьЗапрос
на форме 1С и формирование сообщение об ошибке.
Опустив не относящиеся к теме задачи по инициализации формы и вспомогательных классов,
следует остановиться на методе ОбработатьЗапрос, который отвечает за возврат результата
http-клиенту. Вызывается обработчик из класса Helper, в качестве параметра принимает
объект контекста, может возвратить сообщение об ошибке, которое будет возвращено
клиенту.
Объект контекста типа HttpListenerContext, передаваемый в метод ОбработатьЗапрос
содержит два важных свойства: Request и Response, отвечающие за информацию о запросе
и ответе соответственно.

Свойство Request позволяет получить параметры и содержимое, переданные клиентом
при запросе. Информация о запросе, помещенная в свойство Request содержит такие
основные свойства как:
AcceptTypes – MIME-типы, поддерживаемые клиентом
ContentEncoding – информация о кодировке при ответе
Headers – набор заголовков
HttpMethod – метод HTTP, определенный клиентом
InputStream – поток, содержащий данные тела, пришедшие от клиента
IsAuthenticated – булево значение, показывающее идентифицирован ли пользователь
IsLocal – значение, показывающее локальный ли запрос (через localhost)
QueryString – строка запроса из запроса
RawUrl – информация об URL без хоста и порта
UrlReferrer – URL ресурса-источник данного перехода
UserAgent – информация о агенте-браузере пользователя
Через свойство Response осуществляется возврат контента клиенту, передача информации
об ошибке или перенаправление. Сопровождаться все может выставлением необходимых
заголовков. Многие свойства ответа (Response) схожи со свойствами запроса. Значимые
среди них следующие:
ContentEncoding – информация о кодировке при ответе
Headers – набор заголовков
OutputStream – поток, в который будет записан ответ (например, текст Html, XML или
байтовый массив изображения)
RedirectLocation – свойство отвечает за HTTP-заголовок Location и позволяет перенаправить
вызов
StatusCode – код статсуса при возврате клиенту, например: 200 (OK), 404 (ресурс не
найден)
StatusDescription – описание статуса при возврате клиенту

Следующий код метода ОбработатьЗапрос преобразует сформированную строку РезультатHTML
с HTML-кодом в набор байт и записывает этот набор байт в выходной поток, который
будет возвращен клиенту. Ссылка на выходной поток получена через параметр метода.
ответСервера = context.Response;
массивБайт = net.GetStatic("System.Text.Encoding", "UTF8").GetBytes(РезультатHTML);
ответСервера.ContentLength64 = массивБайт.Length;
выходнойПоток = ответСервера.OutputStream;
выходнойПоток.Write(массивБайт, 0, массивБайт.Length);
выходнойПоток.Close();
Основой обработки является класс HttpServer, который создает объект HttpListener
и нужное число потоков для обработки. При вызове метода Start происходит запуск
всех потоков-обработчиков и отдельного потока для работы HttpListener. Благодаря
этому, можно продолжать работать с 1С во время работы http-сервера. При поступлении
запроса HttpListener помещает запрос в очередь, где каждый запрос последовательно
обрабатывает первый свободный поток. При обработке потока срабатывает цепочка вызовов:
событие HttpServer.ProcessRequest, обработчик события Helper. HttpServer_ProcessRequest,
1С-функция Форма. ОбработатьЗапрос. Код C# класса HttpServer при запуске 1C из макета
ИсходныйКод компилируется «на лету».
_listener.AuthenticationSchemes = authenticationScheme;
_listener.Prefixes.Add(String.Format(@"http://+:{0}/", port));
_listener.Start();
_listenerThread.Start();
for (int i = 0; i < _workers.Length; i++)
{
_workers[i] = new Thread(Worker);
_workers[i].Start();
}
Начало получения запросов происходит в методе HandleRequests до тех пор, пока обработка
не прекратится пользователем. При поступлении запроса, запрос передается в метод
ContextReady и работа процесса прослушивания продолжается.
private void HandleRequests()
{
while (_listener.IsListening)
{
var context = _listener.BeginGetContext(ContextReady, null);
if (0 == WaitHandle.WaitAny(new[] { _stop, context.AsyncWaitHandle }))
return;
}
}
private void Worker()
{
WaitHandle[] wait = new[] { _ready, _stop };
while (0 == WaitHandle.WaitAny(wait))
{
HttpListenerContext context;
lock (_queue)
{
if (_queue.Count > 0)
context = _queue.Dequeue();
else
{
_ready.Reset();
continue;
}
}
try { ProcessRequest(context); }
catch (Exception e) { Console.Error.WriteLine(e); }
}
}
Заключение
Многопоточный сервер, описанный в статье, по простоте настройки и функциональности
превосходит традиционные методы доступа извне к информационным базам 1С: Предприятие
8.х. Это простой по запуску метод, позволяющий одновременно обрабатывать несколько
запросов. При этом приложение 1С не блокируется, и пользователь может продолжать
работу после запуска сервера.
Основное преимущество предложенного подхода – полная подконтрольность программисту
процесса от получения запроса до формирования ответа. Например, на этапе получения
запроса может быть выполнен парсинг URL, получена информация о том, как себя идентифицировал
пользователь, а также полная информация о клиенте (поддерживаемые языки, записанные
куки, заголовки, метод доступа). Ответ же можно вернуть практически любой, начиная
от ошибки 404 Not found, заканчивая разными графическими форматами, форматами Word,
Excel и популярными форматами на основе XML (JSON, HTML, RSS).
Пример, приложенный к статье, спроектирован так, что его функциональность можно
легко расширить. Например, для организации кеша применить System.Web.Caching.Cache
класс из .Net framework. А при парсинге URL попробовать поработать с классом RouteCollection
из Asp.Net MVC. При создании RSS-ленты вам поможет класс System.ServiceModel.Syndication
.SyndicationFeed. А при Json-сериализации обратите внимание на класс System.Runtime.Serialization.Json.DataContractJsonSerializer.
Конкретно для предложенного подхода на данный момент не выявлено явных недостатков.
Есть вероятность того, что 1С: Предприятие в силу своих ограничений не сможет обеспечить
на своей стороне должного распараллеливания и значительного увеличения производительности.
Тем не менее опыты проведенные ранее для 1С: Предприятие 8.2 показали, что в схожем
применении 1С достигается увеличение производительности, и 1С работает при этом
стабильно.
Необходимо обратить внимание разработчиков еще на несколько моментов. Любая публикация
информации в Интернете связана с риском взлома вне зависимости от способа публикации.
Но в предложенном способе благодаря гибкости есть больше возможностей противостоять
угрозам извне. Например, в запросе теперь доступен IP-адрес клиента, который можно
блокировать по каким-то правилам (осуществляя поиск в черном списке локально или
в специализированных сервисах). Параметры запроса доступны в виде строк и их можно
анализировать на стороне 1С или .Net framework и блокировать опасное содержимое.
Кроме этого есть несколько специализированных .Net-библиотек, доступных для решения
данной проблемы, которые можно привлечь в 1С, например AntiXSS.
Второй момент заключается в том, что известные методы, в том числе и этот, предоставления
услуг со стороны 1С не предназначены для массовых обращений и будут всегда уступать
с этой точки зрения профессиональным серверам, например, IIS.
Примеры к статье:
HttpServer82.epf (11.50 kb)
TestHttpServer82.epf (8.30 kb)
Вопросы для собеседования
Servlets, JSP, JSTL
- Что такое «сервлет»?
- В чем заключаются преимущества технологии сервлетов над CGI (Common Gateway Interface)?
- Какова структура веб-проекта?
- Что такое «контейнер сервлетов»?
- Зачем нужны сервера приложений, если есть контейнеры сервлетов?
- Как контейнер сервлетов управляет жизненным циклом сервлета, когда и какие методы вызываются?
- Что такое «дескриптор развертывания»?
- Какие действия необходимо проделать при создании сервлетов?
- В каком случае требуется переопределять метод
service()? - Есть ли смысл определять для сервлета конструктор? Каким образом лучше инициализировать данные?
- Почему необходимо переопределить только
init()метод без аргументов? - Какие наиболее распространенные задачи выполняются в контейнере сервлетов?
- Что вы знаете о сервлетных фильтрах?
- Зачем в сервлетах используются различные listener?
- Когда стоит использовать фильтры сервлетов, а когда слушателей?
- Как реализовать запуск сервлета одновременно с запуском приложения?
- Как обработать в приложении исключения, выброшенные другим сервлетом?
- Что представляет собой
ServletConfig? - Что представляет собой
ServletContext? - В чем отличия
ServletContextиServletConfig? - Для чего нужен интерфейс
ServletResponse? - Для чего нужен интерфейс
ServletRequest? - Что такое
Request Dispatcher? - Как из одного сервлета вызвать другой сервлет?
- Чем отличается
sendRedirect()отforward()? - Для чего используются атрибуты сервлетов и как происходит работа с ними?
- Каким образом можно допустить в сервлете deadlock?
- Как получить реальное расположение сервлета на сервере?
- Как получить информацию о сервере из сервлета?
- Как получить IP адрес клиента на сервере?
- Какие классы-обертки для сервлетов вы знаете?
- В чем отличия
GenericServletиHttpServlet? - Почему
HttpServletкласс объявлен как абстрактный? - Какие основные методы присутствуют в классе
HttpServlet? - Стоит ли волноваться о многопоточной безопасности работая с сервлетами?
- Какой метод HTTP не является неизменяемым?
- Какие есть методы отправки данных с клиента на сервер?
- В чем разница между методами
GETиPOST? - В чем разница между
PrintWriterиServletOutputStream? - Можно ли одновременно использовать в сервлете
PrintWriterиServletOutputStream? - Расскажите об интерфейсе
SingleThreadModel. - Что означает URL encoding? Как это осуществить в Java?
- Какие различные методы управления сессией в сервлетах вы знаете?
- Что такое cookies?
- Какие методы для работы с cookies предусмотрены в сервлетах?
- Что такое URL Rewriting?
- Зачем нужны и чем отличаются методы
encodeURL()иencodeRedirectURL()? - Что такое «сессия»?
- Как уведомить объект в сессии, что сессия недействительна или закончилась?
- Какой существует эффективный способ удостоверится, что все сервлеты доступны только для пользователя с верной сессией?
- Как мы можем обеспечить transport layer security для нашего веб приложения?
- Как организовать подключение к базе данных, обеспечить журналирование в сервлете?
- Какие основные особенности появились в спецификации Servlet 3?
- Какие способы аутентификации доступны сервлету?
- Что такое Java Server Pages (JSP)?
- Зачем нужен JSP?
- Опишите, как обрабатываются JSP страницы, начиная от запроса к серверу, заканчивая ответом пользователю.
- Расскажите об этапах (фазах) жизненного цикла JSP.
- Расскажите о методах жизненного цикла JSP.
- Какие методы жизненного цикла JSP могут быть переопределены?
- Как можно предотвратить прямой доступ к JSP странице из браузера?
- Какая разница между динамическим и статическим содержимым JSP?
- Как закомментировать код в JSP?
- Какие существуют основные типы тегов JSP?
- Что вы знаете о действиях JSP (Action tag и JSP Action Elements).
- Взаимодействие JSP — сервлет — JSP.
- Какие области видимости переменных существуют в JSP?
- Какие неявные, внутренние объекты и методы есть на JSP странице?
- Какие неявные объекты не доступны в обычной JSP странице?
- Что вы знаете о
PageContextи какие преимущества его использования? - Как сконфигурировать параметры инициализации для JSP?
- Почему не рекомендуется использовать скриплеты (скриптовые элементы) в JSP?
- Можно ли определить класс внутри JSP страницы?
- Что вы знаете о Языке выражений JSP (JSP Expression Language – EL)?
- Какие типы EL операторов вы знаете?
- Назовите неявные, внутренние объекты JSP EL и их отличия от объектов JSP.
- Как отключить возможность использования EL в JSP?
- Как узнать тип HTTP метода используя JSP EL?
- Что такое JSTL (JSP Standard tag library)?
- Из каких групп тегов состоит библиотека JSTL?
- Какая разница между
<c:set>и<jsp:useBean>? - Чем отличается
<c:import>от<jsp:include>и директивы<%@include %>? - Как можно расширить функциональность JSP?
- Что вы знаете о написании пользовательских JSP тегов?
- Приведите пример использования собственных тегов.
- Как сделать перенос строки в HTML средствами JSP?
- Почему не нужно конфигурировать стандартные JSP теги в
web.xml? - Как можно обработать ошибки JSP страниц?
- Как происходит обработка ошибок с помощью JSTL?
- Как конфигурируется JSP в дескрипторе развертывания.
- Можно ли использовать Javascript на JSP странице?
- Всегда ли создается объект сессии на JSP странице, можно ли отключить его создание?
- Какая разница между
JSPWriterи сервлетнымPrintWriter? - Опишите общие практические принципы работы с JSP.
Что такое «сервлет»?
Сервлет является интерфейсом, реализация которого расширяет функциональные возможности сервера. Сервлет взаимодействует с клиентами посредством принципа запрос-ответ. Хотя сервлеты могут обслуживать любые запросы, они обычно используются для расширения веб-серверов.
Большинство необходимых для создания сервлетов классов и интерфейсов содержатся в пакетах javax.servlet и javax.servlet.http.
Основные методы сервлета:
public void init(ServletConfig config) throws ServletExceptionзапускается сразу после загрузки сервлета в память;public ServletConfig getServletConfig()возвращает ссылку на объект, который предоставляет доступ к информации о конфигурации сервлета;public String getServletInfo()возвращает строку, содержащую информацию о сервлете, например: автор и версия сервлета;public void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOExceptionвызывается для обработки каждого запроса;public void destroy()выполняется перед выгрузкой сервлета из памяти.
Текущая спецификация — Servlet 3.1 описана в JSR-340 и принята в 2013 году.
к оглавлению
В чем заключаются преимущества технологии сервлетов над CGI (Common Gateway Interface)?
- Сервлеты предоставляют лучшую производительность обработки запросов и более эффективное использование памяти за счет использования преимущество многопоточности (на каждый запрос создается новая нить, что быстрее выделения памяти под новый объект для каждого запроса, как это происходит в CGI).
- Сервлеты, как платформа и система являются независимыми. Таким образом веб-приложение, написанное с использованием сервлетов может быть запущена в любом контейнере сервлетов, реализующим этот стандарт и в любой операционной системе.
- Использование сервлетов повышает надежность программы, т.к. контейнер сервлетов самостоятельно заботится о жизненном цикле сервлетов (а значит и за утечками памяти), безопасности и сборщике мусора.
- Сервлеты относительно легки в изучении и поддержке, таким образом разработчику необходимо заботиться только о бизнес-логике приложения, а не внутренней реализации веб-технологий.
к оглавлению
Какова структура веб-проекта?
src/main/java Исходники приложения/библиотеки
src/main/resources Ресурсные файлы приложения/библиотеки
src/main/filters Файлы сервлетных фильтров
src/main/webapp Исходники веб-приложения
src/test/java Исходники тестов
src/test/resources Ресурсные файлы тестов
src/test/filters Тесты сервлетных фильтров
src/it Интеграционные тесты
src/assembly Описание сборки
src/site Сайт
LICENSE.txt Лицензия проекта
NOTICE.txt Замечания и определения библиотек зависимостей.
README.txt Описание проекта
к оглавлению
Что такое «контейнер сервлетов»?
Контейнер сервлетов — программа, представляющая собой сервер, который занимается системной поддержкой сервлетов и обеспечивает их жизненный цикл в соответствии с правилами, определёнными в спецификациях. Может работать как полноценный самостоятельный веб-сервер, быть поставщиком страниц для другого веб-сервера, или интегрироваться в Java EE сервер приложений.
Контейнер сервлетов обеспечивает обмен данными между сервлетом и клиентами, берёт на себя выполнение таких функций, как создание программной среды для функционирующего сервлета, идентификацию и авторизацию клиентов, организацию сессии для каждого из них.
Наиболее известные реализации контейнеров сервлетов:
- Apache Tomcat
- Jetty
- JBoss
- WildFly
- GlassFish
- IBM WebSphere
- Oracle Weblogic
к оглавлению
Зачем нужны сервера приложений, если есть контейнеры сервлетов?
- Пулы соединений с БД
- Возможность периодического тестирования доступности СУБД и обновления соединения в случае восстановления после сбоев
- Замена прав доступа при подключении
- Балансировка нагрузки между несколькими СУБД, определение доступность или недоступность того или иного узла
- Защита пула соединений от некорректного кода в приложении, которое по недосмотру не возвращает соединения, просто отбирая его после какого-то таймаута.
- JMS
- Доступность сервера очередей сообщений «из-коробки».
- Возможность кластеризации очередей, т.е. доступность построения распределенных очередей, расположенных сразу на нескольких серверах, что существенно увеличивает масштабируемость и доступность приложения
- Возможность миграции очередей — в случае падения одного из серверов, его очереди автоматически перемещаются на другой, сохраняя необработанные сообщения.
- В некоторых серверах приложений поддерживается Unit-of-Order — гарантированный порядок обработки сообщений, удовлетворяющих некоторым критериям.
- JTA Встроенная поддержка распределенных транзакций для обеспечения согласованности данных в разные СУБД или очереди.
- Безопасность
- Наличие множества провайдеров безопасности и аутентификации:
- во встроенном или внешнем LDAP-сервере
- в базе данных
- в различных Internet-directory (специализированных приложениях для управления правами доступа)
- Доступность Single-Sign-On (возможности разделения пользовательской сессии между приложениями) посредством Security Assertion Markup Language (SAML) 1/2 или Simple and Protected Negotiate (SPNEGO) и Kerberos: один из серверов выступает в роли базы для хранения пользователей, все другие сервера при аутентификации пользователя обращаются к этой базе.
- Возможность авторизации посредством протокола eXtensible Access Control Markup Language (XACML), позволяющего описывать довольно сложные политики (например, приложение доступно пользователю только в рабочее время).
- Кластеризация всего вышеперечисленного
- Наличие множества провайдеров безопасности и аутентификации:
- Масштабируемость и высокая доступность Для контейнера сервлетов обычно так же возможно настроить кластеризацию, но она будет довольно примитивной, так как в случае его использования имеются следующие ограничения:
- Сложность передачи пользовательской сессии из одного центра обработки данных (ЦоД) в другой через Интернет
- Отсутствие возможности эффективно настроить репликации сессий на большом (состоящем из 40-50 экземпляров серверов) кластере
- Невозможность обеспечения миграции экземпляров приложения на другой сервер
- Недоступность механизмов автоматического мониторинга и реакции на ошибки
- Управляемость
- Присутствие единого центра управления, т.н. AdminServer и аналога NodeManager’а, обеспечивающего
- Возможность одновременного запуска нескольких экземпляров сервера
- Просмотр состояния запущенных экземпляров сервера, обработчиков той или иной очереди, на том или ином сервере, количества соединений с той или иной БД
- Присутствие единого центра управления, т.н. AdminServer и аналога NodeManager’а, обеспечивающего
- Административный канал и развертывание в промышленном режиме Некоторые сервера приложений позволяют включить так называемый «административный канал» — отдельный порт, запросы по которому имеют приоритет.
- Просмотр состояния (выполняющихся транзакций, потоков, очередей) в случае недоступности («зависания») сервера
- Обновление приложений «на-лету», без простоя:
- добавление на сервер новой версии приложения в «закрытом» режиме, пока пользователи продолжают работать со предыдущей
- тестирование корректности развертывания новой версии
- «скрытый» перевод на использование новой версии всех пользователей
к оглавлению
Как контейнер сервлетов управляет жизненным циклом сервлета, когда и какие методы вызываются?
Контейнер сервлетов управляет четырьмя фазами жизненного цикла сервлета:
- Загрузка класса сервлета — когда контейнер получает запрос для сервлета, то происходит загрузка класса сервлета в память и вызов его конструктора без параметров.
- Инициализация класса сервлета — после того как класс загружен контейнер инициализирует объект
ServletConfigдля этого сервлета и внедряет его черезinit()метод. Это и есть место где сервлет класс преобразуется из обычного класса в сервлет. - Обработка запросов — после инициализации сервлет готов к обработке запросов. Для каждого запроса клиента сервлет контейнер порождает новый поток и вызывает метод
service()путем передачи ссылки на объекты ответа и запроса. - Удаление — когда контейнер останавливается или останавливается приложение, то контейнер сервлетов уничтожает классы сервлетов путем вызова
destroy()метода.
Таким образом, сервлет создаётся при первом обращении к нему и живёт на протяжении всего времени работы приложения (в отличии от объектов классов, которые уничтожаются сборщиком мусора после того, как они уже не используются) и весь жизненный цикл сервлета можно описать как последовательность вызова методов:
public void init(ServletConfig config)– используется контейнером для инициализации сервлета. Вызывается один раз за время жизни сервлета.public void service(ServletRequest request, ServletResponse response)– вызывается для каждого запроса. Метод не может быть вызван раньше выполненияinit()метода.public void destroy()– вызывается для уничтожения сервлета (один раз за время жизни сервлета).
к оглавлению
Что такое «дескриптор развертывания»?
Дескриптор развертывания — это конфигурационный файл артефакта, который будет развернут в контейнере сервлетов. В спецификации Java Platform, Enterprise Edition дескриптор развертывания описывает то, как компонент, модуль или приложение (такое, как веб-приложение или приложение предприятия) должно быть развернуто.
Этот конфигурационный файл указывает параметры развертывания для модуля или приложения с определенными настройками, параметры безопасности и описывает конкретные требования к конфигурации. Для синтаксиса файлов дескриптора развертывания используется язык XML.
<?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <display-name>Display name.</display-name> <description>Description text.</description> <servlet> <servlet-name>ExampleServlet</servlet-name> <servlet-class>xyz.company.ExampleServlet</servlet-class> <load-on-startup>1</load-on-startup> <init-param> <param-name>configuration</param-name> <param-value>default</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>ExampleServlet</servlet-name> <url-pattern>/example</url-pattern> </servlet-mapping> <servlet> <servlet-name>ExampleJSP</servlet-name> <jsp-file>/sample/Example.jsp</jsp-file> </servlet> <context-param> <param-name>myParam</param-name> <param-value>the value</param-value> </context-param> </web-app>
Для веб-приложений дескриптор развертывания должен называться web.xml и находиться в директории WEB-INF, в корне веб-приложения. Этот файл является стандартным дескриптором развертывания, определенным в спецификации. Также есть и другие типы дескрипторов, такие, как файл дескриптора развертывания sun-web.xml, содержащий специфичные для Sun GlassFish Enterprise Server данные для развертывания именно для этого сервера приложений или файл application.xml в директории META-INF для приложений J2EE.
к оглавлению
Какие действия необходимо проделать при создании сервлетов?
Чтобы создать сервлет ExampleServlet, необходимо описать его в дескрипторе развёртывания:
<servlet-mapping> <servlet-name>ExampleServlet</servlet-name> <url-pattern>/example</url-pattern> </servlet-mapping> <servlet> <servlet-name>ExampleServlet</servlet-name> <servlet-class>xyz.company.ExampleServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>default</param-value> </init-param> </servlet>
Затем создать класс xyz.company.ExampleServlet путём наследования от HttpServlet и реализовать логику его работы в методе service() или методах doGet()/doPost().
к оглавлению
В каком случае требуется переопределять метод service()?
Метод service() переопределяется, когда необходимо, чтобы сервлет обрабатывал все запросы (и GET, и POST) в одном методе.
Когда контейнер сервлетов получает запрос клиента, то происходит вызов метода service(), который в зависимости от поступившего запроса вызывает или метод doGet() или метод doPost().
к оглавлению
Есть ли смысл определять для сервлета конструктор? Каким образом лучше инициализировать данные?
Большого смысла определять для сервлета конструктор нет, т.к. инициализировать данные лучше не в конструкторе, а переопределив метод init(), в котором имеется возможность доступа к параметрам инициализации сервлета через использование объекта ServletConfig.
к оглавлению
Почему необходимо переопределить только init() метод без аргументов?
Метод init() переопределяется, если необходимо инициализировать какие-то данные до того как сервлет начнет обрабатывать запросы.
При переопределении метода init(ServletConfig config), первым должен быть вызван метод super(config), который обеспечит вызов метода init(ServletConfig config) суперкласса. GenericServlet предоставляет другой метод init() без параметров, который будет вызываться в конце метода init(ServletConfig config).
Необходимо использовать переопределенный метод init() без параметров для инициализации данных во избежание каких-либо проблем, например ошибку, когда вызов super() не указан в переопределенном init(ServletConfig config).
к оглавлению
Какие наиболее распространенные задачи выполняются в контейнере сервлетов?
- Поддержка обмена данными. Контейнер сервлетов предоставляет легкий способ обмена данными между веб клиентом (браузером) и сервлетом. Благодаря контейнеру нет необходимости создавать слушателя сокета на сервере для отслеживания запросов от клиента, а также разбирать запрос и генерировать ответ. Все эти важные и комплексные задачи решаются с помощью контейнера и разработчик может сосредоточиться на бизнес логике приложения.
- Управление жизненным циклом сервлетов и ресурсов. Начиная от загрузки сервлета в память, инициализации, внедрения методов и заканчивая уничтожением сервлета. Контейнер так же предоставляет дополнительные утилиты, например JNDI, для управления пулом ресурсов.
- Поддержка многопоточности. Контейнер самостоятельно создает новую нить для каждого запроса и предоставляет ей запрос и ответ для обработки. Таким образом сервлет не инициализируется заново для каждого запроса и тем самым сохраняет память и уменьшает время до обработки запроса.
- Поддержка JSP. JSP классы не похожи на стандартные классы джавы, но контейнер сервлетов преобразует каждую JSP в сервлет и далее управляется контейнером как обычным сервлетом.
- Различные задачи. Контейнер сервлетов управляет пулом ресурсов, памятью приложения, сборщиком мусора. Предоставляются возможности настройки безопасности и многое другое.
к оглавлению
Что вы знаете о сервлетных фильтрах?
Сервлетный фильтр — это Java-код, пригодный для повторного использования и позволяющий преобразовать содержание HTTP-запросов, HTTP-ответов и информацию, содержащуюся в заголовках HTML. Сервлетный фильтр занимается предварительной обработкой запроса, прежде чем тот попадает в сервлет, и/или последующей обработкой ответа, исходящего из сервлета.
Сервлетные фильтры могут:
- перехватывать инициацию сервлета прежде, чем сервлет будет инициирован;
- определить содержание запроса прежде, чем сервлет будет инициирован;
- модифицировать заголовки и данные запроса, в которые упаковывается поступающий запрос;
- модифицировать заголовки и данные ответа, в которые упаковывается получаемый ответ;
- перехватывать инициацию сервлета после обращения к сервлету.
Сервлетный фильтр может быть конфигурирован так, что он будет работать с одним сервлетом или группой сервлетов. Основой для формирования фильтров служит интерфейс javax.servlet.Filter, который реализует три метода:
void init(FilterConfig config) throws ServletException;void destroy();void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
Метод init() вызывается прежде, чем фильтр начинает работать,и настраивает конфигурационный объект фильтра. Метод doFilter() выполняет непосредственно работу фильтра. Таким образом, сервер вызывает init() один раз, чтобы запустить фильтр в работу, а затем вызывает doFilter() столько раз, сколько запросов будет сделано непосредственно к данному фильтру. После того, как фильтр заканчивает свою работу, вызывается метод destroy().
Интерфейс FilterConfig содержит метод для получения имени фильтра, его параметров инициации и контекста активного в данный момент сервлета. С помощью своего метода doFilter() каждый фильтр получает текущий запрос request и ответ response, а также FilterChain, содержащий список фильтров, предназначенных для обработки. В doFilter() фильтр может делать с запросом и ответом всё, что ему захочется — собирать данные или упаковывать объекты для придания им нового поведения. Затем фильтр вызывает chain.doFilter(), чтобы передать управление следующему фильтру. После возвращения этого вызова фильтр может по окончании работы своего метода doFilter() выполнить дополнительную работу над полученным ответом. К примеру, сохранить регистрационную информацию об этом ответе.
После того, как класс-фильтр откомпилирован, его необходимо установить в контейнер и «приписать» (map) к одному или нескольким сервлетам. Объявление и подключение фильтра отмечается в дескрипторе развёртывания web.xml внутри элементов <filter> и <filter-mapping>. Для подключение фильтра к сервлету необходимо использовать вложенные элементы <filter-name> и <servlet-name>.
Объявление класс-фильтра
FilterConnectс именемFilterName:
<filter> <filter-name>FilterName</filter-name> <filter-class>FilterConnect</filter-class> <init-param> <!--- фильтр имеет параметр инициализации `active`, которому присваивается значение `true`. --> <param-name>active</param-name> <param-value>true</param-true> </init-param> </filter>
Подключение фильтра
FilterNameк сервлетуServletName:
<filter-mapping> <filter-name>FilterName</filter-name> <servlet-name>ServletName</servlet-name> </filter-mapping>
Для связи фильтра со страницами HTML или группой сервлетов необходимо использовать тег <url-pattern>:
Подключение фильтра
FilterNameко всем вызовам .html страниц
<filter-mapping> <filter-name>FilterName</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping>
Порядок, в котором контейнер строит цепочку фильтров для запроса определяется следующими правилами:
- цепочка, определяемая
<url-pattern>, выстраивается в том порядке, в котором встречаются соответствующие описания фильтров вweb.xml; - последовательность сервлетов, определенных с помощью
<servlet-name>, также выполняется в той последовательности, в какой эти элементы встречаются в дескрипторе развёртыванияweb.xml.
к оглавлению
Зачем в сервлетах используются различные listener?
Listener (слушатель) работает как триггер, выполняя определённые действия при наступлении какого-либо события в жизненном цикле сервлета.
Слушатели, разделённые по области видимости (scope):
- Request:
ServletRequestListenerиспользуется для того, чтобы поймать момент создания и уничтожения запроса;ServletRequestAttributeListenerиспользуется для прослушивания событий, происходящих с атрибутами запроса.
- Context:
ServletContextListenerпозволяет поймать момент, когда контекст инициализируется либо уничтожается;ServletContextAttributeListenerиспользуется для прослушивании событий, происходящих с атрибутами в контексте.
- Session:
HttpSessionListenerпозволяет поймать момент создания и уничтожения сессии;HttpSessionAttributeListenerиспользуется при прослушивании событий происходящих с атрибутами в сессии;HttpSessionActivationListenerиспользуется в случае, если происходит миграция сессии между различными JVM в распределённых приложениях;HttpSessionBindingListenerтак же используется для прослушивания событий, происходящих с атрибутами в сессии. Разница междуHttpSessionAttributeListenerиHttpSessionBindingListenerслушателями: первый декларируется вweb.xml; экземпляр класса создается контейнером автоматически в единственном числе и применяется ко всем сессиям; второй: экземпляр класса должен быть создан и закреплён за определённой сессией «вручную», количество экземпляров также регулируется самостоятельно.
Подключение слушателей:
<web-app> ... <listener> <listener-class>xyz.company.ExampleListener</listener-class> </listener> ... </web-app>
HttpSessionBindingListener подключается в качестве атрибута непосредственно в сессию, т.е., чтобы его подключить необходимо:
- создать экземпляр класса, реализующего этот интерфейс;
- положить созданный экземпляр в сессию при помощи
setAttribute(String, Object).
к оглавлению
Когда стоит использовать фильтры сервлетов, а когда слушателей?
Следует использовать фильтры, если необходимо обрабатывать входящие или исходящие данные (например: для аутентификации, преобразования формата, компрессии, шифрования и т.д.), в случае, когда необходимо реагировать на события — лучше применять слушателей.
к оглавлению
Как реализовать запуск сервлета одновременно с запуском приложения?
Контейнер сервлетов обычно загружает сервлет по первому запросу клиента.
Если необходимо загрузить сервлет прямо на старте приложения (например если загрузка сервлета происходит длительное время) следует использовать элемент <load-on-startup> в дескрипторе или аннотацию @loadOnStartup в коде сервлета, что будет указывать на необходимость загрузки сервлета при запуске.
Если целочисленное значение этого параметра отрицательно, то сервлет будет загружен при запросе клиента. В противном случае — загрузится на старте приложения, при этом, чем число меньше, тем раньше в очереди на загрузку он окажется.
<servlet> <servlet-name>ExampleServlet</servlet-name> <servlet-class>xyz.company.ExampleServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
к оглавлению
Как обработать в приложении исключения, выброшенные другим сервлетом?
Когда приложение выбрасывет исключение контейнер сервлетов обрабатывает его и создаёт ответ в формате HTML. Это аналогично тому, что происходит при кодах ошибок вроде 404, 403 и т.д.
В дополнении к этому существует возможность написания собственных сервлетов для обработки исключений и ошибок с указанием их в дескрипторе развертывания:
<error-page> <error-code>404</error-code> <location>/AppExceptionHandler</location> </error-page> <error-page> <exception-type>javax.servlet.ServletException</exception-type> <location>/AppExceptionHandler</location> </error-page>
Основная задача таких сервлетов — обработать ошибку/исключение и сформировать понятный ответ пользователю. Например, предоставить ссылку на главную страницу или же описание ошибки.
к оглавлению
Что представляет собой ServletConfig?
Интерфейс javax.servlet.ServletConfig используется для передачи сервлету конфигурационной информации. Каждый сервлет имеет свой собственный экземпляр объекта ServletConfig, создаваемый контейнером сервлетов.
Для установки параметров конфигурации используются параметры init-param в web.xml:
<servlet> <servlet-name>ExampleServlet</servlet-name> <servlet-class>xyz.company.ExampleServlet</servlet-class> <init-param> <param-name>exampleParameter</param-name> <param-value>parameterValue</param-value> </init-param> </servlet>
или аннотации @WebInitParam:
@WebServlet( urlPatterns = "/example", initParams = { @WebInitParam(name = "exampleParameter", value = "parameterValue") } ) public class ExampleServlet extends HttpServlet { //... }
Для получения ServletConfig сервлета используется метод getServletConfig().
к оглавлению
Что представляет собой ServletContext?
Уникальный (в рамках веб-приложения) объект ServletContext реализует интерфейс javax.servlet.ServletContext и предоставляет сервлетам доступ к параметрам этого веб-приложения. Для предоставления доступа используется элемент <context-param> в web.xml:
<web-app> ... <context-param> <param-name>exampleParameter</param-name> <param-value>parameterValue</param-value> </context-param> ... </web-app>
Объект ServletContext можно получить с помощью метода getServletContext() у интерфейса ServletConfig. Контейнеры сервлетов так же могут предоставлять контекстные объекты, уникальные для группы сервлетов. Каждая из групп будет связана со своим набором URL-путей хоста. В спецификации Servlet 3 ServletContext был расширен и теперь предоставляет возможности программного добавления слушателей и фильтров в приложение. Так же у этого интерфейса имеется множество полезных методов таких как getServerInfo(), getMimeType(), getResourceAsStream() и т.д.
к оглавлению
В чем отличия ServletContext и ServletConfig?
ServletConfigуникален для сервлета, аServletContext— для приложения;ServletConfigиспользуется для предоставления параметров инициализации конкретному сервлету, аServletContextдля предоставления параметров инициализации для всех сервлетов приложения;- для
ServletConfigвозможности модифицировать атрибуты отсутствуют, атрибуты в объектеServletContextможно изменять.
к оглавлению
Для чего нужен интерфейс ServletResponse?
Интерфейс ServletResponse используется для отправки данных клиенту. Все методы данного инструмента служат именно этой цели:
String getCharacterEncoding()— возвращает MIME тип кодировки (к примеру — UTF8), в которой будет выдаваться информация;void setLocale(Locale locale)/Locale getLocale()— указывают на язык используемый в документе;ServletOutputStream getOutputStream()/PrintWriter getWriter()— возвращают потоки вывода данных;void setContentLength(int len)— устанавливает значение поля HTTP заголовка Content-Length;void setContentType(String type)— устанавливает значение поля HTTP заголовка Content-Type.void reset()— позволяет сбросить HTTP заголовок к значениям по-умолчанию, если он ещё не был отправлен- и др.
к оглавлению
Для чего нужен интерфейс ServletRequest?
Интерфейс ServletRequest используется для получения параметров соединения, запроса, а также заголовков, входящего потока данных и т.д.
к оглавлению
Что такое Request Dispatcher?
Интерфейс RequestDispatcher используется для передачи запроса другому ресурсу, при этом существует возможность добавления данных, полученных из этого ресурса к собственному ответу сервлета. Так же этот интерфейс используется для внутренней коммуникации между сервлетами в одном контексте.
В интерфейсе объявлено два метода:
void forward(ServletRequest var1, ServletResponse var2)— передает запрос из сервлета к другому ресурсу (сервлету, JSP или HTML файлу) на сервере.void include(ServletRequest var1, ServletResponse var2)— включает контент ресурса (сервлет, JSP или HTML страница) в ответ.
Доступ к интерфейсу можно получить с помощью метода интерфейса ServletContext — RequestDispatcher getRequestDispatcher(String path), где путь начинающийся с /, интерпретируется относительно текущего корневого пути контекста.
к оглавлению
Как из одного сервлета вызвать другой сервлет?
Для вызова сервлета из того же приложения необходимо использовать механизм внутренней коммуникации сервлетов (inter-servlet communication mechanisms) через вызовы методов RequestDispatcher:
forward()— передаёт выполнение запроса в другой сервлет;include()— предоставляет возможность включить результат работы другого сервлета в возвращаемый ответ.
Если необходимо вызывать сервлет принадлежащий другому приложению, то использовать RequestDispatcher уже не получится, т.к. он определен только для текущего приложения. Для подобных целей необходимо использовать метод ServletResponse — sendRedirect() которому предоставляется полный URL другого сервлета. Для передачи данных между сервлетами можно использовать cookies.
к оглавлению
Чем отличается sendRedirect() от forward()?
forward():
- Выполняется на стороне сервера;
- Запрос перенаправляется на другой ресурс в пределах того же сервера;
- Не зависит от протокола клиентского запроса, так как обеспечивается контейнером сервлетов;
- Нельзя применять для внедрения сервлета в другой контекст;
- Клиент не знает о фактически обрабатываемом ресурсе и URL в строке остается прежним;
- Выполняется быстрее метода
sendRedirect(); - Определён в интерфейсе
RequestDispatcher.
sendRedirect():
- Выполняется на стороне клиента;
- Клиенту возвращается ответ
302 (redirect)и запрос перенаправляется на другой сервер; - Может использоваться только с клиентами HTTP;
- Разрешается применять для внедрения сервлета в другой контекст;
- URL адрес изменяется на адрес нового ресурса;
- Медленнее
forward()т.к. требует создания нового запроса; - Определён в интерфейсе
HttpServletResponse.
к оглавлению
Для чего используются атрибуты сервлетов и как происходит работа с ними?
Атрибуты сервлетов используются для внутренней коммуникации сервлетов.
В веб-приложении существует возможность работы с атрибутами используя методы setAttribute(), getAttribute(), removeAttribute(), getAttributeNames(), которые предоставлены интерфейсами ServletRequest, HttpSession и ServletContext (для областей видимости request, session, context соответственно).
к оглавлению
Каким образом можно допустить в сервлете deadlock?
Можно получить блокировку, например, допустив циклические вызовы метода doPost() в методе doGet() и метода doGet() в методе doPost().
к оглавлению
Как получить реальное расположение сервлета на сервере?
Реальный путь к расположению сервлета на сервере можно получить из объекта ServletContext:
getServletContext().getRealPath(request.getServletPath()).
к оглавлению
Как получить информацию о сервере из сервлета?
Информацию о сервере можно получить из объекта ServletContext:
getServletContext().getServerInfo().
к оглавлению
Как получить IP адрес клиента на сервере?
IP адрес клиента можно получить вызвав request.getRemoteAddr().
к оглавлению
Какие классы-обертки для сервлетов вы знаете?
Собственные обработчики ServletRequest и ServletResponse можно реализовать, добавив новые или переопределив существующие методы у классов-обёрток ServletRequestWrapper (HttpServletRequestWrapper) и ServletResponseWrapper (HttpServletRequestWrapper).
к оглавлению
В чем отличия GenericServlet и HttpServlet?
Абстрактный класс GenericServlet — независимая от используемого протокола реализация интерфейса Servlet, а абстрактный класс HttpServlet в свою очередь расширяет GenericServlet для протокола HTTP..
к оглавлению
Почему HttpServlet класс объявлен как абстрактный?
Класс HTTPServlet предоставляет лишь общую реализацию сервлета для HTTP протокола. Реализация ключевых методов doGet() и doPost(), содержащих основную бизнес-логику перекладывается на разработчика и по умолчанию возвращает HTTP 405 Method Not Implemented error.
к оглавлению
Какие основные методы присутствуют в классе HttpServlet?
doGet()— для обработки HTTP запросовGET;doPost()— для обработки HTTP запросовPOST;doPut()— для обработки HTTP запросовPUT;doDelete()— для обработки HTTP запросовDELETE;doHead()— для обработки HTTP запросовHEAD;doOptions()— для обработки HTTP запросовOPTIONS;doTrace()— для обработки HTTP запросовTRACE.
к оглавлению
Стоит ли волноваться о многопоточной безопасности работая с сервлетами?
Методы init() и destroy() вызываются один раз за жизненный цикл сервлета — поэтому по поводу них беспокоиться не стоит.
Методы doGet(), doPost(), service() вызываются на каждый запрос клиента и т.к. сервлеты используют многопоточность, то здесь задумываться о потокобезопасной работе обязательно. При этом правила использования многопоточности остаются теми же: локальные переменные этих методов будут созданы отдельно для каждого потока, а при использовании глобальных разделяемых ресурсов необходимо использовать синхронизацию или другие приёмы многопоточного программирования.
к оглавлению
Какой метод HTTP не является неизменяемым?
HTTP метод называется неизменяемым, если он на один и тот же запрос всегда возвращает одинаковый результат. HTTP методы GET, PUT, DELETE, HEAD и OPTIONS являются неизменяемыми, поэтому необходимо реализовывать приложение так, чтобы эти методы возвращали одинаковый результат постоянно. К изменяемым методам относится метод POST, который и используется для реализации чего-либо, что изменяется при каждом запросе.
К примеру, для доступа к статической HTML странице используется метод GET, т.к. он всегда возвращает одинаковый результат. При необходимости сохранять какую-либо информацию, например в базе данных, нужно использовать POST метод.
к оглавлению
Какие есть методы отправки данных с клиента на сервер?
GET— используется для запроса содержимого указанного ресурса, изображения или гипертекстового документа. Вместе с запросом могут передаваться дополнительные параметры как часть URI, значения могут выбираться из полей формы или передаваться непосредственно через URL. При этом запросы кэшируются и имеют ограничения на размер. Этот метод является основным методом взаимодействия браузера клиента и веб-сервера.POST— используется для передачи пользовательских данных в содержимом HTTP-запроса на сервер. Пользовательские данные упакованы в тело запроса согласно полю заголовка Content-Type и/или включены в URI запроса. При использовании метода POST под URI подразумевается ресурс, который будет обрабатывать запрос.
к оглавлению
В чем разница между методами GET и POST?
GETпередает данные серверу используя URL, тогда какPOSTпередает данные, используя тело HTTP запроса. Длина URL ограничена 1024 символами, это и будет верхним ограничением для данных, которые можно отослать черезGET.POSTможет отправлять гораздо большие объемы данных. Лимит устанавливается web-server и составляет обычно около 2 Mb.- Передача данных методом
POSTболее безопасна, чем методомGET, так как секретные данные (например пароль) не отображаются напрямую в web-клиенте пользователя, в отличии от URL, который виден почти всегда. Иногда это преимущество превращается в недостаток — вы не сможете послать данные за кого-то другого. GETметод является неизменяемым, тогда какPOST— изменяемый.
к оглавлению
В чем разница между PrintWriter и ServletOutputStream?
PrintWriter — класс для работы с символьным потоком, экземпляр которого можно получить через метод ServletResponse getWriter();
ServletOutputStream — класс для работы байтовым потоком. Для получения его экземпляра используется метод ServletResponse getOutputStream().
к оглавлению
Можно ли одновременно использовать в сервлете PrintWriter и ServletOutputStream?
Так сделать не получится, т.к. при попытке одновременного вызова getWriter() и getOutputStream() будет выброшено исключение java.lang.IllegalStateException с сообщением, что уже был вызван другой метод.
к оглавлению
Расскажите об интерфейсе SingleThreadModel.
Интерфейс SingleThreadModel является маркерным — в нем не объявлен ни один метод, однако, если сервлет реализует этот интерфейс, то метод service() этого сервлета гарантированно не будет одновременно выполняться в двух потоках. Контейнер сервлетов либо синхронизирует обращения к единственному экземпляру, либо обеспечивает поддержку пула экземпляров и перенаправление запроса свободному сервлету.
Другими словами, контейнер гарантирует отсутствие конфликтов при одновременном обращении к переменным или методам экземпляра сервлета. Однако существуют также и другие разделяемые ресурсы, которые даже при использовании этого интерфейса, остаются всё так же доступны обработчикам запросов в других потоках. Т.о. пользы от использования этого интерфейса немного и в спецификации Servlet 2.4 он был объявлен deprecated.
к оглавлению
Что означает URL encoding? Как это осуществить в Java?
URL Encoding — процесс преобразования данных в форму CGI (Common Gateway Interface), не содержащую пробелов и нестандартных символов, которые заменяются в процессе кодирования на специальные escape-символы. В Java для кодирования строки используется метод java.net.URLEncoder.encode(String str, String unicode). Обратная операция декодирования возможна через использование метода java.net.URLDecoder.decode(String str, String unicode).
Hello мир!преобразовывается вHello%20%D0%BC%D0%B8%D1%80!.
к оглавлению
Какие различные методы управления сессией в сервлетах вы знаете?
При посещении клиентом Web-ресурса и выполнении вариантов запросов, контекстная информация о клиенте не хранится. В протоколе HTTP нет возможностей для сохранения и изменения информации о предыдущих посещениях клиента. Сеанс (сессия) – соединение между клиентом и сервером, устанавливаемое на определенное время, за которое клиент может отправить на сервер сколько угодно запросов. Сеанс устанавливается непосредственно между клиентом и Web-сервером. Каждый клиент устанавливает с сервером свой собственный сеанс. Сеансы используются для обеспечения хранения данных во время нескольких запросов Web-страницы или на обработку информации, введенной в пользовательскую форму в результате нескольких HTTP-соединений (например, клиент совершает несколько покупок в интернет-магазине; студент отвечает на несколько тестов в системе дистанционного обучения).
Существует несколько способов обеспечения уникального идентификатора сессии:
- User Authentication – Предоставление учетных данных самим пользователем в момент аутентификации. Переданная таким образом информация в дальнейшем используется для поддержания сеанса. Это метод не будет работать, если пользователь вошёл в систему одновременно из нескольких мест.
- HTML Hidden Field – Присвоение уникального значения скрытому полю HTML страницы, в момент когда пользователь начинает сеанс. Этот метод не может быть использован со ссылками, потому что нуждается в подтверждении формы со скрытым полем каждый раз во время формирования запроса. Кроме того, это не безопасно, т.к. существует возможность простой подмены такого идентификатора.
- URL Rewriting – Добавление идентификатора сеанса как параметра URL. Достаточно утомительная операция, потому что требует постоянного отслеживания этого идентификатора при каждом запросе или ответе.
- Cookies – Использование небольших фрагментов данных, отправленных web-сервером и хранимых на устройстве пользователя. Данный метод не будет работать, если клиент отключает использование cookies.
- Session Management API – Использование специального API для отслеживания сеанса, построенный на основе и на методах, описанных выше и который решает частные проблемы перечисленных способов:
- Чаще всего недостаточно просто отслеживать сессию, необходимо ещё и сохранять какие-либо дополнительные данные о ней, которые могут потребоваться при обработке последующих запросов. Осуществление такого поведения требует много дополнительных усилий.
- Все вышеперечисленные методы не являются универсальными: для каждого из них можно подобрать конкретный сценарий, при котором они не будут работать.
к оглавлению
Что такое cookies?
Сookies («куки») — небольшой фрагмент данных, отправленный web-сервером и хранимый на устройстве пользователя. Всякий раз при попытке открыть страницу сайта, web-клиент пересылает соответствующие этому сайту cookies web-серверу в составе HTTP-запроса. Применяется для сохранения данных на стороне пользователя и на практике обычно используется для:
- аутентификации пользователя;
- хранения персональных предпочтений и настроек пользователя;
- отслеживания состояния сеанса доступа пользователя;
- ведения разнообразной статистики.
к оглавлению
Какие методы для работы с cookies предусмотрены в сервлетах?
Servlet API предоставляет поддержку cookies через класс javax.servlet.http.Cookie:
- Для получения массива cookies из запроса необходимо воспользоваться методом
HttpServletRequest.getCookies(). Методов для добавления cookies вHttpServletRequestне предусмотрено. - Для добавления cookie в ответ используется
HttpServletResponse.addCookie(Cookie c). Метода получения cookies вHttpServletResponseотсутствует.
к оглавлению
Что такое URL Rewriting?
URL Rewriting — специальная перезапись (перекодирование) оригинального URL. Данный механизм может использоваться для управления сессией в сервлетах, когда cookies отключены.
к оглавлению
Зачем нужны и чем отличаются методы encodeURL() и encodeRedirectURL()?
HttpServletResponse.encodeURL() предоставляет способ преобразования URL в HTML гиперссылку с преобразованием спецсимволов и пробелов, а так же добавления session id к URL. Такое поведение аналогично java.net.URLEncoder.encode(), но с добавлением дополнительного параметра jsessionid в конец URL.
Метод HttpServletResponse.encodeRedirectURL() преобразует URL для последующего использования в методе sendRedirect().
Таким образом для HTML гиперссылок при URL rewriting необходимо использовать encodeURL(), а для URL при перенаправлении — encodeRedirectUrl().
к оглавлению
Что такое «сессия»?
Сессия — это сеанс связи между клиентом и сервером, устанавливаемый на определенное время. Сеанс устанавливается непосредственно между клиентом и веб-сервером в момент получения первого запроса к веб-приложению. Каждый клиент устанавливает с сервером свой собственный сеанс, который сохраняется до окончания работы с приложением.
к оглавлению
Как уведомить объект в сессии, что сессия недействительна или закончилась?
Чтобы быть уверенным в том, что объект будет оповещён о прекращении сессии, нужно реализовать интерфейс javax.servlet.http.HttpSessionBindingListener. Два метода этого интерфейса: valueBound() и valueUnbound() используются при добавлении объекта в качестве атрибута к сессии и при уничтожении сессии соответственно.
к оглавлению
Какой существует эффективный способ удостоверится, что все сервлеты доступны только для пользователя с верной сессией?
Сервлет фильтры используются для перехвата всех запросов между контейнером сервлетов и сервлетом. Поэтому логично использовать соответствующий фильтр для проверки необходимой информации (например валидности сессии) в запросе.
к оглавлению
Как мы можем обеспечить transport layer security для нашего веб приложения?
Для обеспечения transport layer security необходимо настроить поддержку SSL сервлет контейнера. Как это сделать зависит от конкретной реализации сервлет-контейнера.
к оглавлению
Как организовать подключение к базе данных, обеспечить журналирование в сервлете?
При работе с большим количеством подключений к базе данных рекомендуется инициализировать их в servlet context listener, а также установить в качестве атрибута контекста для возможности использования другими сервлетами.
Журналирование подключается к сервлету стандартным для логгера способом (например для log4j это может быть property-файл или XML-конфигурация) , а далее эта информация используется при настройке соответствующего context listener.
к оглавлению
Какие основные особенности появились в спецификации Servlet 3?
- Servlet Annotations. До Servlet 3 вся конфигурация содержалась в
web.xml, что приводило к ошибкам и неудобству при работе с большим количестве сервлетов. Примеры аннотаций:@WebServlet,@WebInitParam,@WebFilter,@WebListener. - Web Fragments. Одностраничное веб приложение может содержать множество модулей: все модули прописываются в
fragment.xmlв папкеMETA-INF. Это позволяет разделять веб приложение на отдельные модули, собранные как .jar-файлы в отдельнойlibдиректории. - Динамическое добавление веб компонентов. Появилась возможность программно добавлять фильтры и слушатели, используя
ServletContextобъект. Для этого применяются методыaddServlet(),addFilter(),addListener(). Используя это нововведение стало доступным построение динамической системы, в которой необходимый объект будет создан и вызван только по необходимости. - Асинхронное выполнение. Поддержка асинхронной обработки позволяет передать выполнение запроса в другой поток без удержания всего сервера занятым.
к оглавлению
Какие способы аутентификации доступны сервлету?
Спецификация сервлетов определяет четыре типа проверки подлинности:
- HTTP Basic Authentication —
BASIC. При доступе к закрытым ресурсам появится окно, которое попросит ввести данные для аутентификации. - Form Based Login —
FORM. Используется собственная html форма: - HTTP Digest Authentication —
DIGEST. Цифровая аутентификация с шифрованием. - HTTPS Authentication —
CLIENT-CERT. Аутентификация с помощью клиентского сертификата.
<login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.html</form-login-page> <form-error-page>/error.html</form-error-page> </form-login-config> </login-config>
к оглавлению
Что такое Java Server Pages (JSP)?
JSP (JavaServer Pages) — платформонезависимая переносимая и легко расширяемая технология разработки веб-приложений, позволяющая веб-разработчикам создавать содержимое, которое имеет как статические, так и динамические компоненты. Страница JSP содержит текст двух типов: статические исходные данные, которые могут быть оформлены в одном из текстовых форматов HTML, SVG, WML, или XML, и JSP-элементы, которые конструируют динамическое содержимое. Кроме этого могут использоваться библиотеки JSP-тегов, а также EL (Expression Language), для внедрения Java-кода в статичное содержимое JSP-страниц.
Код JSP-страницы транслируется в Java-код сервлета с помощью компилятора JSP-страниц Jasper, и затем компилируется в байт-код JVM.
JSP-страницы загружаются на сервере и управляются Java EE Web Application. Обычно такие страницы упакованы в файловые архивы .war и .ear.
к оглавлению
Зачем нужен JSP?
JSP расширяет технологию сервлетов обеспечивая возможность создания динамических страницы с HTML подобным синтаксисом.
Хотя создание представлений поддерживается и в сервлетах, но большая часть любой веб-страницы является статической, поэтому код сервлета в таком случае получается чересчур перегруженным, замусоренным и поэтому при его написании легко допустить ошибку.
Еще одним преимуществом JSP является горячее развертывание — возможность заменить одну страницу на другую непосредственно в контейнере без необходимости перекомпилировать весь проект или перезапускать сервер.
Однако рекомендуется избегать написания серьёзной бизнес-логики в JSP и использовать страницу только в качестве представления.
к оглавлению
Опишите, как обрабатываются JSP страницы, начиная от запроса к серверу, заканчивая ответом пользователю.
Когда пользователь переходит по ссылке на страницу page.jsp, он отправляет http-запрос на сервер GET /page.jsp. Затем, на основе этого запроса и текста самой страницы, сервер генерирует java-класс, компилирует его и выполняет полученный сервлет, формирующий ответ пользователю в виде представления этой страницы, который сервер и перенаправляет обратно пользователю.
к оглавлению
Расскажите об этапах (фазах) жизненного цикла JSP.
Если посмотреть код внутри созданной JSP страницы, то он будет выглядеть как HTML и не будет похож на java класс. Конвертацией JSP страниц в HTML код занимается контейнер, который так же создает и сервлет для использования в веб приложении.
Жизненный цикл JSP состоит из нескольких фаз, которыми руководит JSP контейнер:
- Translation – проверка и парсинг кода JSP страницы для создания кода сервлета.
- Compilation – компиляция исходного кода сервлета.
- Class Loading – загрузка скомпилированного класса в память.
- Instantiation – внедрение конструктора без параметра загруженного класса для инициализации в памяти.
- Initialization – вызов
init()метода объекта JSP класса и инициализация конфигурации сервлета с первоначальными параметрами, которые указаны в дескрипторе развертывания (web.xml). После этой фазы JSP способен обрабатывать запросы клиентов. Обычно эти фазы происходят после первого запроса клиента (т.е. ленивая загрузка), но можно настроить загрузку и инициализацию JSP на старте приложения по аналогии с сервлетами. - Request Processing – длительный жизненный цикл обработки запросов клиента JSP страницей. Обработка является многопоточной и аналогична сервлетам — для каждого запроса создается новый поток, объекты
ServletRequestиServletResponse, происходит выполнение сервис методов. - Destroy – последняя фаза жизненного цикла JSP, на которой её класс удаляется из памяти. Обычно это происходит при выключении сервера или выгрузке приложения.
к оглавлению
Расскажите о методах жизненного цикла JSP.
Контейнер сервлетов (например, Tomcat, GlassFish) создает из JSP-страницы класс сервлета, наследующего свойства интерфейса javax.servlet.jsp.HttpJspBase и включающего следующие методы:
jspInit()— метод объявлен в JSP странице и реализуется с помощью контейнера. Этот метод вызывается один раз в жизненном цикле JSP для того, чтобы инициализировать конфигурационные параметры указанные в дескрипторе развертывания. Этот метод можно переопределить с помощью определения элемента JSP scripting и указания необходимых параметров для инициализации;_jspService()— метод переопределяется контейнером автоматически и соответствует непосредственно коду JSP, описанному на странице. Этот метод определен в интерфейсеHttpJspPage, его имя начинается с нижнего подчеркивания и он отличается от других методов жизненного цикла тем, что его невозможно переопределить;jspDestroy()— метод вызывается контейнером для удаления объекта из памяти (на последней фазе жизненного цикла JSP — Destroy). Метод вызывается только один раз и доступен для переопределения, предоставляя возможность освободить ресурсы, которые были созданы вjspInit().
к оглавлению
Какие методы жизненного цикла JSP могут быть переопределены?
Возможно переопределить лишь jspInit() и jspDestroy() методы.
к оглавлению
Как можно предотвратить прямой доступ к JSP странице из браузера?
Прямой доступ к директории /WEB-INF/ из веб-приложения отсутствует. Поэтому JSP-страницы можно расположить внутри этой папки и тем самым запретить доступ к странице из браузера. Однако, по аналогии с описанием сервлетов, будет необходимо настроить дескриптор развертывания:
<servlet> <servlet-name>Example</servlet-name> <jsp-file>/WEB-INF/example.jsp</jsp-file> <init-param> <param-name>exampleParameter</param-name> <param-value>parameterValue</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>Example</servlet-name> <url-pattern>/example.jsp</url-pattern> </servlet-mapping>
к оглавлению
Какая разница между динамическим и статическим содержимым JSP?
Статическое содержимое JSP (HTML, код JavaScript, изображения и т.д.) не изменяется в процессе работы веб приложения.
Динамические ресурсы созданы для того, чтобы отображать свое содержимое в зависимости от пользовательских действий. Обычно они представлены в виде выражений EL (Expression Language), библиотек JSP-тегов и пр.
к оглавлению
Как закомментировать код в JSP?
<!—- HTML комментарий; отображается на странице JSP —->такие комментарии будут видны клиенту при просмотре кода страницы.<%—- JSP комментарий; не отображается на странице JSP —-%>такие комментарии описываются в созданном сервлете и не посылаются клиенту. Для любых комментариев по коду или отладочной информации необходимо использовать именно такой тип комментариев.
к оглавлению
Какие существуют основные типы тегов JSP?
- Выражение JSP:
<%= expression %>— выражение, которое будет обработано с перенаправлением результата на вывод; - Скриплет JSP:
<% code %>— код, добавляемый в методservice(). - Объявление JSP:
<%! code %>— код, добавляемый в тело класса сервлета вне методаservice(). - Директива JSP page:
<%@ page att="value" %>— директивы для контейнера сервлетов с информацией о параметрах. - Директива JSP include:
<%@ include file="url" %>— файл в локальной системе, подключаемый при трансляции JSP в сервлет. - Комментарий JSP:
<%-- comment --%>— комментарий; игнорируется при трансляции JSP страницы в сервлет.
к оглавлению
Что вы знаете о действиях JSP (Action tag и JSP Action Elements).
Action tag и JSP Action Elements предоставляют методы работы с Java Beans, подключения ресурсов, проброса запросов и создания динамических XML элементов. Такие элементы всегда начинаются с записи jsp: и используются непосредственно внутри страницы JSP без необходимости подключения сторонних библиотек или дополнительных настроек.
Наиболее часто используемыми JSP Action Elements являются:
jsp:include:<jsp:include page="относительный URL" flush="true"/>— подключить файл при запросе страницы. Если необходимо, чтобы файл подключался в процессе трансляции страницы, то используется директиваpageсовместно с атрибутомinclude;jsp:useBean:<jsp:useBean att=значение*/>или<jsp:useBean att=значение*>...</jsp:useBean>— найти или создать Java bean;jsp:setProperty:<jsp:setProperty att=значение*/>— установить свойства Java bean, или явно, или указанием на соответствующее значение, передаваемое при запросе;jsp:forward:<jsp:forward page="относительный URL"/>— передать запрос другой странице;jsp:plugin:<jsp:plugin attribute="значение"*>...</jsp:plugin>— сгенерировать (в зависимости от типа браузера) тэгиOBJECTилиEMBEDдля апплета, использующего технологию Java Plugin.
к оглавлению
Взаимодействие JSP — сервлет — JSP.
«JSP — сервлет — JSP» архитектура построения приложений носит название MVC (Model/View/Controller):
-
Model — классы данных и бизнес-логики;
-
View — страницы JSP;
-
Controller — сервлеты.
к оглавлению
Какие области видимости переменных существуют в JSP?
Область видимости объектов определяется тем контекстом, в который помещается данный объект. В зависимости от той или иной области действия так же определяется время существования объекта.
В JSP предусмотрены следующие области действия переменных (объектов):
requestобласть действия запроса — объект будет доступен на текущей JSP странице, странице пересылки (при использованииjsp:forward) или на включаемой странице (при использованииjsp:include);sessionобласть действия сессии — объект будет помещен в сеанс пользователя, будет доступен на всех JSP страницах и будет существовать пока существует сессия пользователя, или он не будет из нее принудительно удален.applicationобласть действия приложения — объект будет доступен для всех пользователей на всех JSP страницах и будет существовать на протяжении всей работы приложения или пока не будет удален принудительно и контекста приложения.pageобласть действия страницы — объект будет доступен только на той странице, где он определен. На включаемых (jsp:include) и переадресуемых (jsp:forward) страницах данный объект уже не будет доступен.
Таким образом, чтобы объект был доступен всем JSP страницам, необходимо указать область видимости application или session, в зависимости от того требуется ли доступ к объекту всем пользователям или только текущему.
Для указания требуемой области действия при определении объекта на JSP странице используется атрибут scope тега jsp:useBean:
<jsp:useBean id="myBean" class="ru.javacore.MyBean" scope="session"/>
Если не указывать атрибут scope, то по умолчанию задается область видимости страницы page
к оглавлению
Какие неявные, внутренние объекты и методы есть на JSP странице?
JSP implicit objects (неявные объекты) создаются контейнером при конвертации JSP страницы в код сервлета для помощи разработчикам. Эти объекты можно использовать напрямую в скриптлетах для передачи информации в сервис методы, однако мы не можем использовать неявные объекты в JSP Declaration, т.к. такой код пойдет на уровень класса.
Существует 9 видов неявных объектов, которые можно использовать прямо на JSP странице. Семь из них объявлены как локальные переменные в начале _jspService() метода, а два оставшихся могут быть использованы как аргументы метода _jspService().
out Object:
<strong>Current Time is</strong>: <% out.print(new Date()); %><br>
request Object:
<strong>Request User-Agent</strong>: <%=request.getHeader("User-Agent") %><br>
response Object:
<strong>Response</strong>: <%response.addCookie(new Cookie("Test","Value")); %>
config Object:
<strong>User init param value</strong>: <%=config.getInitParameter("User") %><br>
application Object:
<strong>User context param value</strong>: <%=application.getInitParameter("User") %><br>
session Object:
<strong>User Session ID</strong>: <%=session.getId() %><br>
pageContext Object:
<% pageContext.setAttribute("Test", "Test Value"); %>
<strong>PageContext attribute</strong>: {Name="Test",Value="<%=pageContext.getAttribute("Test") %>"}<br>
page Object:
<strong>Generated Servlet Name</strong>: <%=page.getClass().getName() %>
exception Object:
<strong>Exception occured</strong>: <%=exception %><br>
к оглавлению
Какие неявные объекты не доступны в обычной JSP странице?
Неявный объект исключений JSP недоступен в обычных JSP страницах и используется на страницах ошибок JSP (errorpage) только для того, чтобы перехватить исключение, выброшенное JSP страницей и далее предоставить какую-либо полезную информацию клиенту.
к оглавлению
Что вы знаете о PageContext и какие преимущества его использования?
Неявный объект JSP — экземпляр класса javax.servlet.jsp.PageContext предоставляет доступ ко всем пространствам имён, ассоциированным с JSP-страницей, а также к различным её атрибутам.
Остальные неявные объекты добавляются к pageContext автоматически.
Класс PageContext это абстрактный класс, а его экземпляр можно получить через вызов метода JspFactory.getPageContext(), и освободить через вызов метода JspFactory.releasePageContext().
PageContext обладает следующим набором особенностей и возможностей:
- единый API для обслуживания пространств имён различных областей видимости;
- несколько удобных API для доступа к различным
public-объектам; - механизм получения
JspWriterдля вывода; - механизм обслуживания использования сессии страницей;
- механизм экспонирования («показа») атрибутов директивы
pageсреде скриптинга; - механизмы направления или включения текущего запроса в другие компоненты приложения;
- механизм обработки процессов исключений на страницах ошибок errorpage;
к оглавлению
Как сконфигурировать параметры инициализации для JSP?
Параметры инициализации для JSP задаются в web.xml файле аналогично сервлетам — элементами servlet и servlet-mapping. Единственным отличием будет указание местонахождения JSP страницы:
<servlet> <servlet-name>Example</servlet-name> <jsp-file>/WEB-INF/example.jsp</jsp-file> <init-param> <param-name>exampleParameter</param-name> <param-value>parameterValue</param-value> </init-param> </servlet>
к оглавлению
Почему не рекомендуется использовать скриплеты (скриптовые элементы) в JSP?
JSP страницы используются в основном для целей отображения представления (view), а вся бизнес-логика (controller) и модель (model) должны быть реализованы в сервлетах или классах-моделях. Обязанность JSP страницы — создание HTML ответа из переданных через атрибуты параметров. Большая часть JSP содержит HTML код, а для того, чтобы помочь верстальщикам понять JSP код страницы предоставляется функционал элементов action, JSP EL, JSP Standart Tag Library. Именно их и необходимо использовать вместо скриптлетов для создания моста между (JSP)HTML и (JSP)Java частями.
к оглавлению
Можно ли определить класс внутри JSP страницы?
Определить класс внутри JSP страницы можно, но это считается плохой практикой:
<%! private static class ExampleOne { //... } %> <% private class ExampleTwo { //... } %>
к оглавлению
Что вы знаете о Языке выражений JSP (JSP Expression Language – EL)?
JSP Expression Language (EL) — скриптовый язык выражений, который позволяет получить доступ к Java компонентам (JavaBeans) из JSP. Начиная с JSP 2.0 используется внутри JSP тегов для отделения Java кода от JSP для обеспечения лёгкого доступа к Java компонентам, уменьшая при этом количество кода Java в JSP-страницах, или даже полностью исключая его.
Развитие EL происходило с целью сделать его более простым для дизайнеров, которые имеют минимальные познания в языке программирования Java. До появления языка выражений, JSP имел несколько специальных тегов таких как скриптлеты (англ.), выражения и т. п. которые позволяли записывать Java код непосредственно на странице. С использованием языка выражений веб-дизайнер должен знать только то, как организовать вызов соответствующих java-методов.
Язык выражений JSP 2.0 включает:
- Создание и изменение переменных.
- Управление потоком выполнения программы: ветвление, выполнение различных типов итераций и т.д.
- Упрощенное обращение к встроенным JSP-объектам.
- Возможность создавать собственные функции.
Язык выражений используется внутри конструкции ${ ... }. Подобная конструкция может размещаться либо отдельно, либо в правой части выражения установки атрибута тега.
к оглавлению
Какие типы EL операторов вы знаете?
Операторы в EL поддерживают наиболее часто используемые манипуляции данными.
Типы операторов:
- Стандартные операторы отношения:
==(илиeq),!=(илиneq),<(илиlt),>(илиgt),<=(илиle),>=(илиge). - Арифметические операторы:
+,–,*,/(илиdiv),%(илиmod). - Логические операторы:
&&(илиand),||(илиor),!(илиnot). - Оператор
empty– используется для проверки переменной наnull, или «пустое значение», который зависит от типа проверяемого объекта. Например, нулевая длина для строки или нулевой размер для коллекции.
к оглавлению
Назовите неявные, внутренние объекты JSP EL и их отличия от объектов JSP.
Язык выражений JSP предоставляет множество неявных объектов, которые можно использовать для получения атрибутов в различных областях видимости (scopes) и для значений параметров. Важно отметить, что они отличаются от неявных объектов JSP и содержат атрибуты в заданной области видимости. Наиболее часто использующийся implicit object в JSP EL и JSP page — это объект pageContext. Ниже представлена таблица неявных объектов JSP EL.
к оглавлению
Как отключить возможность использования EL в JSP?
Для игнорирования выполнения языка выражений на странице существует два способа:
- использовать директиву
<%@ page isELIgnored = «true» %>, - настроить
web.xml(лучше подходит для отключения EL сразу на нескольких страницах):
<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <el-ignored>true</el-ignored> </jsp-property-group> </jsp-config>
к оглавлению
Как узнать тип HTTP метода используя JSP EL?
${pageContext.request.method}.
к оглавлению
Что такое JSTL (JSP Standard tag library)?
JavaServer Pages Standard Tag Library, JSTL, Стандартная библиотека тегов JSP — расширение спецификации JSP (конечный результат JSR 52), добавляющее библиотеку JSP тегов для общих нужд, таких как разбор XML данных, условная обработка, создание циклов и поддержка интернационализации.
JSTL является альтернативой такому виду встроенной в JSP логики, как скриплеты (прямые вставки Java кода). Использование стандартизованного множества тегов предпочтительнее, поскольку получаемый код легче поддерживать и проще отделять бизнес-логику от логики отображения.
Для использования JSTL тегов необходимо:
- подключить зависимости, например в
pom.xml:
<dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency>
- указать пространство имен основных тегов JSTL через указание на JSP странице код:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
к оглавлению
Из каких групп тегов состоит библиотека JSTL?
Группы тегов JSTL согласно их функциональности:
- Core Tags предоставляют возможности итерации, обработки исключений, URL, forward, redirect response и т.д.
- Formatting Tags и Localization Tags предоставляют возможности по форматированию чисел, дат и поддержки i18n локализации и resource bundles.
- SQL Tags – поддержка работы с базами данных.
- XML Tags используются для работы с XML документами: парсинга, преобразования данных, выполнения выражений XPath и т.д..
- JSTL Functions Tags предоставляет набор функций, которые позволяют выполнять различные операции со строками и т.п. Например, по конкатенации или разбиению строк.
к оглавлению
Какая разница между <c:set> и <jsp:useBean>?
Оба тега создают и помещают экземпляры в заданную область видимости, но <jsp:useBean> только создаёт экземпляр конкретного типа, а <c:set>, создав экземпляр, позволяет дополнительно извлекать значение, например, из параметров запроса, сессии и т. д.
к оглавлению
Чем отличается <c:import> от <jsp:include> и директивы <%@include %>?
По сравнению с action-тегом <jsp:include> и директивой <%@include %> тег <c:import> обеспечивает более совершенное включение динамических ресурсов, т.к. получает доступ к источнику, чтение информации из которого происходит непосредственно без буферизации и контент включается в исходную JSP построчно.
к оглавлению
Как можно расширить функциональность JSP?
Что вы знаете о написании пользовательских JSP тегов?
Приведите пример использования собственных тегов.
JSP можно расширить с помощью создания собственных тегов с необходимой функциональностью, которые можно добавить в библиотеку тегов на страницу JSP указав необходимое пространство имен.
/WEB-INF/exampleTag.tld
<?xml version="1.0" encoding="UTF-8"?> <taglib version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_1.xsd"> <tlib-version>1.0</tlib-version> <short-name>example</short-name> <uri>/WEB-INF/exampleTag</uri> <tag> <name>exampleTag</name> <tag-class>xyz.company.ExampleTag</tag-class> <body-content>empty</body-content> <info>The example tag displays Hello World!</info> </tag> </taglib>
xyz.company.ExampleServlet.java
package xyz.company; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; public class ExampleTag extends TagSupport{ private static final long serialVersionUID = 1L; @Override public int doStartTag() throws JspException { try { pageContext.getOut().print("Hello World!"); } catch(IOException ioException) { throw new JspException("Error: " + ioException.getMessage()); } return SKIP_BODY; } }
exampleTag.jsp
<%@ taglib uri="/WEB-INF/exampleTag.tld" prefix="example"%>
<%@ page session="false" pageEncoding="UTF-8"%>
<html>
<head>
<title>Example Tag</title>
</head>
<body>
<h1>Example Tag</h1>
<p><example:exampleTage /><p>
</body>
</html>
Также в пользовательских тегах существует возможность задать входные параметры. Например, существует необходимость отформатировать каким-либо стилем очень длинное число. Для этого можно использовать собственный тег по типу:
<mytags:formatNumber number="123456.789" format="#,## #.00"/>
Используя входные параметры, число должно быть преобразовано на JSP странице в таком виде 123,456.79 согласно шаблону. Т.к. JSTL не предоставляет такой функциональности, необходимо создать пользовательский тег для получения необходимого результата.
к оглавлению
Как сделать перенос строки в HTML средствами JSP?
Для переноса строки можно использовать тег c:out и атрибут escapeXml, который отключает обработку HTML элементов. В этом случае браузер получит следующий код в виде строки и обработает элемент <br> как требуется:
<c:out value="<br> creates a new line in HTML" escapeXml="true"></c:out>
к оглавлению
Почему не нужно конфигурировать стандартные JSP теги в web.xml?
Стандартные теги JSP не конфигурируются в web.xml, потому что tld файлы уже находятся внутри каталога /META-INF/ в jar файлах JSTL.
Когда контейнер загружает веб-приложение и находит tld файлы в в jar файле в директории /META-INF/, то он автоматически настраивает их для непосредственного использования на JSP страницах. Остается только задать пространство имен на JSP странице.
к оглавлению
Как можно обработать ошибки JSP страниц?
Для обработки исключений выброшенных на JSP странице достаточно лишь задать страницу ошибки JSP и при её создании установить значение page directive attribute isErrorPage в значение true. Таким образом будет предоставлен доступ к неявным объектам исключений в JSP и появится возможность передавать собственные, более информативные сообщения об ошибках клиенту. При этом настройка дескриптора развертывания выглядит так:
<error-page> <error-code>404</error-code> <location>/error.jsp</location> </error-page> <error-page> <exception-type>java.lang.Throwable</exception-type> <location>/error.jsp</location> </error-page>
к оглавлению
Как происходит обработка ошибок с помощью JSTL?
Для перехвата и обработки исключений в служебных методах класса служат JSTL Core Tags c:catch и c:if.
Тег
c:catchперехватывает исключение и обертывает его в переменнуюexception, доступную для обработки в тегеc:if:
<c:catch var ="exception">
<% int x = 42/0;%>
</c:catch>
<c:if test = "${exception ne null}">
<p>Exception is : ${exception} <br />
Exception Message: ${exception.message}</p>
</c:if>
Обратите внимание что используется язык выражений JSP EL в теге c:if.
к оглавлению
Как конфигурируется JSP в дескрипторе развертывания.
Для настройки различных параметров JSP страниц используется элемент jsp-config, который отвечает за:
- управление элементами скриптлетов на странице;
- управления выполнением в языке выражений;
- определение шаблона URL для encoding;
- определение размера буфера, который используется для объектов на странице;
- обозначение групп ресурсов, соответствующих шаблону URL, которые должны быть обработаны как XML документ.
<jsp-config> <taglib> <taglib-uri>http://company.xyz/jsp/tlds/customtags</taglib-uri> <taglib-location>/WEB-INF/exampleTag.tld</taglib-location> </taglib> </jsp-config>
к оглавлению
Можно ли использовать Javascript на JSP странице?
Да, это возможно. Несмотря на то, что JSP это серверная технология, на выходе она всё равно создает HTML страницу, на которую можно добавлять Javascript и CSS.
к оглавлению
Всегда ли создается объект сессии на JSP странице, можно ли отключить его создание?
Jsp-страница, по умолчанию, всегда создает сессию. Используя директиву page с атрибутом session можно изменить это поведение:
<%@ page session ="false" %>
к оглавлению
Какая разница между JSPWriter и сервлетным PrintWriter?
PrintWriter является объектом отвечающим за запись содержания ответа на запрос. JspWriter использует объект PrintWriter для буферизации. Когда буфер заполняется или сбрасывается, JspWriter использует объект PrintWriter для записи содержания в ответ.
к оглавлению
Опишите общие практические принципы работы с JSP.
Хорошей практикой работы с технологией JSP является соблюдение следующих правил:
- Следует избегать использования элементов скриптлетов на странице. Если элементы action, JSTL, JSP EL не удовлетворяют потребностям, то желательно написать собственный тег.
- Рекомендуется использовать разные виды комментариев: так JSP комментарии необходимы для уровня кода и отладки, т.к. они не будут показаны клиенту.
- Не стоит размещать какой-либо бизнес логики внутри JSP страницы. Страницы должны использоваться только для создания ответов клиенту.
- Для повышения производительности лучше отключать создание сессии на странице, когда это не требуется.
- Директивы
taglib,pageв начале JSP страницы улучшают читабельность кода. - Следует правильно использовать директиву
includeи элементjsp:include action. Первая используется для статических ресурсов, а второй для динамических ресурсов времени выполнения. - Обработку исключений нужно производить с помощью страниц ошибок. Это помогает избегать запуска специальных служебных методов и может повысить производительность.
- Использующиеся CSS и JavaScript должны быть разнесены в разные файлы и подключаться в начале страницы.
- В большинстве случаев JSTL должно хватать для всех нужд. Если это не так, то в начале следует проанализировать логику своего приложения, и попробовать перенести выполнения кода в сервлет, а далее с помощью установки атрибутов использовать на JSP странице только результат.
к оглавлению
Источники
- javastudy.ru
- java2ee.ru
- Java-online
- Codenet
- JavaTalks Articles
Вопросы для собеседования
Программирование, JAVA, Параллельное программирование, GitHub, Eclipse
Рекомендация: подборка платных и бесплатных курсов Python — https://katalog-kursov.ru/
Цель публикации показать начинающим Java программистам все этапы создания многопоточного сервера. Для полного понимания данной темы основная информация содержится в комментариях моего кода и в выводимых в консоли сообщениях для лучшего понимания что именно происходит и в какой именно последовательности.
В начале будет рассмотрено создание элементарного клиент-сервера, для усвоения базовых знаний, на основе которых будет строиться многопоточная архитектура.
Понятия.
— Потоки: для того чтобы не перепутать что именно подразумевается под потоком я буду использовать существующий в профессиональной литературе синоним — нить, чтобы не путать Stream и Thread, всё-таки более профессионально выражаться — нить, говоря про Thread.
— Сокеты(Sockets): данное понятие тоже не однозначно, поскольку в какой-то момент сервер выполняет — клиентские действия, а клиент — серверные. Поэтому я разделил понятие серверного сокета — (ServerSocket) и сокета (Socket) через который практически осуществляется общение, его будем называть сокет общения, чтобы было понятно о чём речь.
Кроме того сокетов общения создаётся по одному на каждом из обменивающихся данными приложении, поэтому сокет приложения которое имеет у себя объект - ServerSocket и первоначально открывает порт в ожидании подключения будем называть сокет общения на стороне сервера, а сокет который создаёт подключающееся к порту по известному адресу второе приложение будем называть сокетом общения на стороне клиента.
Спасибо за подсказку про Thread.sleep();!
Конечно в реальном коде Thread.sleep(); устанавливать не нужно — это муветон! В данной публикации я его использую только для того чтобы выполнение программы было нагляднее, что бы успевать разобраться в происходящем.
Так что тестируйте, изучайте и в своём коде никогда не используйте Thread.sleep();!
Оглавление:
1) Однопоточный элементарный сервер.
2) Клиент.
3) Многопоточный сервер – сам по себе этот сервер не участвует в общении напрямую, а лишь является фабрикой однонитевых делегатов(делегированных для ведения диалога с клиентами серверов) для общения с вновь подключившимися клиентами, которые закрываются после окончания общения с клиентом.
4) Имитация множественного обращения клиентов к серверу.
По многочисленным замечаниям выкладываю ссылку на исходники на GitHub:
(https://github.com/merceneryinbox/Clietn-Server_Step-by-step.git)
Итак, начнём с изучения структуры однопоточного сервер, который может принять только одного клиента для диалога. Код приводимый ниже необходимо запускать в своей IDE в этом идея всей статьи. Предлагаю все детали уяснить из подробно задокументированного кода ниже:
- 1) Однопоточный элементарный сервер.
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TestAsServer {
/**
*
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// стартуем сервер на порту 3345
try (ServerSocket server= new ServerSocket(3345)){
// становимся в ожидание подключения к сокету под именем - "client" на серверной стороне
Socket client = server.accept();
// после хэндшейкинга сервер ассоциирует подключающегося клиента с этим сокетом-соединением
System.out.print("Connection accepted.");
// инициируем каналы для общения в сокете, для сервера
// канал записи в сокет
DataOutputStream out = new DataOutputStream(client.getOutputStream());
System.out.println("DataOutputStream created");
// канал чтения из сокета
DataInputStream in = new DataInputStream(client.getInputStream());
System.out.println("DataInputStream created");
// начинаем диалог с подключенным клиентом в цикле, пока сокет не закрыт
while(!client.isClosed()){
System.out.println("Server reading from channel");
// сервер ждёт в канале чтения (inputstream) получения данных клиента
String entry = in.readUTF();
// после получения данных считывает их
System.out.println("READ from client message - "+entry);
// и выводит в консоль
System.out.println("Server try writing to channel");
// инициализация проверки условия продолжения работы с клиентом по этому сокету по кодовому слову - quit
if(entry.equalsIgnoreCase("quit")){
System.out.println("Client initialize connections suicide ...");
out.writeUTF("Server reply - "+entry + " - OK");
Thread.sleep(3000);
break;
}
// если условие окончания работы не верно - продолжаем работу - отправляем эхо-ответ обратно клиенту
out.writeUTF("Server reply - "+entry + " - OK");
System.out.println("Server Wrote message to client.");
// освобождаем буфер сетевых сообщений (по умолчанию сообщение не сразу отправляется в сеть, а сначала накапливается в специальном буфере сообщений, размер которого определяется конкретными настройками в системе, а метод - flush() отправляет сообщение не дожидаясь наполнения буфера согласно настройкам системы
out.flush();
}
// если условие выхода - верно выключаем соединения
System.out.println("Client disconnected");
System.out.println("Closing connections & channels.");
// закрываем сначала каналы сокета !
in.close();
out.close();
// потом закрываем сам сокет общения на стороне сервера!
client.close();
// потом закрываем сокет сервера который создаёт сокеты общения
// хотя при многопоточном применении его закрывать не нужно
// для возможности поставить этот серверный сокет обратно в ожидание нового подключения
System.out.println("Closing connections & channels - DONE.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 2) Клиент.
Сервер запущен и находится в блокирующем ожидании server.accept(); обращения к нему с запросом на подключение. Теперь можно подключаться клиенту, напишем код клиента и запустим его. Клиент работает когда пользователь вводит что-либо в его консоли (внимание! в данном случае сервер и клиент запускаются на одном компьютере с локальным адресом — localhost, поэтому при вводе строк, которые должен отправлять клиент не забудьте убедиться, что вы переключились в рабочую консоль клиента!).
После ввода строки в консоль клиента и нажатия enter строка проверяется не ввёл ли клиент кодовое слово для окончания общения дальше отправляется серверу, где он читает её и то же проверяет на наличие кодового слова выхода. Оба и клиент и сервер получив кодовое слово закрывают ресурсы после предварительных приготовлений и завершают свою работу.
Посмотрим как это выглядит в коде:
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
public class TestASClient {
/**
*
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// запускаем подключение сокета по известным координатам и нициализируем приём сообщений с консоли клиента
try(Socket socket = new Socket("localhost", 3345);
BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
DataOutputStream oos = new DataOutputStream(socket.getOutputStream());
DataInputStream ois = new DataInputStream(socket.getInputStream()); )
{
System.out.println("Client connected to socket.");
System.out.println();
System.out.println("Client writing channel = oos & reading channel = ois initialized.");
// проверяем живой ли канал и работаем если живой
while(!socket.isOutputShutdown()){
// ждём консоли клиента на предмет появления в ней данных
if(br.ready()){
// данные появились - работаем
System.out.println("Client start writing in channel...");
Thread.sleep(1000);
String clientCommand = br.readLine();
// пишем данные с консоли в канал сокета для сервера
oos.writeUTF(clientCommand);
oos.flush();
System.out.println("Clien sent message " + clientCommand + " to server.");
Thread.sleep(1000);
// ждём чтобы сервер успел прочесть сообщение из сокета и ответить
// проверяем условие выхода из соединения
if(clientCommand.equalsIgnoreCase("quit")){
// если условие выхода достигнуто разъединяемся
System.out.println("Client kill connections");
Thread.sleep(2000);
// смотрим что нам ответил сервер на последок перед закрытием ресурсов
if(ois.available()!=0) {
System.out.println("reading...");
String in = ois.readUTF();
System.out.println(in);
}
// после предварительных приготовлений выходим из цикла записи чтения
break;
}
// если условие разъединения не достигнуто продолжаем работу
System.out.println("Client sent message & start waiting for data from server...");
Thread.sleep(2000);
// проверяем, что нам ответит сервер на сообщение(за предоставленное ему время в паузе он должен был успеть ответить)
if(ois.available()!=0) {
// если успел забираем ответ из канала сервера в сокете и сохраняем её в ois переменную, печатаем на свою клиентскую консоль
System.out.println("reading...");
String in = ois.readUTF();
System.out.println(in);
}
}
}
// на выходе из цикла общения закрываем свои ресурсы
System.out.println("Closing connections & channels on clentSide - DONE.");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- 3) Многопоточный сервер
А что если к серверу хочет подключиться ещё один клиент!? Ведь описанный выше сервер либо находится в ожидании подключения одного клиента, либо общается с ним до завершения соединения, что делать остальным клиентам? Для такого случая нужно создать фабрику которая будет создавать описанных выше серверов при подключении к сокету новых клиентов и не дожидаясь пока делегированный подсервер закончит диалог с клиентом откроет accept() в ожидании следующего клиента. Но чтобы на серверной машине хватило ресурсов для общения со множеством клиентов нужно ограничить количество возможных подключений. Фабрика будет выдавать немного модифицированный вариант предыдущего сервера(модификация будет касаться того что класс сервера для фабрики будет имплементировать интерфейс — Runnable для возможности его использования в пуле нитей — ExecuteServices). Давайте создадим такую серверную фабрику и ознакомимся с подробным описанием её работы в коде:
- Фабрика:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author mercenery
*
*/
public class MultiThreadServer {
static ExecutorService executeIt = Executors.newFixedThreadPool(2);
/**
* @param args
*/
public static void main(String[] args) {
// стартуем сервер на порту 3345 и инициализируем переменную для обработки консольных команд с самого сервера
try (ServerSocket server = new ServerSocket(3345);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
System.out.println("Server socket created, command console reader for listen to server commands");
// стартуем цикл при условии что серверный сокет не закрыт
while (!server.isClosed()) {
// проверяем поступившие комманды из консоли сервера если такие
// были
if (br.ready()) {
System.out.println("Main Server found any messages in channel, let's look at them.");
// если команда - quit то инициализируем закрытие сервера и
// выход из цикла раздачии нитей монопоточных серверов
String serverCommand = br.readLine();
if (serverCommand.equalsIgnoreCase("quit")) {
System.out.println("Main Server initiate exiting...");
server.close();
break;
}
}
// если комманд от сервера нет то становимся в ожидание
// подключения к сокету общения под именем - "clientDialog" на
// серверной стороне
Socket client = server.accept();
// после получения запроса на подключение сервер создаёт сокет
// для общения с клиентом и отправляет его в отдельную нить
// в Runnable(при необходимости можно создать Callable)
// монопоточную нить = сервер - MonoThreadClientHandler и тот
// продолжает общение от лица сервера
executeIt.execute(new MonoThreadClientHandler(client));
System.out.print("Connection accepted.");
}
// закрытие пула нитей после завершения работы всех нитей
executeIt.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- Модифицированный Runnable сервер для запуска из предыдущего кода:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class MonoThreadClientHandler implements Runnable {
private static Socket clientDialog;
public MonoThreadClientHandler(Socket client) {
MonoThreadClientHandler.clientDialog = client;
}
@Override
public void run() {
try {
// инициируем каналы общения в сокете, для сервера
// канал записи в сокет следует инициализировать сначала канал чтения для избежания блокировки выполнения программы на ожидании заголовка в сокете
DataOutputStream out = new DataOutputStream(clientDialog.getOutputStream());
// канал чтения из сокета
DataInputStream in = new DataInputStream(clientDialog.getInputStream());
System.out.println("DataInputStream created");
System.out.println("DataOutputStream created");
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// основная рабочая часть //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// начинаем диалог с подключенным клиентом в цикле, пока сокет не
// закрыт клиентом
while (!clientDialog.isClosed()) {
System.out.println("Server reading from channel");
// серверная нить ждёт в канале чтения (inputstream) получения
// данных клиента после получения данных считывает их
String entry = in.readUTF();
// и выводит в консоль
System.out.println("READ from clientDialog message - " + entry);
// инициализация проверки условия продолжения работы с клиентом
// по этому сокету по кодовому слову - quit в любом регистре
if (entry.equalsIgnoreCase("quit")) {
// если кодовое слово получено то инициализируется закрытие
// серверной нити
System.out.println("Client initialize connections suicide ...");
out.writeUTF("Server reply - " + entry + " - OK");
Thread.sleep(3000);
break;
}
// если условие окончания работы не верно - продолжаем работу -
// отправляем эхо обратно клиенту
System.out.println("Server try writing to channel");
out.writeUTF("Server reply - " + entry + " - OK");
System.out.println("Server Wrote message to clientDialog.");
// освобождаем буфер сетевых сообщений
out.flush();
// возвращаемся в началло для считывания нового сообщения
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// основная рабочая часть //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// если условие выхода - верно выключаем соединения
System.out.println("Client disconnected");
System.out.println("Closing connections & channels.");
// закрываем сначала каналы сокета !
in.close();
out.close();
// потом закрываем сокет общения с клиентом в нити моносервера
clientDialog.close();
System.out.println("Closing connections & channels - DONE.");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Для имитации множественного обращения клиентов к серверу, создадим и запустим (после запуска серверной части) фабрику Runnable клиентов которые будут подключаться серверу и писать сообщения в цикле:
- 4) Имитация множественного обращения клиентов к серверу.
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
// private static ServerSocket server;
public static void main(String[] args) throws IOException, InterruptedException {
// запустим пул нитей в которых колличество возможных нитей ограничено -
// 10-ю.
ExecutorService exec = Executors.newFixedThreadPool(10);
int j = 0;
// стартуем цикл в котором с паузой в 10 милисекунд стартуем Runnable
// клиентов,
// которые пишут какое-то количество сообщений
while (j < 10) {
j++;
exec.execute(new TestRunnableClientTester());
Thread.sleep(10);
}
// закрываем фабрику
exec.shutdown();
}
}
Как видно из предыдущего кода фабрика запускает — TestRunnableClientTester() клиентов, напишем для них код и после этого запустим саму фабрику, чтобы ей было кого исполнять в своём пуле:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class TestRunnableClientTester implements Runnable {
static Socket socket;
public TestRunnableClientTester() {
try {
// создаём сокет общения на стороне клиента в конструкторе объекта
socket = new Socket("localhost", 3345);
System.out.println("Client connected to socket");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
try (
// создаём объект для записи строк в созданный скокет, для
// чтения строк из сокета
// в try-with-resources стиле
DataOutputStream oos = new DataOutputStream(socket.getOutputStream());
DataInputStream ois = new DataInputStream(socket.getInputStream())) {
System.out.println("Client oos & ois initialized");
int i = 0;
// создаём рабочий цикл
while (i < 5) {
// пишем сообщение автогенерируемое циклом клиента в канал
// сокета для сервера
oos.writeUTF("clientCommand " + i);
// проталкиваем сообщение из буфера сетевых сообщений в канал
oos.flush();
// ждём чтобы сервер успел прочесть сообщение из сокета и
// ответить
Thread.sleep(10);
System.out.println("Client wrote & start waiting for data from server...");
// забираем ответ из канала сервера в сокете
// клиента и сохраняем её в ois переменную, печатаем на
// консоль
System.out.println("reading...");
String in = ois.readUTF();
System.out.println(in);
i++;
Thread.sleep(5000);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Запускайте, вносите изменения в код, только так на самом деле можно понять работу этой структуры.
Спасибо за внимание.