From 1f5a2e35c744c46208956c6b7df0663b80b776ec Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Sat, 8 Oct 2022 20:15:15 +0300 Subject: [PATCH 1/8] =?UTF-8?q?=D0=94=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D0=B5?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BD=D1=8F=D1=82=D0=B8=D1=8E=20=C2=AB1.2.=20?= =?UTF-8?q?=D0=A4=D0=BE=D1=80=D0=BC=D1=8B=20=D0=B8=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D0=B0=D1=82=D1=8B=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B8=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=C2=BB.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HomeWork_1.md | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 107 +++++++++++++++++++-------------------------- 2 files changed, 162 insertions(+), 62 deletions(-) create mode 100644 HomeWork_1.md diff --git a/HomeWork_1.md b/HomeWork_1.md new file mode 100644 index 0000000..ac886aa --- /dev/null +++ b/HomeWork_1.md @@ -0,0 +1,117 @@ +# Домашнее задание к занятию «1.1. HTTP и современный Web» + +В качестве результата пришлите ссылки на ваши GitHub-проект в личном кабинете студента на сайте [netology.ru](https://netology.ru). + +**Важно**: ознакомьтесь со ссылками, представленными на главной странице [репозитория с домашними заданиями](../README.md). + +**Важно**: если у вас что-то не получилось, то оформляйте Issue [по установленным правилам](../report-requirements.md). + +**Важно**: все задачи нужно делать в **одном** репозитории. + +## Как сдавать задачи + +1. Создайте на вашем компьютере Maven-проект +1. Инициализируйте в нём пустой Git-репозиторий +1. Добавьте в него готовый файл [.gitignore](../.gitignore) +1. Добавьте в этот же каталог остальные необходимые файлы +1. Сделайте необходимые коммиты +1. Создайте публичный репозиторий на GitHub и свяжите свой локальный репозиторий с удалённым +1. Сделайте пуш (удостоверьтесь, что ваш код появился на GitHub) +1. Ссылку на ваш проект отправьте в личном кабинете на сайте [netology.ru](https://netology.ru) + +## Refactoring & MultiThreading + +### Легенда + +Достаточно часто после того, как прототип проверен (мы про то, что было реализовано на лекции), возникает задача привести это в более-менее нормальный вид: выделить классы, методы, обеспечить должную функциональность. + +### Задача + +Необходимо отрефакторить код, рассмотренный на лекции, и применить все те знания, которые у вас есть: +1. Выделить класс `Server` с методами для + - запуска + - обработки конкретного подключения +1. Реализовать обработку подключений с помощью `ThreadPool`'а (выделите фиксированный на 64 потока и каждое подключение обрабатывайте в потоке из пула) + +Поскольку вы - главный архитектор и проектировщик данного небольшого класса, то все архитектурные решения принимать вам, но будьте готовы к критике со стороны проверяющих. + +### Результат + +В качестве результата пришлите ссылку на ваш проект на GitHub в личном кабинете студента на сайте [netology.ru](https://netology.ru). + +## Handlers* + +**Важно**: это необязательная задача, её выполнение не влияет на получение зачёта. + +### Легенда + +Сервер, который вы написали в предыдущей задаче, - это, конечно, здорово, но пока он не расширяем и его нельзя переиспользовать, т.к. код обработки зашит прямо внутрь сервера. + +Давайте попробуем его сделать немного полезнее. + +Что хотим сделать? Мы хотим сделать так, чтобы в сервер можно было добавлять обработчики на определённые шаблоны путей. + +Что это значит? Мы хотим, чтобы можно было сделать вот так: + +```java +public class Main { + public static void main(String[] args){ + final var server = new Server(); + // код инициализации сервера (из вашего предыдущего ДЗ) + + // добавление handler'ов (обработчиков) + server.addHandler("GET", "/messages", new Handler() { + public void handle(Request request, BufferedOutputStream responseStream) { + // TODO: handlers code + } + }); + server.addHandler("POST", "/messages", new Handler() { + public void handle(Request request, BufferedOutputStream responseStream) { + // TODO: handlers code + } + }); + + server.listen(9999); + } +} +``` + +В итоге на запрос типа GET на путь "/messages" будет вызван первый обработчик, на запрос типа POST и путь "/messages" будет вызван второй. + +Как вы видите - `Handler` из себя представляет функциональный интерфейс всего с одним методом (может быть заменён на lambda). + +`Request` - это класс, который проектируете вы сами, для нас важно, чтобы он содержал: +1. Метод запроса (потому что на разные методы можно назначить один и тот же Handler) +1. Заголовки запроса +1. Тело запроса (если есть) + +`BufferedOutputStream` берётся просто путём заворачивания `OutputStream`'а `socket`'а: `new BufferedOutputStream(socket.getOutputStream())`. + +### Задача + +Реализуйте требования, указанные в легенде. + +
+Подсказки по реализации + +1. Вы принимаете запрос, парсите его целиком (как мы сделали на лекции) и собираете объект типа `Request` +1. На основании данных из `Request` вы выбираете handler (он может быть только один), который и будет обрабатывать запрос +1. Все handler'ы должны храниться в полях `Server`'а +1. Самый простой способ хранить handler'ы - это использовать в качестве ключей метод и путь (можно как сделать `Map` внутри `Map`, так и отдельные `Map`'ы на каждый метод) +1. Поиск хендлера заключается в том, что вы выбираете по нужному методу все зарегистрированные handler'ы, а затем перебираете по пути (используйте пока точное соответствие: считайте, что у вас все запросы без Query String) +1. Найдя нужный handler - достаточно вызвать его метод `handle`, передав туда `Request` и `BufferedOutputStream` +1. Поскольку ваш сервер многопоточный - думайте о том, как вы будете безопасно хранить handler'ы +1. В качестве Body достаточно передавать `InputStream` (напоминаем, Body начинается после `\r\n\r\n` + +Итого: фактически вы решаете задачу поиска элемента в "коллекции" с вызовом его метода. +
+ +### Результат + +Реализуйте новую функциональность в ветке `feature/handlers` вашего репозитория из ДЗ 1 и откройте Pull Request. + +Поскольку вы - главный архитектор и проектировщик данного решения (уже более функционального), то все архитектурные решения принимать вам, но будьте готовы к критике со стороны проверяющих. + +В качестве результата пришлите ссылку на ваш Pull Request на GitHub в личном кабинете студента на сайте [netology.ru](https://netology.ru). + +После того, как ДЗ будет принято, сделайте `merge` для Pull Request'а. \ No newline at end of file diff --git a/README.md b/README.md index ac886aa..8765516 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ -# Домашнее задание к занятию «1.1. HTTP и современный Web» +# Домашнее задание к занятию «1.2. Формы и форматы передачи данных» -В качестве результата пришлите ссылки на ваши GitHub-проект в личном кабинете студента на сайте [netology.ru](https://netology.ru). +В качестве результата пришлите ссылки на ваши GitHub-проекты в личном кабинете студента на сайте [netology.ru](https://netology.ru). **Важно**: ознакомьтесь со ссылками, представленными на главной странице [репозитория с домашними заданиями](../README.md). **Важно**: если у вас что-то не получилось, то оформляйте Issue [по установленным правилам](../report-requirements.md). -**Важно**: все задачи нужно делать в **одном** репозитории. - ## Как сдавать задачи 1. Создайте на вашем компьютере Maven-проект @@ -19,96 +17,81 @@ 1. Сделайте пуш (удостоверьтесь, что ваш код появился на GitHub) 1. Ссылку на ваш проект отправьте в личном кабинете на сайте [netology.ru](https://netology.ru) -## Refactoring & MultiThreading +## query ### Легенда -Достаточно часто после того, как прототип проверен (мы про то, что было реализовано на лекции), возникает задача привести это в более-менее нормальный вид: выделить классы, методы, обеспечить должную функциональность. +В рамках изучения Java Core и изучения работы протокола HTTP вы использовали библиотеку HttpClient из состава [Apache HttpComponents](https://hc.apache.org). -### Задача +В состав данной библиотеки входит утилитный класс [URLEncodedUtils](http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/utils/URLEncodedUtils.html), который и позволяет "парсить" Query String, извлекая параметры. -Необходимо отрефакторить код, рассмотренный на лекции, и применить все те знания, которые у вас есть: -1. Выделить класс `Server` с методами для - - запуска - - обработки конкретного подключения -1. Реализовать обработку подключений с помощью `ThreadPool`'а (выделите фиксированный на 64 потока и каждое подключение обрабатывайте в потоке из пула) +Необходимо добавить в ваш сервер (из предыдущего ДЗ) функциональность обработки параметров запроса так, чтобы можно было из объекта типа `Request` отдельно получать и путь запроса, и параметры из Query String. -Поскольку вы - главный архитектор и проектировщик данного небольшого класса, то все архитектурные решения принимать вам, но будьте готовы к критике со стороны проверяющих. +Например, это можно сделать в виде метода `getQueryParam(String name)` и `getQueryParams()`. Подумайте, что они должны возвращать, исходя из документации на утилитный класс. + +### Задача + +1. Подключите к своему проекту HttpClient +1. Реализуйте функциональность по обработке параметров из Query +1. При необходимости, доработайте функциональность поиска handler'а так, чтобы учитывался только путь без Query: т.е. handler, зарегистрированный на "/messages", обрабатывал и запросы "/messages?last=10" ### Результат -В качестве результата пришлите ссылку на ваш проект на GitHub в личном кабинете студента на сайте [netology.ru](https://netology.ru). +Реализуйте новую функциональность в ветке `feature/query` вашего репозитория из ДЗ 1 и откройте Pull Request. + +Поскольку вы - главный архитектор и проектировщик данного решения (уже более функционального), то все архитектурные решения принимать вам, но будьте готовы к критике со стороны проверяющих. + +В качестве результата пришлите ссылку на ваш Pull Request на GitHub в личном кабинете студента на сайте [netology.ru](https://netology.ru). + +После того, как ДЗ будет принято, сделайте `merge` для Pull Request'а. -## Handlers* +## x-www-form-urlencoded* (задача со звёздочкой) -**Важно**: это необязательная задача, её выполнение не влияет на получение зачёта. +**Важно**: выполнение данного ДЗ не влияет на получение зачёта по ДЗ. ### Легенда -Сервер, который вы написали в предыдущей задаче, - это, конечно, здорово, но пока он не расширяем и его нельзя переиспользовать, т.к. код обработки зашит прямо внутрь сервера. +Необходимо добавить в ваш сервер (из предыдущего ДЗ) функциональность обработки тела, оформленного в виде `x-www-form-url-encoded`, запроса так, чтобы можно было из объекта типа `Request` отдельно параметры, переданные в теле запроса. -Давайте попробуем его сделать немного полезнее. +Например, это можно сделать в виде метода `getPostParam(String name)` и `getPostParams()`. Подумайте, что они должны возвращать, исходя из того, что в передаваемой вам форме может быть два параметра с одинаковым именем. -Что хотим сделать? Мы хотим сделать так, чтобы в сервер можно было добавлять обработчики на определённые шаблоны путей. +### Задача -Что это значит? Мы хотим, чтобы можно было сделать вот так: +Реализуйте функциональность по обработке тела запроса, если он представлен в виде `x-www-form-urlencoded`. -```java -public class Main { - public static void main(String[] args){ - final var server = new Server(); - // код инициализации сервера (из вашего предыдущего ДЗ) +### Результат - // добавление handler'ов (обработчиков) - server.addHandler("GET", "/messages", new Handler() { - public void handle(Request request, BufferedOutputStream responseStream) { - // TODO: handlers code - } - }); - server.addHandler("POST", "/messages", new Handler() { - public void handle(Request request, BufferedOutputStream responseStream) { - // TODO: handlers code - } - }); +Реализуйте новую функциональность в ветке `feature/form` вашего репозитория из предыдущего ДЗ и откройте Pull Request. - server.listen(9999); - } -} -``` +Поскольку вы - главный архитектор и проектировщик данного решения (уже более функционального), то все архитектурные решения принимать вам, но будьте готовы к критике со стороны проверяющих. -В итоге на запрос типа GET на путь "/messages" будет вызван первый обработчик, на запрос типа POST и путь "/messages" будет вызван второй. +В качестве результата пришлите ссылку на ваш Pull Request на GitHub в личном кабинете студента на сайте [netology.ru](https://netology.ru). -Как вы видите - `Handler` из себя представляет функциональный интерфейс всего с одним методом (может быть заменён на lambda). +После того, как ДЗ будет принято, сделайте `merge` для Pull Request'а. -`Request` - это класс, который проектируете вы сами, для нас важно, чтобы он содержал: -1. Метод запроса (потому что на разные методы можно назначить один и тот же Handler) -1. Заголовки запроса -1. Тело запроса (если есть) +## multipart/form-data* (задача со звёздочкой) -`BufferedOutputStream` берётся просто путём заворачивания `OutputStream`'а `socket`'а: `new BufferedOutputStream(socket.getOutputStream())`. +**Важно**: выполнение данного ДЗ не влияет на получение зачёта по ДЗ. -### Задача +### Легенда + +Почему бы не реализовать "полную" функциональность и не добавить поддержку multipart-запросов? Используйте библиотеку [FileUpload](http://commons.apache.org/proper/commons-fileupload/), чтобы добавить подобную функциональность. -Реализуйте требования, указанные в легенде. +Например, это можно сделать в виде метода `getPart(String name)` и `getParts()`. Подумайте, что они должны возвращать, исходя из того, что: +1. В передаваемой вам форме может быть два параметра с одинаковым именем +1. Каждый `Part` может быть как файлом, так и обычным полем -
-Подсказки по реализации +Всё это значит, что нужно как-то уметь отличать обычные поля от файлов. -1. Вы принимаете запрос, парсите его целиком (как мы сделали на лекции) и собираете объект типа `Request` -1. На основании данных из `Request` вы выбираете handler (он может быть только один), который и будет обрабатывать запрос -1. Все handler'ы должны храниться в полях `Server`'а -1. Самый простой способ хранить handler'ы - это использовать в качестве ключей метод и путь (можно как сделать `Map` внутри `Map`, так и отдельные `Map`'ы на каждый метод) -1. Поиск хендлера заключается в том, что вы выбираете по нужному методу все зарегистрированные handler'ы, а затем перебираете по пути (используйте пока точное соответствие: считайте, что у вас все запросы без Query String) -1. Найдя нужный handler - достаточно вызвать его метод `handle`, передав туда `Request` и `BufferedOutputStream` -1. Поскольку ваш сервер многопоточный - думайте о том, как вы будете безопасно хранить handler'ы -1. В качестве Body достаточно передавать `InputStream` (напоминаем, Body начинается после `\r\n\r\n` +### Задача -Итого: фактически вы решаете задачу поиска элемента в "коллекции" с вызовом его метода. -
+Реализуйте функциональность по обработке тела запроса, если от представлен в виде `multipart/form-data`. + +Поскольку вы - главный архитектор и проектировщик данного небольшого класса, то все архитектурные решения принимать вам, но будьте готовы к критике со стороны проверяющих. ### Результат -Реализуйте новую функциональность в ветке `feature/handlers` вашего репозитория из ДЗ 1 и откройте Pull Request. +Реализуйте новую функциональность в ветке `feature/multipart` вашего репозитория из предыдущего ДЗ и откройте Pull Request. Поскольку вы - главный архитектор и проектировщик данного решения (уже более функционального), то все архитектурные решения принимать вам, но будьте готовы к критике со стороны проверяющих. From ad89227920b7127b80fa4bf84938af4e1a9d6cc2 Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Sat, 8 Oct 2022 20:51:39 +0300 Subject: [PATCH 2/8] org.apache.httpcomponents.client5 --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 186b774..14db4bf 100644 --- a/pom.xml +++ b/pom.xml @@ -13,4 +13,12 @@ 11 + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.1.3 + + \ No newline at end of file From 4f969af74c44566eece4ef3c16e0ce7251628fe0 Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Wed, 12 Oct 2022 20:13:30 +0300 Subject: [PATCH 3/8] org.apache.httpcomponents --- pom.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 14db4bf..79576b1 100644 --- a/pom.xml +++ b/pom.xml @@ -14,11 +14,12 @@ - + - org.apache.httpcomponents.client5 - httpclient5 - 5.1.3 + org.apache.httpcomponents + httpclient + 4.5.13 + \ No newline at end of file From 7fc353fda331bf85aa79c8f269028220c48316b9 Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Fri, 14 Oct 2022 19:40:59 +0300 Subject: [PATCH 4/8] Server and Request refactor --- src/main/java/Request.java | 84 ++++++++++++++++++++++++++++++++++++++ src/main/java/Server.java | 42 ++++++------------- 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/src/main/java/Request.java b/src/main/java/Request.java index 1a6281d..63a8f8b 100644 --- a/src/main/java/Request.java +++ b/src/main/java/Request.java @@ -1,15 +1,35 @@ +import java.io.BufferedInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + /** + * Represents HTTP request received from a server. + * * @author Stanislav Rakitov + * @version 1.2 */ public class Request { private final String method; private final String path; + private final List headers; +// private byte[] body; + + public static final String GET = "GET"; + public static final String POST = "POST"; public Request(String requestMethod, String requestPath) { this.method = requestMethod; this.path = requestPath; + headers = null; + } + + public Request(String method, String path, List headers) { + this.method = method; + this.path = path; + this.headers = headers; } public String getMethod() { @@ -20,4 +40,68 @@ public String getPath() { return path; } + static Request createRequest(BufferedInputStream in) throws IOException { + final List allowedMethods = List.of(GET, POST); + + final var limit = 4096; + in.mark(limit); + final var buffer = new byte[limit]; + final var read = in.read(buffer); + + // ищем request line + final var requestLineDelimiter = new byte[]{'\r', '\n'}; + final var requestLineEnd = indexOf(buffer, requestLineDelimiter, 0, read); + if (requestLineEnd == -1) { + return null; + } + + // читаем request line + final var requestLine = new String(Arrays.copyOf(buffer, requestLineEnd)).split(" "); + if (requestLine.length != 3) { + return null; + } + + final var method = requestLine[0]; + if (!allowedMethods.contains(method)) { + return null; + } + System.out.println(method); + + final var path = requestLine[1]; + System.out.println(path); + + // ищем заголовки + final var headersDelimiter = new byte[]{'\r', '\n', '\r', '\n'}; + final var headersStart = requestLineEnd + requestLineDelimiter.length; + final var headersEnd = indexOf(buffer, headersDelimiter, headersStart, read); + if (headersEnd == -1) { + return null; + } + + // отматываем на начало буфера + in.reset(); + // пропускаем requestLine + in.skip(headersStart); + + final var headersBytes = in.readNBytes(headersEnd - headersStart); + List headers = Arrays.asList(new String(headersBytes).split("\r\n")); + System.out.println(headers); + + + return new Request(method, path, headers); + } + + // from Google guava with modifications + private static int indexOf(byte[] array, byte[] target, int start, int max) { + outer: + for (int i = start; i < max - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } } diff --git a/src/main/java/Server.java b/src/main/java/Server.java index 9d59230..770593c 100644 --- a/src/main/java/Server.java +++ b/src/main/java/Server.java @@ -1,7 +1,6 @@ +import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.nio.file.Files; @@ -14,6 +13,12 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +/** + * simple http Server + * + * @author Stanislav Rakitov + * @version 1.2 + */ public class Server { private final int SERVER_SOCKET; @@ -44,23 +49,11 @@ void start() { } private void proceedConnection(Socket socket) { - try (final var in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - final var out = new BufferedOutputStream(socket.getOutputStream())) { + try (final var in = new BufferedInputStream(socket.getInputStream()); + final var out = new BufferedOutputStream(socket.getOutputStream()) + ) { - // read only request line for simplicity - // must be in form GET /path HTTP/1.1 - final var requestLine = in.readLine(); - final var parts = requestLine.split(" "); - - if (parts.length != 3) { - // just close socket - socket.close(); - return; - } - - String method = parts[0]; - final var path = parts[1]; - Request request = createRequest(method, path); + Request request = Request.createRequest(in); // Check for bad requests and drop connection if (request == null || !handlers.containsKey(request.getMethod())) { @@ -79,7 +72,7 @@ private void proceedConnection(Socket socket) { if (!validPaths.contains(request.getPath())) { responseWithoutContent(out, "404", "Not Found"); } else { - defaultHandler(out, path); + defaultHandler(out, request.getPath()); } } @@ -123,16 +116,6 @@ void defaultHandler(BufferedOutputStream out, String path) throws IOException { out.flush(); } - private Request createRequest(String method, String path) { - // TODO: More checks for bad fields - if (method != null && !method.isBlank()) { - return new Request(method, path); - } else { - return null; - } - - } - void addHandler(String method, String path, Handler handler) { if (!handlers.containsKey(method)) { handlers.put(method, new HashMap<>()); @@ -149,6 +132,7 @@ void responseWithoutContent(BufferedOutputStream out, String responseCode, Strin ).getBytes()); out.flush(); } + } From ce8dc94704c07f8006f6c674562ba05629dac178 Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Fri, 14 Oct 2022 20:59:36 +0300 Subject: [PATCH 5/8] query --- src/main/java/Main.java | 3 ++- src/main/java/Request.java | 32 ++++++++++++++++++++++++++------ src/main/java/Server.java | 11 ++++++----- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/Main.java b/src/main/java/Main.java index c47ddde..2fe1f2c 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -14,7 +14,7 @@ public static void main(String[] args) { // добавление handler'ов (обработчиков) server.addHandler("GET", "/messages", (request, responseStream) -> { try { - server.responseWithoutContent(responseStream, "404", "Not Found"); + server.responseWithoutContent(responseStream, "200", "OK"); } catch (IOException e) { throw new RuntimeException(e); } @@ -24,6 +24,7 @@ public static void main(String[] args) { server.addHandler("GET", "/", ((request, outputStream) -> server.defaultHandler(outputStream, "index.html"))); + // Start server.start(); } diff --git a/src/main/java/Request.java b/src/main/java/Request.java index 63a8f8b..a320076 100644 --- a/src/main/java/Request.java +++ b/src/main/java/Request.java @@ -1,7 +1,14 @@ +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; + import java.io.BufferedInputStream; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; /** * Represents HTTP request received from a server. @@ -18,6 +25,7 @@ public class Request { public static final String GET = "GET"; public static final String POST = "POST"; + private List params; public Request(String requestMethod, String requestPath) { @@ -26,7 +34,7 @@ public Request(String requestMethod, String requestPath) { headers = null; } - public Request(String method, String path, List headers) { + public Request(String method, String path, List headers, List params) { this.method = method; this.path = path; this.headers = headers; @@ -40,7 +48,7 @@ public String getPath() { return path; } - static Request createRequest(BufferedInputStream in) throws IOException { + static Request createRequest(BufferedInputStream in) throws IOException, URISyntaxException { final List allowedMethods = List.of(GET, POST); final var limit = 4096; @@ -65,10 +73,10 @@ static Request createRequest(BufferedInputStream in) throws IOException { if (!allowedMethods.contains(method)) { return null; } - System.out.println(method); + System.out.println("METHOD: " + method); final var path = requestLine[1]; - System.out.println(path); + System.out.println("PATH: " + path); // ищем заголовки final var headersDelimiter = new byte[]{'\r', '\n', '\r', '\n'}; @@ -85,10 +93,12 @@ static Request createRequest(BufferedInputStream in) throws IOException { final var headersBytes = in.readNBytes(headersEnd - headersStart); List headers = Arrays.asList(new String(headersBytes).split("\r\n")); - System.out.println(headers); +// System.out.println(headers); + List params = URLEncodedUtils.parse(new URI(path), StandardCharsets.UTF_8); + System.out.println(params); - return new Request(method, path, headers); + return new Request(method, path, headers, params); } // from Google guava with modifications @@ -104,4 +114,14 @@ private static int indexOf(byte[] array, byte[] target, int start, int max) { } return -1; } + + public List getQueryParam(String name) { + return params.stream().filter(p -> name.equals(p.getName())).collect(Collectors.toList()); + } + + public List getQueryParams() { + return params; + } + + } diff --git a/src/main/java/Server.java b/src/main/java/Server.java index 770593c..92aa584 100644 --- a/src/main/java/Server.java +++ b/src/main/java/Server.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; @@ -63,20 +64,20 @@ private void proceedConnection(Socket socket) { // Get PATH, HANDLER Map Map handlerMap = handlers.get(request.getMethod()); - String requestPath = request.getPath(); + String requestPath = request.getPath().split("\\?")[0]; if (handlerMap.containsKey(requestPath)) { Handler handler = handlerMap.get(requestPath); handler.handle(request, out); } else { // Defaults // Resource not found - if (!validPaths.contains(request.getPath())) { + if (!validPaths.contains(requestPath)) { responseWithoutContent(out, "404", "Not Found"); } else { - defaultHandler(out, request.getPath()); + defaultHandler(out, requestPath); } } - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { throw new RuntimeException(e); } } @@ -86,7 +87,7 @@ void defaultHandler(BufferedOutputStream out, String path) throws IOException { final var mimeType = Files.probeContentType(filePath); // special case for classic - if (path.equals("/classic.html")) { + if (path.startsWith("/classic.html")) { final var template = Files.readString(filePath); final var content = template.replace( "{time}", From a9c60299e7b47928f238ed893e4c9b235b45acf9 Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Sun, 16 Oct 2022 14:22:25 +0300 Subject: [PATCH 6/8] Request refactor --- src/main/java/Request.java | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/Request.java b/src/main/java/Request.java index a320076..11b0f24 100644 --- a/src/main/java/Request.java +++ b/src/main/java/Request.java @@ -8,13 +8,12 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; /** * Represents HTTP request received from a server. * * @author Stanislav Rakitov - * @version 1.2 + * @version 1.2.1 */ public class Request { @@ -38,6 +37,7 @@ public Request(String method, String path, List headers, List headers = Arrays.asList(new String(headersBytes).split("\r\n")); -// System.out.println(headers); List params = URLEncodedUtils.parse(new URI(path), StandardCharsets.UTF_8); - System.out.println(params); return new Request(method, path, headers, params); } @@ -115,13 +111,27 @@ private static int indexOf(byte[] array, byte[] target, int start, int max) { return -1; } - public List getQueryParam(String name) { - return params.stream().filter(p -> name.equals(p.getName())).collect(Collectors.toList()); + public NameValuePair getQueryParam(String name) { + return getQueryParams().stream() + .filter(param -> param.getName().equalsIgnoreCase(name)) + .findFirst().orElse(new NameValuePair() { + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return ""; + } + }); } public List getQueryParams() { return params; } - + public List getHeaders() { + return headers; + } } From fd65b8347325d8339a67809f4820dd104dae9535 Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Sun, 16 Oct 2022 14:30:12 +0300 Subject: [PATCH 7/8] adds printout debug information for Request object --- src/main/java/Server.java | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/Server.java b/src/main/java/Server.java index 92aa584..5b64401 100644 --- a/src/main/java/Server.java +++ b/src/main/java/Server.java @@ -18,7 +18,7 @@ * simple http Server * * @author Stanislav Rakitov - * @version 1.2 + * @version 1.2.1 */ public class Server { @@ -55,13 +55,16 @@ private void proceedConnection(Socket socket) { ) { Request request = Request.createRequest(in); - // Check for bad requests and drop connection if (request == null || !handlers.containsKey(request.getMethod())) { responseWithoutContent(out, "400", "Bad Request"); return; + } else { + // Print out debug info for request + printRequestDebug(request); } + // Get PATH, HANDLER Map Map handlerMap = handlers.get(request.getMethod()); String requestPath = request.getPath().split("\\?")[0]; @@ -82,6 +85,27 @@ private void proceedConnection(Socket socket) { } } + /** + * Printout debug information for Request object + * + * @param request Request to printout + */ + private static void printRequestDebug(Request request) { + System.out.println("Request debug information: "); + System.out.println("METHOD" + request.getMethod()); + System.out.println("PATH" + request.getPath()); + System.out.println("HEADERS" + request.getHeaders()); + System.out.println("Query Params:"); + for (var para : request.getQueryParams()) { + System.out.println(para.getName() + " = " + para.getValue()); + } + + System.out.println("Test for dumb param name:"); + System.out.println(request.getQueryParam("YetAnotherDumb").getName()); + System.out.println("Test for dumb param name-value:"); + System.out.println(request.getQueryParam("testDebugInfo").getValue()); + } + void defaultHandler(BufferedOutputStream out, String path) throws IOException { final var filePath = Path.of(".", "public", path); final var mimeType = Files.probeContentType(filePath); From 44f29276bc4e1d3fa364b427a1a3d1ef462ae328 Mon Sep 17 00:00:00 2001 From: Stanislav Rakitov Date: Sun, 16 Oct 2022 14:36:07 +0300 Subject: [PATCH 8/8] adds printout debug information for Request object --- src/main/java/Server.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/Server.java b/src/main/java/Server.java index 5b64401..06b872d 100644 --- a/src/main/java/Server.java +++ b/src/main/java/Server.java @@ -90,11 +90,11 @@ private void proceedConnection(Socket socket) { * * @param request Request to printout */ - private static void printRequestDebug(Request request) { + private void printRequestDebug(Request request) { System.out.println("Request debug information: "); - System.out.println("METHOD" + request.getMethod()); - System.out.println("PATH" + request.getPath()); - System.out.println("HEADERS" + request.getHeaders()); + System.out.println("METHOD: " + request.getMethod()); + System.out.println("PATH: " + request.getPath()); + System.out.println("HEADERS: " + request.getHeaders()); System.out.println("Query Params:"); for (var para : request.getQueryParams()) { System.out.println(para.getName() + " = " + para.getValue());