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

ru.vyarus.guicey.spa.SpaBundle Maven / Gradle / Ivy

There is a newer version: 7.1.4
Show newest version
package ru.vyarus.guicey.spa;

import com.google.common.base.Joiner;
import io.dropwizard.jetty.setup.ServletEnvironment;
import io.dropwizard.servlets.assets.AssetServlet;
import io.dropwizard.core.setup.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBootstrap;
import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBundle;
import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyEnvironment;
import ru.vyarus.dropwizard.guice.module.installer.util.PathUtils;
import ru.vyarus.guicey.spa.filter.SpaRoutingFilter;

import jakarta.servlet.DispatcherType;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static ru.vyarus.dropwizard.guice.module.installer.util.PathUtils.SLASH;

/**
 * Provides support for SPA (for example, angular apps).
 * Such applications often use html5 pretty url, like "/app/someroute/subroute".
 * When user bookmark such url or simply refresh page, browser requests complete url on server
 * and server must support it: redirect to main index page (without changing url, so client could handle routing).
 * 

* Use dropwizard-assets servlet internally, but wraps it with special filter, which reacts on resource not found * errors (by default, all calls pass to assets filter!). Applies no-cache header for index page. *

* You can register multiple SPA applications on main or admin contexts (or both). * All applications must have unique names. In case of duplicate names or mapping on the same path, error * will be thrown. * * @author Vyacheslav Rusakov * @since 02.04.2017 */ @SuppressWarnings("PMD.ImmutableField") public class SpaBundle implements GuiceyBundle { public static final String DEFAULT_PATTERN = "\\.(html|css|js|png|jpg|jpeg|gif|ico|xml|rss|txt|eot|svg|ttf|woff|woff2|cur)" + "(\\?((r|v|rel|rev)=[\\-\\.\\w]*)?)?$"; private final Logger logger = LoggerFactory.getLogger(SpaBundle.class); private boolean mainContext; private String assetName; private String resourcePath; private String uriPath; private String indexFile = "index.html"; private String noRedirectRegex = DEFAULT_PATTERN; @Override public void initialize(final GuiceyBootstrap bootstrap) { // list shared between all spa bundles final List used = bootstrap.sharedState(SpaBundle.class, ArrayList::new); // important because name used for filter mapping checkArgument(!used.contains(assetName), "SPA with name '%s' is already registered", assetName); used.add(assetName); } @Override public void run(final GuiceyEnvironment environment) { final Environment env = environment.environment(); final ServletEnvironment context = mainContext ? env.servlets() : env.admin(); final Set clash = context.addServlet(assetName, new AssetServlet(resourcePath, uriPath, indexFile, StandardCharsets.UTF_8)) .addMapping(uriPath + '*'); if (clash != null && !clash.isEmpty()) { throw new IllegalStateException(String.format( "Assets servlet %s registration clash with already installed servlets on paths: %s", assetName, Joiner.on(',').join(clash))); } final EnumSet types = EnumSet.of(DispatcherType.REQUEST); context.addFilter(assetName + "Routing", new SpaRoutingFilter(uriPath, noRedirectRegex)) .addMappingForServletNames(types, false, assetName); logger.info("SPA '{}' for source '{}' registered on uri '{}' in {} context", assetName, resourcePath, uriPath + '*', mainContext ? "main" : "admin"); } /** * Register SPA application in main context. * Note: application names must be unique (when you register multiple SPA applications. * * @param name application name (used as servlet name) * @param resourcePath application resources classpath location (may use slash or dots as separator) * @param uriPath mapping uri * @return builder instance for SPA configuration */ public static Builder app(final String name, final String resourcePath, final String uriPath) { return new Builder(true, name, resourcePath, uriPath); } /** * Register SPA application in admin context. * Note: application names must be unique (when you register multiple SPA applications. * * @param name application name (used as servlet name) * @param resourcePath path to application resources (classpath) * @param uriPath mapping uri * @return builder instance for SPA configuration */ public static Builder adminApp(final String name, final String resourcePath, final String uriPath) { return new Builder(false, name, resourcePath, uriPath); } /** * Spa bundle builder. */ public static class Builder { private final SpaBundle bundle = new SpaBundle(); @SuppressWarnings("PMD.UseStringBufferForStringAppends") public Builder(final boolean mainContext, final String name, final String path, final String uri) { bundle.mainContext = mainContext; bundle.assetName = checkNotNull(name, "Name is required"); bundle.uriPath = PathUtils.trailingSlash(uri); bundle.resourcePath = PathUtils.normalizeClasspathPath(path); checkArgument(!SLASH.equals(bundle.resourcePath), "%s is the classpath root", path); } /** * @param name index file name (by default "index.html") * @return builder instance */ public Builder indexPage(final String name) { bundle.indexFile = name; return this; } /** * Redirect filter will prevent redirection when accept header is not compatible with text/html. * As this may not be enough, default regex {@link #DEFAULT_PATTERN} is used to prevent redirection in * some cases. By default it's all common web files (html, css, js) with possible version markers * (e.g. ?ver=1214324). *

* NOTE: regex is applied with "find", so use ^ or $ to apply boundaries. * * @param regex regular expression to prevent redirection to root (prevent when regex matched) * @return builder instance */ public Builder preventRedirectRegex(final String regex) { bundle.noRedirectRegex = checkNotNull(regex, "Regex can't be null"); return this; } /** * @return configured dropwizard bundle instance */ public SpaBundle build() { return bundle; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy