Remove hash (#) from URL in Jhipster (both java an

2019-04-14 21:56发布

问题:

I am using Jhipster Spring boot + angular 6. But i'm having trouble because of the hash(#) in URL. It is affecting SEO.

I tried setting useHash: false in app-routing-module.ts. But then the API is not working when I run the project via npm start.

I think somewhere in Java files I have to change a configuration to remove # from the URL.

Here is my WebConfigurer code,

@Configuration
public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer<WebServerFactory> {

    private final Logger log = LoggerFactory.getLogger(WebConfigurer.class);

    private final Environment env;

    private final JHipsterProperties jHipsterProperties;

    private MetricRegistry metricRegistry;

    public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) {

        this.env = env;
        this.jHipsterProperties = jHipsterProperties;
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        if (env.getActiveProfiles().length != 0) {
            log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
        }
        EnumSet<DispatcherType> disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
        initMetrics(servletContext, disps);
        log.info("Web application fully configured");
    }

    /**
     * Customize the Servlet engine: Mime types, the document root, the cache.
     */
    @Override
    public void customize(WebServerFactory server) {
        setMimeMappings(server);

        /*
         * Enable HTTP/2 for Undertow - https://twitter.com/ankinson/status/829256167700492288
         * HTTP/2 requires HTTPS, so HTTP requests will fallback to HTTP/1.1.
         * See the JHipsterProperties class and your application-*.yml configuration files
         * for more information.
         */
        if (jHipsterProperties.getHttp().getVersion().equals(JHipsterProperties.Http.Version.V_2_0) &&
            server instanceof UndertowServletWebServerFactory) {

            ((UndertowServletWebServerFactory) server)
                .addBuilderCustomizers(builder ->
                    builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true));
        }
    }

    private void setMimeMappings(WebServerFactory server) {
        if (server instanceof ConfigurableServletWebServerFactory) {
            MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
            // IE issue, see https://github.com/jhipster/generator-jhipster/pull/711
            mappings.add("html", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
            // CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64
            mappings.add("json", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
            ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
            servletWebServer.setMimeMappings(mappings);
        }
    }

    /**
     * Initializes Metrics.
     */
    private void initMetrics(ServletContext servletContext, EnumSet<DispatcherType> disps) {
        log.debug("Initializing Metrics registries");
        servletContext.setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE,
            metricRegistry);
        servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY,
            metricRegistry);

        log.debug("Registering Metrics Filter");
        FilterRegistration.Dynamic metricsFilter = servletContext.addFilter("webappMetricsFilter",
            new InstrumentedFilter());

        metricsFilter.addMappingForUrlPatterns(disps, true, "/*");
        metricsFilter.setAsyncSupported(true);

        log.debug("Registering Metrics Servlet");
        ServletRegistration.Dynamic metricsAdminServlet =
            servletContext.addServlet("metricsServlet", new MetricsServlet());

        metricsAdminServlet.addMapping("/management/metrics/*");
        metricsAdminServlet.setAsyncSupported(true);
        metricsAdminServlet.setLoadOnStartup(2);
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = jHipsterProperties.getCors();
        if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) {
            log.debug("Registering CORS filter");
            source.registerCorsConfiguration("/api/**", config);
            source.registerCorsConfiguration("/management/**", config);
            source.registerCorsConfiguration("/v2/api-docs", config);
        }
        return new CorsFilter(source);
    }

    @Autowired(required = false)
    public void setMetricRegistry(MetricRegistry metricRegistry) {
        this.metricRegistry = metricRegistry;
    }
}

Here is my AngularRouteFilter servlet code,

public class AngularRouteFilter extends OncePerRequestFilter {

    // add the values you want to redirect for
    private static final Pattern PATTERN = Pattern.compile("^/((api|swagger-ui|management|swagger-resources)/|favicon\\.ico|v2/api-docs).*");

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
        throws ServletException, IOException {
        if (isServerRoute(request)) {
            filterChain.doFilter(request, response);
        } else {
            RequestDispatcher rd = request.getRequestDispatcher("/");
            rd.forward(request, response);
        }
    }

    protected static boolean isServerRoute(HttpServletRequest request) {
        if (request.getMethod().equals("GET")) {
            String uri = request.getRequestURI();
        if (uri.startsWith("/app")){

                return true;
            }
            return PATTERN.matcher(uri).matches();
        }
        return true;
    }
}

here is my Swagger index.html(swagger-ui/index.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Swagger UI</title>
    <link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" />
    <link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" />
    <link href='./dist/css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
    <link href='./dist/css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
    <link href='./dist/css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
    <link href='./dist/css/reset.css' media='print' rel='stylesheet' type='text/css'/>
    <link href='./dist/css/print.css' media='print' rel='stylesheet' type='text/css'/>
    <script src='./dist/lib/object-assign-pollyfill.js' type='text/javascript'></script>
    <script src='./dist/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
    <script src='./dist/lib/jquery.slideto.min.js' type='text/javascript'></script>
    <script src='./dist/lib/jquery.wiggle.min.js' type='text/javascript'></script>
    <script src='./dist/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
    <script src='./dist/lib/handlebars-4.0.5.js' type='text/javascript'></script>
    <script src='./dist/lib/lodash.min.js' type='text/javascript'></script>
    <script src='./dist/lib/backbone-min.js' type='text/javascript'></script>
    <script src='./dist/swagger-ui.min.js' type='text/javascript'></script>
    <script src='./dist/lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
    <script src='./dist/lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
    <script src='./dist/lib/jsoneditor.min.js' type='text/javascript'></script>
    <script src='./dist/lib/marked.js' type='text/javascript'></script>
    <script src='./dist/lib/swagger-oauth.js' type='text/javascript'></script>

    <!-- Some basic translations -->
    <!-- <script src='lang/translator.js' type='text/javascript'></script> -->
    <!-- <script src='lang/ru.js' type='text/javascript'></script> -->
    <!-- <script src='lang/en.js' type='text/javascript'></script> -->

    <script type="text/javascript">
        $(function() {
            var springfox = {
                "baseUrl": function() {
                    var urlMatches = /(.*)\/swagger-ui\/index.html.*/.exec(window.location.href);
                    return urlMatches[1];
                },
                "securityConfig": function(cb) {
                    $.getJSON(this.baseUrl() + "/swagger-resources/configuration/security", function(data) {
                        cb(data);
                    });
                },
                "uiConfig": function(cb) {
                                    alert(cb);

                    $.getJSON(this.baseUrl() + "/swagger-resources/configuration/ui", function(data) {
                        cb(data);
                    });
                }
            };
            window.springfox = springfox;
            window.oAuthRedirectUrl = springfox.baseUrl() + './dist/o2c.html'

            window.springfox.uiConfig(function(data) {
                window.swaggerUi = new SwaggerUi({
                    dom_id: "swagger-ui-container",
                    validatorUrl: data.validatorUrl,
                    supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
                    onComplete: function(swaggerApi, swaggerUi) {
                        initializeSpringfox();
                        if (window.SwaggerTranslator) {
                            window.SwaggerTranslator.translate();
                        }
                        $('pre code').each(function(i, e) {
                            hljs.highlightBlock(e)
                        });
                    },
                    onFailure: function(data) {
                        log("Unable to Load SwaggerUI");
                    },
                    docExpansion: "none",
                    apisSorter: "alpha",
                    showRequestHeaders: false
                });

                initializeBaseUrl();

                $('#select_baseUrl').change(function() {
                    window.swaggerUi.headerView.trigger('update-swagger-ui', {
                        url: $('#select_baseUrl').val()
                    });
                    addApiKeyAuthorization();
                });

                function addApiKeyAuthorization() {
                    var authToken = JSON.parse(localStorage.getItem("jhi-authenticationtoken") || sessionStorage.getItem("jhi-authenticationtoken"));
                    var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("Authorization", "Bearer " + authToken, "header");
                    window.swaggerUi.api.clientAuthorizations.add("bearer", apiKeyAuth);
                }

                function getCSRF() {
                    var name = "XSRF-TOKEN=";
                    var ca = document.cookie.split(';');
                    for(var i=0; i<ca.length; i++) {
                        var c = ca[i];
                        while (c.charAt(0)==' ') c = c.substring(1);
                        if (c.indexOf(name) !== -1) return c.substring(name.length,c.length);
                    }
                    return "";
                }

                function log() {
                    if ('console' in window) {
                        console.log.apply(console, arguments);
                    }
                }

                function oAuthIsDefined(security) {
                    return security.clientId
                    && security.clientSecret
                    && security.appName
                    && security.realm;
                }

                function initializeSpringfox() {
                    var security = {};
                    window.springfox.securityConfig(function(data) {
                        security = data;
                        if (typeof initOAuth === "function" && oAuthIsDefined(security)) {
                            initOAuth(security);
                        }
                    });
                }
            });

            function maybePrefix(location, withRelativePath) {
                var pat = /^https?:\/\//i;
                if (pat.test(location)) {
                    return location;
                }
                return withRelativePath + location;
            }

            function initializeBaseUrl() {
                var relativeLocation = springfox.baseUrl();

                $('#input_baseUrl').hide();

                $.getJSON(relativeLocation + "/swagger-resources", function(data) {

                    var $urlDropdown = $('#select_baseUrl');
                    $urlDropdown.empty();
                    $.each(data, function(i, resource) {
                        var option = $('<option></option>')
                        .attr("value", maybePrefix(resource.location, relativeLocation))
                        .text(resource.name + " (" + resource.location + ")");
                        $urlDropdown.append(option);
                    });
                    $urlDropdown.change();
                });

            }

        });
    </script>
</head>


<body class="swagger-section">
<div id='header'>
    <div class="swagger-ui-wrap">
        <a id="logo" href="http://swagger.io">swagger</a>

        <form id='api_selector'>
            <div class='input'>
                <select id="select_baseUrl" name="select_baseUrl"></select>
            </div>
            <div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/>
            </div>
        </form>
    </div>
</div>

<div id="message-bar" class="swagger-ui-wrap" data-sw-translate>&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body>
</html>

here is docs.component.html

<iframe src="swagger-ui/index.html" width="100%" height="900" seamless
    target="_top" title="Swagger UI" class="border-0"></iframe>

here my server code is running perfectly @ localhost:6060. Butm localhost:6060/api/docs opening a blank page.

here is the screen shot,

Please suggest me where i am doing wrong.

回答1:

Solution using a servlet filter

First step is to configure client, set useHash: false in app-routing-module.ts

In index.html, change <base href="./" /> to <base href="/" />

But it's not enough because it does not support deep linking which means linking to a client route from an external link like from a mail message or from another web site, or veen when refreshing page in browser.

When deep linking, the server receives the request first and if the URL has to be handled by client side, it will not be found so our server app must detect whether the URL has to be served as-is by server (e.g. all API calls) or forwarded to index.html so that the javascript app can interpret it.

One solution is to use pattern matching in a servlet filter (Html5RouteFilter) that we register in WebConfigurer.java

WebConfigurer.java

    @Bean
    public Html5RouteFilter html5RouteFilter() {
        return new Html5RouteFilter();
    }

Html5RouteFilter.java

package com.mycompany.myapp.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Pattern;

/**
 * Filter that distinguishes between client routes and server routes  when you don't use '#' in client routes.
 */
public class Html5RouteFilter extends OncePerRequestFilter {

    private Logger log = LoggerFactory.getLogger(getClass());


    // These are the URIs that should be processed server-side
    private static final Pattern PATTERN = Pattern.compile("^/((api|content|i18n|management|swagger-ui|swagger-resources)/|error|h2-console|swagger-resources|favicon\\.ico|v2/api-docs).*");

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
        throws ServletException, IOException {
        if (isServerRoute(request)) {
            filterChain.doFilter(request, response);
        } else {
            RequestDispatcher rd = request.getRequestDispatcher("/");
            rd.forward(request, response);
        }
    }

    protected static boolean isServerRoute(HttpServletRequest request) {
        if (request.getMethod().equals("GET")) {
            String uri = request.getRequestURI();
            if (uri.startsWith("/app")) {
                return true;
            }
            return PATTERN.matcher(uri).matches();
        }
        return true;
    }
}


回答2:

Solution using a REST controller

First step is to configure client, set useHash: false in app-routing-module.ts

In index.html, change <base href="./" /> to <base href="/" />

But it's not enough because it does not support deep linking which means linking to a client route from an external link like from a mail message or from another web site, or veen when refreshing page in browser.

When deep linking, the server receives the request first and if the URL has to be handled by client side, it will not be found so our server app must detect whether the URL has to be served as-is by server (e.g. all API calls) or forwarded to index.html so that the javascript app can interpret it.

ClientRouteForwarder .java

package com.mycompany.myapp.web.rest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * A REST controller that forwards all GET requests that did not match a RequestMapping to /index.html so that
 * client routes can be handled by client code in browser.
 *
 * This works because Spring resolves exact matches first.
 */
@Controller
public class ClientRouteForwarder {

    @GetMapping(value = "/**/{[path:[^\\.]*}")
    public String forward() {
        return "forward:/";
    }
}


回答3:

As per their official documentation Configuring html5, AngularJS uses a “#” in it’s urls. HTML5Mode of AngularJS removes these “#” from URL.

Activate HTML 5 Mode

Create html5.mode.config.js file in webapp/app/blocks/config/ directory:

(function() {
  'use strict';

  angular
    .module('<YourAppName>')
    .config(html5ModeConfig);

  html5ModeConfig.$inject = ['$locationProvider'];

  function html5ModeConfig($locationProvider) {
    $locationProvider.html5Mode({ enabled: true, requireBase: true });
  }
})();

Then open index.html and add this line in head tag:

<base href="/">