Это проект, который был создан как часть курса, который я прошел в Университете Варшавы. Это простая реализация контейнера сервлета, которая поддерживает Java Servlets, соответствующие API Java Servlet API. Несмотря на то, что это довольно обнаженная, он может загружать и запускать простые приложения, которые используют основные функциональные возможности сервлетов, включая диспетчеринг, JSP и Async Servlets.
Я не нашел много других небольших реализаций контейнеров сервлетов, поэтому не стесняйтесь использовать это в качестве примера того, как создать такой контейнер. Имейте в виду, что из -за ограничений по времени это довольно несовершенный, багги, и, хотя я старался оставаться верным для API Servlet, я взял некоторые свободы здесь и там.
Идея состоит в том, что этот контейнер работает так же, как и Tomcat. Вы можете добавить классы сервлетов, используя ServletContainer :: AddRoute, но предпочтительным способом является использование функциональности сканирования компонентов. То есть, если приложение затрачено в файл warName.war и помещается в каталог deploy внутри server/src/main/resources он будет загружен на запуск и доступен в соответствии с localhost:8000/warName .
По умолчанию сервер работает на порту 8000. Его можно изменить в Main функции. Эта функция также содержит и пример того, как запустить и настроить этот сервер.
Проект был построен с использованием Gradle. Он содержит два подпроекта: сервер и демонстрационное приложение (простая база данных книг). Он был протестирован под Linux, и я не знаю, работает ли он вообще под Windows/MacOS.
Для запуска использования сервера:
./gradlew server:run
Начальный сервер требует от нас создания экземпляра ServletContainer .
ServletContainer(int threads) Создает новый экземпляр ServletContainer . с заданным количеством потоков, используемых для создания Threadpool. Этот пул будет использоваться для обработки HTTP -запросов.
void ServletContainer::start(int port) Запускает контейнер сервлета в данном порту. Имейте в виду, что .WAR -файлы не загружаются на данный момент. Они загружаются только при вызове ServletContainer:servletScan .
void ServletContainer::stop()Грациозно останавливает контейнер.
void ServletContainer::servletScan() Загружает все файлы .war из deploy Directory Inside server/src/main/resources . Он также загрузит файлы .jsp и немедленно перенести их в файлы .class. Будут загружены только классы, аннотированные с @WebServlet . Одним из предостережений является то, что .war, которые я использую, имеют определенную структуру каталогов, которую я не уверен, такая же, как и в большинстве других файлов .war. Вы можете проверить задачу war Gradle в демонстрационном приложении, чтобы увидеть, как должна выглядеть эта структура. Этот метод должен быть вызван не более раз перед началом () методом.
void ServletContainer::servletScan(String url) То же самое, что и ServletContainer::servletScan() , но может загружать .war файлы с данного URL.
void ServletContainer::addRoute(Class<? extends HttpServlet> cls, String path)Добавляет сервлета в контейнер. Сервлет будет обрабатывать только данный URL ( FAQ для более подробной информации см.
Сервер поддерживает одновременную обработку нескольких клиентов одновременно. Каждый клиент будет обрабатываться отдельным потоком от Threadpool. Размер пула по умолчанию 4 и может быть изменен в Main функции.
Сервер будет поддерживать любой класс, который наследует от httpservlet. Такой класс можно добавить с помощью ServletContainer::addRoute .
Наиболее важные функции этих классов реализованы. Вы можете написать клиенту, используя (только) PrintWriter , установить заголовки и состояние ответа. Httpservletrequest может извлечь URL -адрес запроса, метод HTTP (get, post, delete, patch), параметры запроса и параметры из тела (в случае Post). Данные отправляются клиенту после промывания буфера или закрытия HttpServletResponse . Используя RequestDispatcher , вы можете отправлять запросы в другие сервлеты, включая сервлеты JSP.
Существует поддержка асинхронных сервлетов. После выполнения HttpServletRequest::startAsync запрос входит в асинхронный режим. Есть два способа использовать этот режим. Любой код будет работать до тех пор, пока в какой -то момент выполняется AsyncContext::complete , который завершает соединение с клиентом. Вы также можете использовать AsyncConext::start(Runnable) , здесь вам также необходимо выполнить AsyncContext::complete в какой -то момент. Последний совместим с API Java Servlet. Он также поддерживает тайм -аут ( AsyncContext::setTimeout ) и AsyncListener . Асинхронные сервлеты реализованы с использованием CompletableFuture , поэтому они будут использовать Threadpool отдельно от той, которая используется для синхронных клиентов.
Если приложение было записано на .war и перенесено в папку deploy , оно будет загружена автоматически. Все классы, аннотированные с @WebServlet , и наследство от HttpServlet будут добавлены в контейнер сервлета и будут доступны в localhost:port/warName/servletUrl . Каждый такой класс должен иметь ровно один servletUrl указанный в аннотации @WebServlet в атрибуте значения. Если приложение использует JSP, оно будет автоматически составлено в .class и загружено. Многие приложения могут быть загружены, однако я не знаю, что происходит, когда в двух приложениях используются те же имена классов.
Сервер может транспилировать файлы .jsp в .class. Существует поддержка практически всего синтаксиса в JSP. Это включает в себя:
<%@ page import/include=... %><% ... %><%! ... %><%= ... %><%-- %>${...} ${} Синтаксис частично поддерживает язык выражения. Простые арифметические операции могут быть выполнены, и любое выражение экземпляра instance.property1.property2 будет преобразовано в request.getAttribute("instance").getProperty1().getProperty2() . В <% ... %> есть также out.println(...) , который пишет непосредственно клиенту. JSP может отображаться с использованием RequestDispatcher::forward или доступен непосредственно по адресу localhost:8000/warName/jspFileName.jsp . Образец действия JSP доступен по адресу localhost:8000/library/jsp . Имейте в виду, что я реализовал анализ себя, чтобы странный форматирование кода/синтаксис мог его сломать.
Чтобы продемонстрировать контейнер сервлета, я внедрил простое приложение, которое имитирует базу данных книг. Книги могут быть добавлены, удалены и обновлены с помощью HTML -форм. Вся книга также можно просмотреть в таблице HTML. Фронт полностью разработан с использованием JSP. Приложение застегивается в Library.war, используя задачу war Gradle, перенесено на deploy и загружено на сервер при запуске.
Конечные точки:
Существует более 30 тестов, чтобы проверить большую часть функциональности и демонстрационного приложения. Предпочтительно запускать их из IntelliJ из -за проблем, объясненных в FAQ . В качестве альтернативы их можно запустить, используя:
./gradlew test
Этот проект был частью университетского курса, и мне не разрешили использовать какие -либо сторонние библиотеки, кроме Junit.
Существуют проблемы, когда порты сокетов уже используются. Я никогда не собирался исправить это, но большую часть времени он работает, когда бежит от Intellij.
Прямо сейчас разрешение, из которого сервлет должен обрабатывать заданный URL -адрес, довольно примитивно. По сути, получение url -сервера ищет сервлета, чей servletUrl является префиксом url . Если есть много того, что с самым длинным префиксом выбран. Это означает, что если у сервлета есть servletUrl равный /app/home он также будет обрабатывать /app/home/nonexisting/ , /app/home/12345 и т. Д.
Любой может внести свой вклад в этот проект. Я создал несколько проблем с хорошим выпуском, так что не стесняйтесь проверять их :)