All Downloads are FREE. Search and download functionalities are using the official Maven repository.

host.anzo.core.webserver.WebService Maven / Gradle / Ivy

There is a newer version: 1.29
Show newest version
package host.anzo.core.webserver;

import com.google.gson.Gson;
import de.mxro.metrics.jre.Metrics;
import delight.async.properties.PropertyNode;
import host.anzo.classindex.ClassIndex;
import host.anzo.commons.concurrent.CloseableReentrantLock;
import host.anzo.commons.emergency.metric.IMetric;
import host.anzo.commons.emergency.metric.Metric;
import host.anzo.commons.emergency.metric.MetricGroupType;
import host.anzo.commons.emergency.metric.MetricResult;
import host.anzo.commons.model.enums.ERestrictionType;
import host.anzo.commons.utils.ConsoleUtils;
import host.anzo.commons.utils.VMUtils;
import host.anzo.core.config.EmergencyConfig;
import host.anzo.core.config.WebServerConfig;
import host.anzo.core.service.FirewallService;
import host.anzo.core.service.HttpService;
import host.anzo.core.startup.StartupComponent;
import host.anzo.core.webserver.engine.ThymeleafTemplateEngine;
import host.anzo.core.webserver.model.AWebArea;
import host.anzo.core.webserver.model.WebArea;
import host.anzo.core.webserver.model.WebSocketsArea;
import host.anzo.core.webserver.model.captcha.ReCaptchaResponse;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload2.core.DiskFileItem;
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
import org.apache.commons.fileupload2.jakarta.JakartaServletDiskFileUpload;
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
import org.apache.commons.lang3.StringUtils;
import spark.Spark;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author ANZO
 * @since 18.08.2016
 */
@Slf4j
@Metric
@StartupComponent("AfterStart")
public class WebService implements IMetric {
	@Getter(lazy = true)
	private final static WebService instance = new WebService();

	private @Getter ThymeleafTemplateEngine templateEngine;
	private @Getter JakartaServletFileUpload fileUploadServlet;
	private final static PropertyNode webMetrics = Metrics.create();

	private final static Gson gson = new Gson();

	private final ConcurrentHashMap lockersMap = new ConcurrentHashMap<>();

	private WebService() {
		ConsoleUtils.printSection("WebService Loading");
		if (WebServerConfig.ENABLE) {
			if (WebServerConfig.USE_FIDDLER_PROXY) {
				System.setProperty("http.proxyHost", "127.0.0.1");
				System.setProperty("https.proxyHost", "127.0.0.1");
				System.setProperty("http.proxyPort", "18888");
				System.setProperty("https.proxyPort", "18888");
				System.setProperty("javax.net.ssl.trustStoreType","Windows-ROOT");
			}

			templateEngine = new ThymeleafTemplateEngine();
			fileUploadServlet = new JakartaServletDiskFileUpload();
			init();
		}
		else {
			log.info("Embedded webserver disabled due config.");
		}
	}

	private void init() {
		if (StringUtils.isNoneEmpty(WebServerConfig.DATA_PATH)) {
			try {
				Files.createDirectories(Paths.get(WebServerConfig.DATA_PATH));
			} catch (IOException e) {
				throw new RuntimeException("Error while creating web root directory.", e);
			}
			Spark.staticFiles.externalLocation(WebServerConfig.DATA_PATH);
		}
		else {
			log.info("Web server static files folder not defined. Ignoring.");
		}

		if (!WebServerConfig.DEBUG && !VMUtils.DEBUG) {
			Spark.staticFiles.expireTime(600);
		}

		if (WebServerConfig.ALLOW_ONLY_CLOUDFLARE_IPS) {
			Spark.untrustForwardHeaders();
		}

		if (WebServerConfig.ENABLE_HTTP2) {
			Spark.useHTTP2(true);
		}

		Spark.port(WebServerConfig.PORT);

		if (WebServerConfig.ENABLE_SOCKETS) {
			for(Class webSocketAreaClass : ClassIndex.getAnnotated(WebSocketsArea.class)) {
				final WebSocketsArea webSocketsAreaAnnotation = webSocketAreaClass.getAnnotation(WebSocketsArea.class);
				Spark.webSocket(webSocketsAreaAnnotation.path(), webSocketAreaClass);
				log.info("Registered [{}] web sockets area at path [{}]", webSocketAreaClass.getSimpleName(), webSocketsAreaAnnotation.path());
			}
		}

		if (WebServerConfig.ENABLE_SSL) {
			try {
				final KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
				try (final FileInputStream jksInputStream = (new FileInputStream(WebServerConfig.SSL_STORE_PATH))) {
					keystore.load(jksInputStream, WebServerConfig.SSL_STORE_PASSWORD.toCharArray());
					final Enumeration aliases = keystore.aliases();
					while (aliases.hasMoreElements()) {
						final String aliasName = aliases.nextElement();
						final Date certExpiryDate = ((X509Certificate) keystore.getCertificate(aliasName)).getNotAfter();
						final long dateDiff = certExpiryDate.getTime() - new Date().getTime();
						final long expiresIn = dateDiff / TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
						if (expiresIn <= 0) {
							throw new RuntimeException("Certificate for alias [" + aliasName + "] is expired (valid until [" + certExpiryDate + "])");
						}
						else {
							if (expiresIn > 30) {
								log.info("Loaded certificate for alias [{}] with expire date [{}]", aliasName, certExpiryDate);
							}
							else {
								log.warn("Certificate for alias [{}] will expire on [{}] ({} days left)", aliasName, certExpiryDate, expiresIn);
							}
						}
					}
				}
			}
			catch (Exception e) {
				log.error("Error while loading web certificate", e);
			}

			Spark.secure(WebServerConfig.SSL_STORE_PATH, WebServerConfig.SSL_STORE_PASSWORD, null, null);
		}

		Spark.threadPool(WebServerConfig.THREAD_COUNT_MAX, WebServerConfig.THREAD_COUNT_MIN, WebServerConfig.TIMEOUT);
		for(Class webAreaClass : ClassIndex.getAnnotated(WebArea.class)) {
			try {
				final AWebArea webArea = (AWebArea)webAreaClass.getDeclaredConstructor().newInstance();
				webArea.registerPaths();
				log.info("Registered [{}] web area", webAreaClass.getSimpleName());
			}
			catch (Exception e) {
				log.error("Error while registering web area [{}]", webAreaClass.getSimpleName(), e);
			}
		}

		Spark.awaitInitialization();
	}

	public void onRequest(boolean authentificated) {
		if (EmergencyConfig.ENABLE_METRICS) {
			webMetrics.record(Metrics.increment(authentificated ? "authentificated" : "non-authentificated"));
		}
	}

	/**
	 * @param ip client IP address
	 * @return {@code true} if request is passed firewall checks, {@code false} otherwise
	 */
	public boolean isAllowedAddress(String ip) {
		if (!WebServerConfig.ENABLE_REQUEST_LIMITER) {
			return true;
		}
		return FirewallService.getInstance().isAllowedAddress(WebService.class, ip, WebServerConfig.PORT, WebServerConfig.MAX_REQUESTS_PER_SECOND, ERestrictionType.BAN);
	}

	public CloseableReentrantLock getLock(long userNo) {
		return lockersMap.computeIfAbsent(userNo, newLock -> new CloseableReentrantLock());
	}

	/**
	 * @return new captcha HTML object for displaying on page
	 */
	@SuppressWarnings("unused")
	public String createReCaptcha() {
		if (!WebServerConfig.RECAPTCHA_ENABLE) {
			return "
"; } return "
"; } /** * @param remoteIp client IP address * @param response client captcha challenge answer * @return challenge result for captcha, verified by google service */ @SuppressWarnings("unused") public ReCaptchaResponse checkReCaptchaAnswer(String remoteIp, String response) { if (!WebServerConfig.RECAPTCHA_ENABLE) { return null; } final String postParameters = "secret=" + URLEncoder.encode(WebServerConfig.RECAPTCHA_PRIVATE_KEY) + "&remoteip=" + URLEncoder.encode(remoteIp) + "&response=" + URLEncoder.encode(response); final String message = HttpService.getInstance().httpPost("https://www.google.com/recaptcha/api/siteverify", postParameters); if (message == null) { return new ReCaptchaResponse(false); } return gson.fromJson(message, ReCaptchaResponse.class); } @Override public List getMetric() { final MetricResult result = new MetricResult(); result.setMetricGroupType(MetricGroupType.WEB); result.setName("WebServiceRequests"); result.setData(webMetrics.render().get()); return Collections.singletonList(result); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy