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> </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.
Solution using a REST controller
First step is to configure client, set
useHash: false
inapp-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
As per their official documentation Configuring html5,
AngularJS uses a “#” in it’s urls. HTML5Mode of AngularJS removes these “#” from URL
.Create
html5.mode.config.js
file inwebapp/app/blocks/config/
directory:Then open
index.html
and add this line in head tag:Solution using a servlet filter
First step is to configure client, set
useHash: false
inapp-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
Html5RouteFilter.java