From 6922e1ffe06c56ab42e8ca357449e07e6ede3703 Mon Sep 17 00:00:00 2001 From: NF-coder Date: Sun, 14 Sep 2025 23:31:10 +0300 Subject: [PATCH 01/10] java-fcgi: small naming improvements + simple validator --- .../src/main/java/org/web1/DTOs/CordsDTO.java | 3 +++ server/src/main/java/org/web1/Main.java | 11 +++++++---- .../web1/{utils => netUtils}/JsonBuilder.java | 2 +- .../QueryStringToHashmap.java | 2 +- .../ResponseFactory.java} | 4 ++-- .../web1/validators/QueryHmapValidator.java | 18 ++++++++++++++++++ 6 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 server/src/main/java/org/web1/DTOs/CordsDTO.java rename server/src/main/java/org/web1/{utils => netUtils}/JsonBuilder.java (95%) rename server/src/main/java/org/web1/{utils => netUtils}/QueryStringToHashmap.java (96%) rename server/src/main/java/org/web1/{utils/ResponseController.java => netUtils/ResponseFactory.java} (87%) create mode 100644 server/src/main/java/org/web1/validators/QueryHmapValidator.java diff --git a/server/src/main/java/org/web1/DTOs/CordsDTO.java b/server/src/main/java/org/web1/DTOs/CordsDTO.java new file mode 100644 index 0000000..4c3d8c6 --- /dev/null +++ b/server/src/main/java/org/web1/DTOs/CordsDTO.java @@ -0,0 +1,3 @@ +package org.web1.DTOs; + +public record CordsDTO(int x, float y, float r) {} diff --git a/server/src/main/java/org/web1/Main.java b/server/src/main/java/org/web1/Main.java index a5181cc..99a1a82 100644 --- a/server/src/main/java/org/web1/Main.java +++ b/server/src/main/java/org/web1/Main.java @@ -6,10 +6,11 @@ import org.web1.checkers.Checker; import org.web1.checkers.CheckerFunction; -import org.web1.utils.JsonBuilder; -import org.web1.utils.QueryStringToHashmap; -import org.web1.utils.ResponseController; +import org.web1.netUtils.JsonBuilder; +import org.web1.netUtils.QueryStringToHashmap; +import org.web1.netUtils.ResponseFactory; import org.web1.utils.Timer; +import org.web1.validators.QueryHmapValidator; public class Main { private static final Function> parseQuery = new QueryStringToHashmap(); @@ -25,9 +26,11 @@ public static void main(String[] args) { HashMap queryParams = parseQuery.apply( FCGIInterface.request.params.getProperty("QUERY_STRING") ); + new QueryHmapValidator().apply(queryParams); + boolean checkResult = checker.test(queryParams); - String result = ResponseController.create( + String result = ResponseFactory.create( new JsonBuilder() .add("result", checkResult) .add("elapsedTimeNs", timer.stop()) diff --git a/server/src/main/java/org/web1/utils/JsonBuilder.java b/server/src/main/java/org/web1/netUtils/JsonBuilder.java similarity index 95% rename from server/src/main/java/org/web1/utils/JsonBuilder.java rename to server/src/main/java/org/web1/netUtils/JsonBuilder.java index c87b3a2..809ae58 100644 --- a/server/src/main/java/org/web1/utils/JsonBuilder.java +++ b/server/src/main/java/org/web1/netUtils/JsonBuilder.java @@ -1,4 +1,4 @@ -package org.web1.utils; +package org.web1.netUtils; import java.util.HashMap; diff --git a/server/src/main/java/org/web1/utils/QueryStringToHashmap.java b/server/src/main/java/org/web1/netUtils/QueryStringToHashmap.java similarity index 96% rename from server/src/main/java/org/web1/utils/QueryStringToHashmap.java rename to server/src/main/java/org/web1/netUtils/QueryStringToHashmap.java index da22d77..2f775e9 100644 --- a/server/src/main/java/org/web1/utils/QueryStringToHashmap.java +++ b/server/src/main/java/org/web1/netUtils/QueryStringToHashmap.java @@ -1,4 +1,4 @@ -package org.web1.utils; +package org.web1.netUtils; import java.util.HashMap; import java.util.function.Function; diff --git a/server/src/main/java/org/web1/utils/ResponseController.java b/server/src/main/java/org/web1/netUtils/ResponseFactory.java similarity index 87% rename from server/src/main/java/org/web1/utils/ResponseController.java rename to server/src/main/java/org/web1/netUtils/ResponseFactory.java index 8032608..1b9a7f2 100644 --- a/server/src/main/java/org/web1/utils/ResponseController.java +++ b/server/src/main/java/org/web1/netUtils/ResponseFactory.java @@ -1,6 +1,6 @@ -package org.web1.utils; +package org.web1.netUtils; -public class ResponseController { +public class ResponseFactory { private static final String BASE_RESPONSE = """ Access-Control-Allow-Origin: * Connection: keep-alive diff --git a/server/src/main/java/org/web1/validators/QueryHmapValidator.java b/server/src/main/java/org/web1/validators/QueryHmapValidator.java new file mode 100644 index 0000000..522f7cc --- /dev/null +++ b/server/src/main/java/org/web1/validators/QueryHmapValidator.java @@ -0,0 +1,18 @@ +package org.web1.validators; + +import java.util.HashMap; +import java.util.function.Function; + +public class QueryHmapValidator implements Function, Boolean> { + public Boolean apply(HashMap data) { + try { + Integer.parseInt(data.get("x")); + Float.parseFloat(data.get("y")); + Float.parseFloat(data.get("r")); + + return true; + } catch (NumberFormatException | NullPointerException e) { + return false; + } + } +} From 2a009fc79d62401884fdc6c96cbbd36ad2c2b390 Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 00:16:50 +0300 Subject: [PATCH 02/10] nginx: minimal config for fastcgi and staticfiles support --- docker-compose.yaml | 23 +++++++++++++++---- httpd/{Dockerfile => Dockerfile-apache} | 4 ++-- httpd/Dockerfile-nginx | 7 ++++++ .../custom/apache-httpd-lab1.conf | 0 httpd/{conf => apache-conf}/httpd.conf | 0 httpd/nginx-conf/httpd.conf | 18 +++++++++++++++ server/.idea/misc.xml | 9 +------- 7 files changed, 46 insertions(+), 15 deletions(-) rename httpd/{Dockerfile => Dockerfile-apache} (77%) create mode 100644 httpd/Dockerfile-nginx rename httpd/{conf => apache-conf}/custom/apache-httpd-lab1.conf (100%) rename httpd/{conf => apache-conf}/httpd.conf (100%) create mode 100644 httpd/nginx-conf/httpd.conf diff --git a/docker-compose.yaml b/docker-compose.yaml index d3e538d..645128f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,10 +1,22 @@ services: - apache-httpd: - container_name: httpd-server +# apache-httpd: +# container_name: httpd-server-apache +# build: +# context: ./httpd/ +# dockerfile: ./Dockerfile-apache +# args: +# - APACHE_LOG_DIR="/var/log/httpd-server/" +# restart: on-failure +# ports: +# - "8080:80" +# networks: +# - internal_net + + nginx-httpd: + container_name: httpd-server-nginx build: context: ./httpd/ - args: - - APACHE_LOG_DIR="/var/log/httpd-server/" + dockerfile: ./Dockerfile-nginx restart: on-failure ports: - "8080:80" @@ -17,7 +29,8 @@ services: context: ./server/ restart: on-failure depends_on: - - apache-httpd + # - apache-httpd + - nginx-httpd networks: - internal_net diff --git a/httpd/Dockerfile b/httpd/Dockerfile-apache similarity index 77% rename from httpd/Dockerfile rename to httpd/Dockerfile-apache index 555b51c..0319d6b 100644 --- a/httpd/Dockerfile +++ b/httpd/Dockerfile-apache @@ -6,7 +6,7 @@ COPY ./static/ /var/www/localhost/htdocs/static/ # add custom apache configs RUN mkdir -p /etc/apache2/conf-custom -COPY ./conf/custom/ /etc/apache2/conf-custom/ +COPY ./apache-conf/custom/ /etc/apache2/conf-custom/ # create logs directory (temporary disabled) ARG APACHE_LOG_DIR @@ -14,6 +14,6 @@ RUN mkdir -p ${APACHE_LOG_DIR} RUN chown www-data:www-data ${APACHE_LOG_DIR} # setup main apache configuration -COPY ./conf/httpd.conf /usr/local/apache2/conf/httpd.conf +COPY ./apache-conf/httpd.conf /usr/local/apache2/conf/httpd.conf EXPOSE 80 \ No newline at end of file diff --git a/httpd/Dockerfile-nginx b/httpd/Dockerfile-nginx new file mode 100644 index 0000000..0f7508b --- /dev/null +++ b/httpd/Dockerfile-nginx @@ -0,0 +1,7 @@ +FROM nginx:latest + +COPY /nginx-conf/httpd.conf /etc/nginx/conf.d/default.conf +COPY ./static/ /var/www/html/ + +EXPOSE 80 +#EXPOSE 443 \ No newline at end of file diff --git a/httpd/conf/custom/apache-httpd-lab1.conf b/httpd/apache-conf/custom/apache-httpd-lab1.conf similarity index 100% rename from httpd/conf/custom/apache-httpd-lab1.conf rename to httpd/apache-conf/custom/apache-httpd-lab1.conf diff --git a/httpd/conf/httpd.conf b/httpd/apache-conf/httpd.conf similarity index 100% rename from httpd/conf/httpd.conf rename to httpd/apache-conf/httpd.conf diff --git a/httpd/nginx-conf/httpd.conf b/httpd/nginx-conf/httpd.conf new file mode 100644 index 0000000..c1f6636 --- /dev/null +++ b/httpd/nginx-conf/httpd.conf @@ -0,0 +1,18 @@ +server { + listen 80; + + root /var/www/html; + + # Обработка статических файлов + location / { + try_files $uri $uri/ =404; + } + + location /fcgi-bin/ { + # Базовые настройки FastCGI + include fastcgi_params; + + # Адрес FastCGI сервера + fastcgi_pass fastcgi-server:55555; + } +} \ No newline at end of file diff --git a/server/.idea/misc.xml b/server/.idea/misc.xml index 82dbec8..b2eea27 100644 --- a/server/.idea/misc.xml +++ b/server/.idea/misc.xml @@ -1,14 +1,7 @@ - - - - + \ No newline at end of file From 47b3df4b1a3033ee2c49b6cc5d00cad7091652e7 Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 00:20:52 +0300 Subject: [PATCH 03/10] apache+nginx+static: /fcgi-bin/ has been moved to /fcgi/ --- httpd/apache-conf/custom/apache-httpd-lab1.conf | 6 +++--- httpd/nginx-conf/httpd.conf | 2 +- httpd/static/index.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/httpd/apache-conf/custom/apache-httpd-lab1.conf b/httpd/apache-conf/custom/apache-httpd-lab1.conf index 4b9a4d7..d0cc1e2 100644 --- a/httpd/apache-conf/custom/apache-httpd-lab1.conf +++ b/httpd/apache-conf/custom/apache-httpd-lab1.conf @@ -16,10 +16,10 @@ # FastCGI server setup start # ========================== - ProxyPass /fcgi-bin/ fcgi://fastcgi-server:55555/ - ProxyPassReverse /fcgi-bin/ fcgi://fastcgi-server:55555/ + ProxyPass /fcgi/ fcgi://fastcgi-server:55555/ + ProxyPassReverse /fcgi/ fcgi://fastcgi-server:55555/ - + Require all granted diff --git a/httpd/nginx-conf/httpd.conf b/httpd/nginx-conf/httpd.conf index c1f6636..de90b04 100644 --- a/httpd/nginx-conf/httpd.conf +++ b/httpd/nginx-conf/httpd.conf @@ -8,7 +8,7 @@ server { try_files $uri $uri/ =404; } - location /fcgi-bin/ { + location /fcgi/ { # Базовые настройки FastCGI include fastcgi_params; diff --git a/httpd/static/index.html b/httpd/static/index.html index b82fdfd..dc37d4f 100644 --- a/httpd/static/index.html +++ b/httpd/static/index.html @@ -644,7 +644,7 @@

История введенных данных

if (X && R && Y) { // Stub - sendAJAXGETRequest("/fcgi-bin/", {"x": X, "y": Y, "r": R}, (data) => {console.log(data)}); + sendAJAXGETRequest("/fcgi/", {"x": X, "y": Y, "r": R}, (data) => {console.log(data)}); console.log(`Данные отправлены!\nX: ${X}\nY: ${Y}\nR: ${R}`); // Сохраняем данные в историю From 9a41f84b5ecc7335df4c3dcbea02733bf6bc3a40 Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 00:47:21 +0300 Subject: [PATCH 04/10] validator: new validation mechanism added --- server/.idea/vcs.xml | 1 - .../main/java/org/validator/ExampleDTO.java | 7 +++ server/src/main/java/org/validator/Main.java | 15 ++++++ .../org/validator/ValidatedRecordFactory.java | 22 ++++++++ .../org/validator/annotations/NotNull.java | 12 +++++ .../org/validator/annotations/Number.java | 19 +++++++ .../org/validator/annotations/Pattern.java | 14 +++++ .../java/org/validator/annotations/Size.java | 15 ++++++ .../validation/ValidationController.java | 54 +++++++++++++++++++ .../validators/NotNullValidator.java | 12 +++++ .../validators/NumberValidator.java | 22 ++++++++ .../validators/PatternValidator.java | 14 +++++ .../validation/validators/SizeValidator.java | 20 +++++++ .../validation/validators/Validator.java | 7 +++ 14 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 server/src/main/java/org/validator/ExampleDTO.java create mode 100644 server/src/main/java/org/validator/Main.java create mode 100644 server/src/main/java/org/validator/ValidatedRecordFactory.java create mode 100644 server/src/main/java/org/validator/annotations/NotNull.java create mode 100644 server/src/main/java/org/validator/annotations/Number.java create mode 100644 server/src/main/java/org/validator/annotations/Pattern.java create mode 100644 server/src/main/java/org/validator/annotations/Size.java create mode 100644 server/src/main/java/org/validator/validation/ValidationController.java create mode 100644 server/src/main/java/org/validator/validation/validators/NotNullValidator.java create mode 100644 server/src/main/java/org/validator/validation/validators/NumberValidator.java create mode 100644 server/src/main/java/org/validator/validation/validators/PatternValidator.java create mode 100644 server/src/main/java/org/validator/validation/validators/SizeValidator.java create mode 100644 server/src/main/java/org/validator/validation/validators/Validator.java diff --git a/server/.idea/vcs.xml b/server/.idea/vcs.xml index 288b36b..6c0b863 100644 --- a/server/.idea/vcs.xml +++ b/server/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/server/src/main/java/org/validator/ExampleDTO.java b/server/src/main/java/org/validator/ExampleDTO.java new file mode 100644 index 0000000..6d55712 --- /dev/null +++ b/server/src/main/java/org/validator/ExampleDTO.java @@ -0,0 +1,7 @@ +package org.validator; + +import org.validator.annotations.Number; + +public record ExampleDTO ( + @Number(min = 4, step = 3) String textField +) {} diff --git a/server/src/main/java/org/validator/Main.java b/server/src/main/java/org/validator/Main.java new file mode 100644 index 0000000..44947e5 --- /dev/null +++ b/server/src/main/java/org/validator/Main.java @@ -0,0 +1,15 @@ +package org.validator; + +public class Main { + public static void main(String[] args) { + try { + ExampleDTO exampleDTO = ValidatedRecordFactory.create( + ExampleDTO.class, + "0" + ); + System.out.println(exampleDTO); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/ValidatedRecordFactory.java b/server/src/main/java/org/validator/ValidatedRecordFactory.java new file mode 100644 index 0000000..3d3d75e --- /dev/null +++ b/server/src/main/java/org/validator/ValidatedRecordFactory.java @@ -0,0 +1,22 @@ +package org.validator; + +import org.validator.validation.ValidationController; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class ValidatedRecordFactory { + @SuppressWarnings("unchecked") + public static T create(Class recordClass, Object... args) { + try { + Constructor constructor = (Constructor) recordClass.getDeclaredConstructors()[0]; + T record = constructor.newInstance(args); + + ValidationController.validateObject(record); + + return record; + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Record can't be created", e); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/annotations/NotNull.java b/server/src/main/java/org/validator/annotations/NotNull.java new file mode 100644 index 0000000..731af81 --- /dev/null +++ b/server/src/main/java/org/validator/annotations/NotNull.java @@ -0,0 +1,12 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.ANNOTATION_TYPE}) +public @interface NotNull { + String errorMsg() default "Field required"; +} diff --git a/server/src/main/java/org/validator/annotations/Number.java b/server/src/main/java/org/validator/annotations/Number.java new file mode 100644 index 0000000..e92023f --- /dev/null +++ b/server/src/main/java/org/validator/annotations/Number.java @@ -0,0 +1,19 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) +@NotNull(errorMsg = "Number field is required") +@Size(min = 1, errorMsg = "Number must not be empty") +@Pattern(pattern = "^-?\\d+(\\.\\d+)?$", errorMsg = "Must be a valid number") +public @interface Number { + String rangeErrorMsg() default "Number must be between %s and %s"; + String stepErrorMsg() default "Number distance from minimum must be divisible by %s"; + double min() default Integer.MIN_VALUE; + double max() default Integer.MAX_VALUE; + double step() default 0; +} diff --git a/server/src/main/java/org/validator/annotations/Pattern.java b/server/src/main/java/org/validator/annotations/Pattern.java new file mode 100644 index 0000000..ca18e9c --- /dev/null +++ b/server/src/main/java/org/validator/annotations/Pattern.java @@ -0,0 +1,14 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.ANNOTATION_TYPE}) +@NotNull +public @interface Pattern { + String errorMsg() default "Pattern mismatch"; + String pattern(); +} diff --git a/server/src/main/java/org/validator/annotations/Size.java b/server/src/main/java/org/validator/annotations/Size.java new file mode 100644 index 0000000..e923d5b --- /dev/null +++ b/server/src/main/java/org/validator/annotations/Size.java @@ -0,0 +1,15 @@ +package org.validator.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.ANNOTATION_TYPE}) +@NotNull +public @interface Size { + String errorMsg() default "String size must be between %d and %d"; + int min() default 0; + long max() default Integer.MAX_VALUE; +} diff --git a/server/src/main/java/org/validator/validation/ValidationController.java b/server/src/main/java/org/validator/validation/ValidationController.java new file mode 100644 index 0000000..392e6f5 --- /dev/null +++ b/server/src/main/java/org/validator/validation/ValidationController.java @@ -0,0 +1,54 @@ +package org.validator.validation; + +import org.validator.annotations.*; +import org.validator.annotations.Number; +import org.validator.validation.validators.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.HashMap; + +public class ValidationController { + + private static final HashMap, Validator> validatorCache = new HashMap<>(); + + static { + validatorCache.put(NotNull.class, new NotNullValidator()); + validatorCache.put(Size.class, new SizeValidator()); + validatorCache.put(Pattern.class, new PatternValidator()); + validatorCache.put(Number.class, new NumberValidator()); + } + + public static void validateObject(T object) { + for (Field field : object.getClass().getDeclaredFields()) { + try { + field.setAccessible(true); + + Object value = field.get(object); + validateField(value, field.getAnnotations()); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to validate field: " + field.getName(), e); + } + } + } + + private static void validateField(Object value, T[] annotations) { + for (Annotation annotation : annotations) { + validateAnnotation(annotation, value); + } + } + + @SuppressWarnings("unchecked") + private static void validateAnnotation(Annotation annotation, Object value) { + Class annotationType = annotation.annotationType(); + + Validator validator = (Validator) validatorCache.get(annotationType); + if (validator == null) return; + + for (Annotation metaAnnotation : annotationType.getAnnotations()) validateAnnotation(metaAnnotation, value); + + validator.validate(annotation, value); + } +} + + diff --git a/server/src/main/java/org/validator/validation/validators/NotNullValidator.java b/server/src/main/java/org/validator/validation/validators/NotNullValidator.java new file mode 100644 index 0000000..690150c --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/NotNullValidator.java @@ -0,0 +1,12 @@ +package org.validator.validation.validators; + +import org.validator.annotations.NotNull; + +public class NotNullValidator implements Validator { + @Override + public void validate(NotNull annotation, Object value) { + if (value == null) { + throw new RuntimeException(annotation.errorMsg()); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/NumberValidator.java b/server/src/main/java/org/validator/validation/validators/NumberValidator.java new file mode 100644 index 0000000..01d8cf5 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/NumberValidator.java @@ -0,0 +1,22 @@ +package org.validator.validation.validators; + +import org.validator.annotations.Number; + +public class NumberValidator implements Validator { + @Override + public void validate(Number annotation, Object value) { + if (value instanceof String str) { + double num = Double.parseDouble(str); + if (num < annotation.min() || num > annotation.max()) { + throw new RuntimeException( + String.format(annotation.rangeErrorMsg(), annotation.min(), annotation.max()) + ); + } + if (annotation.step() != 0 && (num-annotation.min())%annotation.step() != 0) { + throw new RuntimeException( + String.format(annotation.stepErrorMsg(), annotation.step()) + ); + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/PatternValidator.java b/server/src/main/java/org/validator/validation/validators/PatternValidator.java new file mode 100644 index 0000000..cf73383 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/PatternValidator.java @@ -0,0 +1,14 @@ +package org.validator.validation.validators; + +import org.validator.annotations.Pattern; + +public class PatternValidator implements Validator { + @Override + public void validate(Pattern annotation, Object value) { + if (value instanceof String str) { + if (!str.matches(annotation.pattern())) { + throw new RuntimeException(annotation.errorMsg()); + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/SizeValidator.java b/server/src/main/java/org/validator/validation/validators/SizeValidator.java new file mode 100644 index 0000000..89470d4 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/SizeValidator.java @@ -0,0 +1,20 @@ +package org.validator.validation.validators; + +import org.validator.annotations.Size; + +public class SizeValidator implements Validator { + @Override + public void validate(Size annotation, Object value) { + if (value instanceof String str) { + if (str.length() < annotation.min() || str.length() > annotation.max()) { + throw new RuntimeException( + String.format( + annotation.errorMsg(), + annotation.min(), + annotation.max() + ) + ); + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/Validator.java b/server/src/main/java/org/validator/validation/validators/Validator.java new file mode 100644 index 0000000..990eb59 --- /dev/null +++ b/server/src/main/java/org/validator/validation/validators/Validator.java @@ -0,0 +1,7 @@ +package org.validator.validation.validators; +import java.lang.annotation.Annotation; + +@FunctionalInterface +public interface Validator { + void validate(A annotation, Object value); +} \ No newline at end of file From aa20c8021b06178e0cb45cc74f344884b731dece Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 01:32:36 +0300 Subject: [PATCH 05/10] =?UTF-8?q?java-fcgi:=20user=20input=20validation=20?= =?UTF-8?q?added!=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/web1/DTOs/CordsDTO.java | 3 -- .../main/java/org/web1/DTOs/RequestDTO.java | 9 ++++ server/src/main/java/org/web1/Main.java | 45 +++++++++++++------ .../main/java/org/web1/checkers/Checker.java | 6 +-- .../org/web1/checkers/CheckerFunction.java | 2 +- .../java/org/web1/{utils => timer}/Timer.java | 2 +- .../web1/validators/QueryHmapValidator.java | 18 -------- 7 files changed, 44 insertions(+), 41 deletions(-) delete mode 100644 server/src/main/java/org/web1/DTOs/CordsDTO.java create mode 100644 server/src/main/java/org/web1/DTOs/RequestDTO.java rename server/src/main/java/org/web1/{utils => timer}/Timer.java (89%) delete mode 100644 server/src/main/java/org/web1/validators/QueryHmapValidator.java diff --git a/server/src/main/java/org/web1/DTOs/CordsDTO.java b/server/src/main/java/org/web1/DTOs/CordsDTO.java deleted file mode 100644 index 4c3d8c6..0000000 --- a/server/src/main/java/org/web1/DTOs/CordsDTO.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.web1.DTOs; - -public record CordsDTO(int x, float y, float r) {} diff --git a/server/src/main/java/org/web1/DTOs/RequestDTO.java b/server/src/main/java/org/web1/DTOs/RequestDTO.java new file mode 100644 index 0000000..d811ccd --- /dev/null +++ b/server/src/main/java/org/web1/DTOs/RequestDTO.java @@ -0,0 +1,9 @@ +package org.web1.DTOs; + +import org.validator.annotations.Number; + +public record RequestDTO( + @Number(min = -4, max = 4, step = 1) String X, + @Number(min = 1, max = 3, step = 0.5) String R, + @Number(min = -3, max = 5) String Y +) {} diff --git a/server/src/main/java/org/web1/Main.java b/server/src/main/java/org/web1/Main.java index 99a1a82..dee1f71 100644 --- a/server/src/main/java/org/web1/Main.java +++ b/server/src/main/java/org/web1/Main.java @@ -4,13 +4,15 @@ import java.util.HashMap; import java.util.function.Function; +import org.validator.ExampleDTO; +import org.validator.ValidatedRecordFactory; +import org.web1.DTOs.RequestDTO; import org.web1.checkers.Checker; import org.web1.checkers.CheckerFunction; import org.web1.netUtils.JsonBuilder; import org.web1.netUtils.QueryStringToHashmap; import org.web1.netUtils.ResponseFactory; -import org.web1.utils.Timer; -import org.web1.validators.QueryHmapValidator; +import org.web1.timer.Timer; public class Main { private static final Function> parseQuery = new QueryStringToHashmap(); @@ -23,20 +25,37 @@ public static void main(String[] args) { while (fastCGI.FCGIaccept() >= 0) { timer.start(); - HashMap queryParams = parseQuery.apply( + final HashMap queryParams = parseQuery.apply( FCGIInterface.request.params.getProperty("QUERY_STRING") ); - new QueryHmapValidator().apply(queryParams); - boolean checkResult = checker.test(queryParams); - - String result = ResponseFactory.create( - new JsonBuilder() - .add("result", checkResult) - .add("elapsedTimeNs", timer.stop()) - ); - - System.out.println(result); + try { + RequestDTO requestData = ValidatedRecordFactory.create( + RequestDTO.class, + queryParams.get("x"), + queryParams.get("r"), + queryParams.get("y") + ); + + boolean checkResult = checker.test( + Integer.parseInt(requestData.X()), + Float.parseFloat(requestData.Y()), + Float.parseFloat(requestData.R()) + ); + + String result = ResponseFactory.create( + new JsonBuilder() + .add("result", checkResult) + .add("elapsedTimeNs", timer.stop()) + ); + + System.out.println(result); + } catch (Exception e) { + String result = ResponseFactory.create( + new JsonBuilder().add("error", '"'+e.getMessage()+'"') + ); + System.out.println(result); + } } } } \ No newline at end of file diff --git a/server/src/main/java/org/web1/checkers/Checker.java b/server/src/main/java/org/web1/checkers/Checker.java index 6885c88..c65bdd8 100644 --- a/server/src/main/java/org/web1/checkers/Checker.java +++ b/server/src/main/java/org/web1/checkers/Checker.java @@ -5,11 +5,7 @@ import java.util.HashMap; public class Checker implements CheckerFunction{ - public boolean test(HashMap data) { - int x = Integer.parseInt(data.get("x")); - float y = Float.parseFloat(data.get("y")); - float r = Float.parseFloat(data.get("r")); - + public boolean test(int x, float y, float r) { final GraphQuarters quarter = GraphUtils.getQuarter(x, y); if (quarter == GraphQuarters.FIRST_QUADRANT) return firstQuarterTester(x, y, r); diff --git a/server/src/main/java/org/web1/checkers/CheckerFunction.java b/server/src/main/java/org/web1/checkers/CheckerFunction.java index 38f03ec..6948e52 100644 --- a/server/src/main/java/org/web1/checkers/CheckerFunction.java +++ b/server/src/main/java/org/web1/checkers/CheckerFunction.java @@ -4,5 +4,5 @@ @FunctionalInterface public interface CheckerFunction { - boolean test(HashMap data); + boolean test(int x, float y, float r); } diff --git a/server/src/main/java/org/web1/utils/Timer.java b/server/src/main/java/org/web1/timer/Timer.java similarity index 89% rename from server/src/main/java/org/web1/utils/Timer.java rename to server/src/main/java/org/web1/timer/Timer.java index 48adcff..9531368 100644 --- a/server/src/main/java/org/web1/utils/Timer.java +++ b/server/src/main/java/org/web1/timer/Timer.java @@ -1,4 +1,4 @@ -package org.web1.utils; +package org.web1.timer; public class Timer { long startTime; diff --git a/server/src/main/java/org/web1/validators/QueryHmapValidator.java b/server/src/main/java/org/web1/validators/QueryHmapValidator.java deleted file mode 100644 index 522f7cc..0000000 --- a/server/src/main/java/org/web1/validators/QueryHmapValidator.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.web1.validators; - -import java.util.HashMap; -import java.util.function.Function; - -public class QueryHmapValidator implements Function, Boolean> { - public Boolean apply(HashMap data) { - try { - Integer.parseInt(data.get("x")); - Float.parseFloat(data.get("y")); - Float.parseFloat(data.get("r")); - - return true; - } catch (NumberFormatException | NullPointerException e) { - return false; - } - } -} From ac544219801b47bcecdf016b59ae6d43bf33d21c Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 01:35:03 +0300 Subject: [PATCH 06/10] validator: remove unused files --- .../src/main/java/org/validator/ExampleDTO.java | 7 ------- server/src/main/java/org/validator/Main.java | 15 --------------- server/src/main/java/org/web1/Main.java | 1 - 3 files changed, 23 deletions(-) delete mode 100644 server/src/main/java/org/validator/ExampleDTO.java delete mode 100644 server/src/main/java/org/validator/Main.java diff --git a/server/src/main/java/org/validator/ExampleDTO.java b/server/src/main/java/org/validator/ExampleDTO.java deleted file mode 100644 index 6d55712..0000000 --- a/server/src/main/java/org/validator/ExampleDTO.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.validator; - -import org.validator.annotations.Number; - -public record ExampleDTO ( - @Number(min = 4, step = 3) String textField -) {} diff --git a/server/src/main/java/org/validator/Main.java b/server/src/main/java/org/validator/Main.java deleted file mode 100644 index 44947e5..0000000 --- a/server/src/main/java/org/validator/Main.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.validator; - -public class Main { - public static void main(String[] args) { - try { - ExampleDTO exampleDTO = ValidatedRecordFactory.create( - ExampleDTO.class, - "0" - ); - System.out.println(exampleDTO); - } catch (Exception e) { - System.out.println(e.getMessage()); - } - } -} \ No newline at end of file diff --git a/server/src/main/java/org/web1/Main.java b/server/src/main/java/org/web1/Main.java index dee1f71..56f208d 100644 --- a/server/src/main/java/org/web1/Main.java +++ b/server/src/main/java/org/web1/Main.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.function.Function; -import org.validator.ExampleDTO; import org.validator.ValidatedRecordFactory; import org.web1.DTOs.RequestDTO; import org.web1.checkers.Checker; From 955cff6d1fa7935e7bac5d9db6ccf705d2304714 Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 01:38:14 +0300 Subject: [PATCH 07/10] validation: RuntimeError replaced with ValidationException to make it easier to filter --- .../validation/exceptions/ValidationException.java | 7 +++++++ .../validator/validation/validators/NotNullValidator.java | 3 ++- .../validator/validation/validators/NumberValidator.java | 5 +++-- .../validator/validation/validators/PatternValidator.java | 3 ++- .../org/validator/validation/validators/SizeValidator.java | 3 ++- server/src/main/java/org/web1/Main.java | 3 ++- 6 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 server/src/main/java/org/validator/validation/exceptions/ValidationException.java diff --git a/server/src/main/java/org/validator/validation/exceptions/ValidationException.java b/server/src/main/java/org/validator/validation/exceptions/ValidationException.java new file mode 100644 index 0000000..d889557 --- /dev/null +++ b/server/src/main/java/org/validator/validation/exceptions/ValidationException.java @@ -0,0 +1,7 @@ +package org.validator.validation.exceptions; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/server/src/main/java/org/validator/validation/validators/NotNullValidator.java b/server/src/main/java/org/validator/validation/validators/NotNullValidator.java index 690150c..37381fc 100644 --- a/server/src/main/java/org/validator/validation/validators/NotNullValidator.java +++ b/server/src/main/java/org/validator/validation/validators/NotNullValidator.java @@ -1,12 +1,13 @@ package org.validator.validation.validators; import org.validator.annotations.NotNull; +import org.validator.validation.exceptions.ValidationException; public class NotNullValidator implements Validator { @Override public void validate(NotNull annotation, Object value) { if (value == null) { - throw new RuntimeException(annotation.errorMsg()); + throw new ValidationException(annotation.errorMsg()); } } } \ No newline at end of file diff --git a/server/src/main/java/org/validator/validation/validators/NumberValidator.java b/server/src/main/java/org/validator/validation/validators/NumberValidator.java index 01d8cf5..4f12131 100644 --- a/server/src/main/java/org/validator/validation/validators/NumberValidator.java +++ b/server/src/main/java/org/validator/validation/validators/NumberValidator.java @@ -1,6 +1,7 @@ package org.validator.validation.validators; import org.validator.annotations.Number; +import org.validator.validation.exceptions.ValidationException; public class NumberValidator implements Validator { @Override @@ -8,12 +9,12 @@ public void validate(Number annotation, Object value) { if (value instanceof String str) { double num = Double.parseDouble(str); if (num < annotation.min() || num > annotation.max()) { - throw new RuntimeException( + throw new ValidationException( String.format(annotation.rangeErrorMsg(), annotation.min(), annotation.max()) ); } if (annotation.step() != 0 && (num-annotation.min())%annotation.step() != 0) { - throw new RuntimeException( + throw new ValidationException( String.format(annotation.stepErrorMsg(), annotation.step()) ); } diff --git a/server/src/main/java/org/validator/validation/validators/PatternValidator.java b/server/src/main/java/org/validator/validation/validators/PatternValidator.java index cf73383..450bcf6 100644 --- a/server/src/main/java/org/validator/validation/validators/PatternValidator.java +++ b/server/src/main/java/org/validator/validation/validators/PatternValidator.java @@ -1,13 +1,14 @@ package org.validator.validation.validators; import org.validator.annotations.Pattern; +import org.validator.validation.exceptions.ValidationException; public class PatternValidator implements Validator { @Override public void validate(Pattern annotation, Object value) { if (value instanceof String str) { if (!str.matches(annotation.pattern())) { - throw new RuntimeException(annotation.errorMsg()); + throw new ValidationException(annotation.errorMsg()); } } } diff --git a/server/src/main/java/org/validator/validation/validators/SizeValidator.java b/server/src/main/java/org/validator/validation/validators/SizeValidator.java index 89470d4..9e96199 100644 --- a/server/src/main/java/org/validator/validation/validators/SizeValidator.java +++ b/server/src/main/java/org/validator/validation/validators/SizeValidator.java @@ -1,13 +1,14 @@ package org.validator.validation.validators; import org.validator.annotations.Size; +import org.validator.validation.exceptions.ValidationException; public class SizeValidator implements Validator { @Override public void validate(Size annotation, Object value) { if (value instanceof String str) { if (str.length() < annotation.min() || str.length() > annotation.max()) { - throw new RuntimeException( + throw new ValidationException( String.format( annotation.errorMsg(), annotation.min(), diff --git a/server/src/main/java/org/web1/Main.java b/server/src/main/java/org/web1/Main.java index 56f208d..077ec75 100644 --- a/server/src/main/java/org/web1/Main.java +++ b/server/src/main/java/org/web1/Main.java @@ -5,6 +5,7 @@ import java.util.function.Function; import org.validator.ValidatedRecordFactory; +import org.validator.validation.exceptions.ValidationException; import org.web1.DTOs.RequestDTO; import org.web1.checkers.Checker; import org.web1.checkers.CheckerFunction; @@ -49,7 +50,7 @@ public static void main(String[] args) { ); System.out.println(result); - } catch (Exception e) { + } catch (ValidationException e) { String result = ResponseFactory.create( new JsonBuilder().add("error", '"'+e.getMessage()+'"') ); From 45001d79e6c4bb541d620c564e2193c888bc96b7 Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 02:01:34 +0300 Subject: [PATCH 08/10] fix(static): fixed typo due to which the time was not displayed in the history table --- httpd/static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpd/static/index.html b/httpd/static/index.html index d4f7339..78a595b 100644 --- a/httpd/static/index.html +++ b/httpd/static/index.html @@ -677,7 +677,7 @@

История введенных данных

X: X, Y: Y, R: R, - elapsedTimeMs: 100, + elapsedTimeNs: 100, isHitted: true }; From 7bf751dc37b8929f2c90bfc3c07a35e75e8f6aae Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 02:26:44 +0300 Subject: [PATCH 09/10] fix(static): ok... i now fastcgi ang static can communicate. again --- httpd/static/index.html | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/httpd/static/index.html b/httpd/static/index.html index 78a595b..2f5e9d2 100644 --- a/httpd/static/index.html +++ b/httpd/static/index.html @@ -668,28 +668,9 @@

История введенных данных

if (X && R && Y) { // Stub - sendAJAXGETRequest("/fcgi/", {"x": X, "y": Y, "r": R}, (data) => {console.log(data)}); + sendAJAXGETRequest("/fcgi/", {"x": X, "y": Y, "r": R}, (data) => {FFCGIResponceHandler(data, X,Y,R)}); console.log(`Данные отправлены!\nX: ${X}\nY: ${Y}\nR: ${R}`); - // Сохраняем данные в историю - const formData = { - timestamp: new Date().toISOString(), - X: X, - Y: Y, - R: R, - elapsedTimeNs: 100, - isHitted: true - }; - - formHistory.push(formData); - localStorage.setItem('formHistory', JSON.stringify(formHistory)); - - // Обновляем таблицу - refreshHistoryTable(); - - // Обновляем график - refreshGraph(); - // Очищаем форму this.reset(); From f427113e9798b0552f0caa8fdb740d4e6bf620bd Mon Sep 17 00:00:00 2001 From: NF-coder Date: Mon, 15 Sep 2025 03:03:38 +0300 Subject: [PATCH 10/10] static: errors in responce now handling correctly --- httpd/static/index.html | 86 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/httpd/static/index.html b/httpd/static/index.html index 2f5e9d2..96dd94a 100644 --- a/httpd/static/index.html +++ b/httpd/static/index.html @@ -27,6 +27,8 @@ --svg-axis-primary: oklch(75% 0.04 260); --svg-fill-area: oklch(52% 0.07 200); + --error-bg: oklch(55% 0.15 19); + /* Всякое разное */ --btn-active-translateY: -2px; @@ -231,13 +233,9 @@ } .container { - animation: fadeIn 0.6s ease-out; + animation: containerFadeIn 0.6s ease-out; } - .form-row { - animation: fadeIn 0.8s ease-out; - } - /* Адаптивность */ @media (max-width: 768px) { .form-grid { @@ -375,7 +373,28 @@ transform: translateY(var(--btn-active-translateY)); } - /* Адаптивность */ + /* Сообщение об ошибке */ + .error-message { + position: fixed; + bottom: 20px; + right: 20px; + + background-color: var(--error-bg); + color: var(--text-primary); + + padding: 15px 20px; + + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + + z-index: 1000; + + max-width: 350px; + + animation: errorSlideIn 0.3s ease-out, errorFadeOut 0.5s ease-in 4.5s forwards; + } + + /* Адаптивность */ @media (max-width: 968px) { .container { min-width: 95%; @@ -404,7 +423,7 @@ } /* Анимации */ - @keyframes fadeIn { + @keyframes containerFadeIn { from { opacity: 0; transform: translateY(20px); @@ -414,6 +433,27 @@ transform: translateY(0); } } + + @keyframes errorSlideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes errorFadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + display: none; + } + } @@ -633,8 +673,34 @@

История введенных данных

// Хранение истории данных let formHistory = JSON.parse(localStorage.getItem('formHistory')) || []; + const showError = (message) => { + // Убираем существующие сообщения + const existingErrors = document.querySelectorAll('.error-message'); + existingErrors.forEach(error => error.remove()); + + // Слздаём элемент + const errorDiv = document.createElement('div'); + errorDiv.className = 'error-message'; + errorDiv.textContent = message; + + // Вставка + document.body.appendChild(errorDiv); + + // Убираем по прошествии 5-ти секунд + setTimeout(() => { + if (errorDiv.parentNode) { + errorDiv.parentNode.removeChild(errorDiv); + } + }, 5000); + }; + const FFCGIResponceHandler = (responce, X, Y, R) => { - const JSONresponce = JSON.parse(responce); + const JSONResponce = JSON.parse(responce); + + if (JSONResponce.error) { + showError(`Ошибка: ${JSONResponce.error}`); + return; + } // Сохраняем данные в историю const formData = { @@ -642,8 +708,8 @@

История введенных данных

X: X, Y: Y, R: R, - elapsedTimeNs: JSONresponce.elapsedTimeNs, - isHitted: JSONresponce.result + elapsedTimeNs: JSONResponce.elapsedTimeNs, + isHitted: JSONResponce.result }; formHistory.push(formData);