Running Vaadin 10 application locally with Jetty 9

Hi,
I need to run a Vaadin 10 application locally (in Eclipse) within a Jetty 9. I tried different configurations but he is not able to navigate to my routing path.

My setup is the following:

An empty Vaadin servlet to be added to Jetty:

@WebServlet(value = "/*", asyncSupported = true)
@VaadinServletConfiguration(productionMode = false, ui = ShowCaseVaadinApplication.class)
public class ShowCaseVaadinServlet extends VaadinServlet {}

An UI which I need to make some general setups for my framework etc.:

@PageTitle("Show Case")
public class ShowCaseVaadinApplication extends RayVaadinApplication {
   ...
}

And my main page:

@Route("showCaseVaadin10")
public class MainPage extends VerticalLayout {
   ...
}

Finally I am adding the servlet to Jetty:

		Server server = new Server(SERVER_SOCKET);
		ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
		context.setContextPath("/");
		server.setHandler(context);
		ServletHolder servletHolder = new ServletHolder(ShowCaseVaadinServlet.class);
		servletHolder.setInitParameter("filterMappingUrlPattern", "/*");
		context.addServlet(servletHolder, urlPattern);

But Vaadin is not able to find the routing to the MainPage (“showCaseVaadin10”). I only get an “empty” page with some stuff like:

 {
            "node": 3,
            "type": "put",
            "key": "innerHTML",
            "feat": 1,
            "value": "\n\tCould not navigate to 'showCaseVaadin10/'\n\t\x3Cp>Reason: Couldn't find route for 'showCaseVaadin10/'\x3C/p>\n\t\x3Cp>\x3C/p>\x3Cdiv>Available routes:\x3C/div>\n\t\x3Cp>\x3C/p>\n\t\x3Cul>\n\t\t\n\t\x3C/ul>\n\t\x3Cp>This detailed message is only shown when running in development mode.\x3C/p>\n"
        },

I also tried to add the servlet with the url pattern “/showCaseVaadin10/*” and to leave the rout empty. But this didn’t work either.

Is there anything I have to do to trigger Vaadin to scan for Route annotations or to add manually routes or something else?

@HandlesTypes (https://docs.oracle.com/javaee/6/api/javax/servlet/annotation/HandlesTypes.html) and ServletContainerInitializer are used to initialize routes and other metadata. You probably need to make your embedded Jetty instance triggers these correctly (not sure how exactly). If you use a standalone Jetty, it “just works”

Thanks a lot Artur. I found something about annotation evaluation in embedded Jetty instances, which unfortunately did not work for me so far.

Can you give me a hint about the Vaadin implementations of ServletContainerInitializer (name and package) where I can see how the initialization is triggered?
Maybe this gives me an idea how the mechanism works and I can handle it.

https://github.com/vaadin/flow/blob/6252bc4940e86c2cffef5642e335da7d00ade47c/flow-server/src/main/java/com/vaadin/flow/server/startup/RouteRegistryInitializer.java#L35

It is the servlet containers responsibility to call the onStartup method when it starts

Hi again :slight_smile:

I managed Jetty to find the routing.
Now the page will be loaded in browser but not shown correctly. The page is shown completely empty, but when I take a look at the page’s source I see this:

<!doctype html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><base href="."><meta name="viewport" content="width=device-width, initial-scale=1.0"><style type="text/css">body {height:100vh;width:100vw;margin:0;}.v-reconnect-dialog {position: absolute;top: 1em;right: 1em;border: 1px solid black;padding: 1em;z-index: 10000;}.v-system-error {color: red;background: white;position: absolute;top: 1em;right: 1em;border: 1px solid black;padding: 1em;z-index: 10000;pointer-events: auto;}</style><script type="text/javascript">//<![CDATA[
(function() {	var apps = {};	var widgetsets = {};					var log;	if (typeof console === undefined || !window.location.search.match(/[&?]
debug(&|$)/)) {		/* If no console.log present, just use a no-op */		log = function() {};	} else if (typeof console.log === "function") {		/* If it's a function, use it with apply */		log = function() {			console.log.apply(console, arguments);		};	} else {		/* In IE, its a native function for which apply is not defined, but it works		 without a proper 'this' reference */		log = console.log;	}		var isWidgetsetLoaded = function(widgetset) {		var className = widgetset.replace(/\./g, "_");		return (typeof window[className]
) != "undefined";	};		var loadWidgetset = function(url, widgetset) {		if (widgetsets[widgetset]
) {			return;		}		log("load widgetset", url, widgetset);		setTimeout(function() {			if (!isWidgetsetLoaded(widgetset)) {				alert("Failed to load the widgetset: " + url);			}		}, 15000);			var scriptTag = document.createElement('script');		scriptTag.setAttribute('type', 'text/javascript');		scriptTag.setAttribute('src', url);		document.getElementsByTagName('head')[0]
.appendChild(scriptTag);				widgetsets[widgetset]
 = {			pendingApps: []		};	};		var isInitializedInDom = function(appId) {		var appDiv = document.getElementById(appId);		if (!appDiv) {			return false;		}		for ( var i = 0; i < appDiv.childElementCount; i++) {			var className = appDiv.childNodes[i]
.className;			/* If the app div contains a child with the class			"v-app-loading" we have only received the HTML			but not yet started the widget set			(UIConnector removes the v-app-loading div). */			if (className && className.indexOf("v-app-loading") != -1) {				return false;			}		}		return true;	};		/* 	 * Needed for Testbench compatibility, but prevents any Vaadin 7 app from	 * bootstrapping unless the legacy vaadinBootstrap.js file is loaded before	 * this script.	 */    window.Vaadin = window.Vaadin || {};	window.Vaadin.Flow = window.Vaadin.Flow || {        clients: {},		initApplication: function(appId, config) {			var testbenchId = appId.replace(/-\d+$/, '');						if (apps[appId]
) {				if (window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.clients && window.Vaadin.Flow.clients[testbenchId]
 && window.Vaadin.Flow.clients[testbenchId]
.initializing) {					throw "Application " + appId + " is already being initialized";				}				if (isInitializedInDom(appId)) {					throw "Application " + appId + " already initialized";				}			}				log("init application", appId, config);						window.Vaadin.Flow.clients[testbenchId]
 = {					isActive: function() {						return true;					},					initializing: true			};						var getConfig = function(name) {				var value = config[name]
;				return value;			};						/* Export public data */			var app = {				getConfig: getConfig			};			apps[appId]
 = app;						if (!window.name) {				window.name =  appId + '-' + Math.random();			}				var widgetset = "client";			widgetsets[widgetset]
 = {					pendingApps: []				};			if (widgetsets[widgetset]
.callback) {				log("Starting from bootstrap", appId);				widgetsets[widgetset]
.callback(appId);			}  else {				log("Setting pending startup", appId);				widgetsets[widgetset]
.pendingApps.push(appId);			}				return app;		},		getAppIds: function() {			var ids = [ ]
;			for (var id in apps) {				if (apps.hasOwnProperty(id)) {					ids.push(id);				}			}			return ids;		},		getApp: function(appId) {			return apps[appId]
;		},		registerWidgetset: function(widgetset, callback) {			log("Widgetset registered", widgetset);			var ws = widgetsets[widgetset]
;			if (ws && ws.pendingApps) {				ws.callback = callback;				for(var i = 0; i < ws.pendingApps.length; i++) {					var appId = ws.pendingApps[i]
;					log("Starting from register widgetset", appId);					callback(appId);				}				ws.pendingApps = null;			}		}	};		log('Flow bootstrap loaded');		if (typeof window.__gwtStatsEvent != 'function') {window.Vaadin.Flow.gwtStatsEvents = [];window.__gwtStatsEvent = function(event) {window.Vaadin.Flow.gwtStatsEvents.push(event); return true;};};		var uidl = {
    "syncId": 0,
    "clientId": 0,
    "constants": {
        "F8oCtNArLiI=": {
            "event.shiftKey": false,
            "event.metaKey": false,
            "event.detail": false,
            "event.ctrlKey": false,
            "event.clientX": false,
            "event.clientY": false,
            "event.altKey": false,
            "event.button": false,
            "event.screenY": false,
            "event.screenX": false
        }
    },
    "changes": [
        {
            "node": 1,
            "type": "put",
            "key": "tag",
            "feat": 0,
            "value": "body"
        },
        {
            "node": 1,
            "type": "splice",
            "feat": 2,
            "index": 0,
            "addNodes": [
                54
            ]
        },
		
[...]
 skipped long list of nodes [...]


        {
            "node": 54,
            "type": "put",
            "key": "width",
            "feat": 12,
            "value": "100%"
        }
    ],
    "timings": [
        0,
        -1
    ],
    "Vaadin-Security-Key": "f815b80a-a2bf-4479-8326-958b5f38a284",
    "Vaadin-Push-ID": "b59f63c3-e2bc-4f94-9e3b-93478b3d7ce3"
};	var config = {
    "frontendUrlEs6": "context://frontend/",
    "frontendUrlEs5": "context://frontend/",
    "versionInfo": {
        "vaadinVersion": "1.0.4",
        "atmosphereVersion": "2.4.24.vaadin1"
    },
    "sessExpMsg": {
        "caption": null,
        "message": null,
        "url": null
    },
    "contextRootUrl": "./",
    "debug": true,
    "requestTiming": true,
    "heartbeatInterval": 300,
    "v-uiId": 0
};	config.uidl = uidl;    window.Vaadin.Flow.initApplication("ROOT-2521314", config);})();//]]></script><script type="text/javascript" defer src="./VAADIN/static/client/client-CAF80B1F92BD4481C6D5EC01BC2D55AE.cache.js"></script><link rel="import" href="./frontend/bower_components/vaadin-button/src/vaadin-button.html"><link rel="import" href="./frontend/bower_components/vaadin-ordered-layout/src/vaadin-horizontal-layout.html"><link rel="import" href="./frontend/bower_components/vaadin-ordered-layout/src/vaadin-vertical-layout.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/color.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/typography.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/sizing.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/spacing.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/style.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/icons.html"><script id="_theme-header-injection">
function _inlineHeader(tag, content){
var customStyle = document.createElement(tag);
customStyle.innerHTML= content;
var firstScript=document.head.querySelector('script');
document.head.insertBefore(customStyle,firstScript);
}
_inlineHeader('custom-style','<style include="lumo-color lumo-typography"></style>');
document.head.removeChild(document.getElementById('_theme-header-injection'));
</script></head><body><noscript>You have to enable javascript in your browser to use this web site.</noscript></body></html>

Something seems to be missing that applies this code to a proper HTML rendering.

Hi Martin,
I’m also to trying to run WITHOUT using mvn jetty:run to start the Jetty server. I am at a point where I see the same thing you do: a blank web page with source similar to yours. Have you been able to figure out what the problem is?

Hi George,
unfortunately not so far.
I suppose that some piece of Vaadin framework JavaScript code is not loaded to the web page which would apply the change entries to the DOM.
So my guess is that some kind of path mapping/routing to the Jetty server is needed to load the Vaadin bib.
But I could not find out yet, how to do that.

The crucial part seems to be

<script type="text/javascript" defer src="./VAADIN/static/client/client-CAF80B1F92BD4481C6D5EC01BC2D55AE.cache.js"></script><link rel="import" href="./frontend/bower_components/vaadin-button/src/vaadin-button.html"><link rel="import" href="./frontend/bower_components/vaadin-ordered-layout/src/vaadin-horizontal-layout.html"><link rel="import" href="./frontend/bower_components/vaadin-ordered-layout/src/vaadin-vertical-layout.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/color.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/typography.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/sizing.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/spacing.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/style.html"><link rel="import" href="./frontend/bower_components/vaadin-lumo-styles/icons.html">

at the end of the head block.

After loading the initial page the subsequent get requests like

Request(GET //127.0.0.1:8080/VAADIN/static/client/client-CAF80B1F92BD4481C6D5EC01BC2D55AE.cache.js)@12688d8d

and

Request(GET //127.0.0.1:8080/frontend/bower_components/vaadin-button/src/vaadin-button.html)@766b35ec

etc.

just return a

HTTP/1.1 404 Not Found
Date: Wed, 28 Nov 2018 12:57:39 GMT
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html;charset=iso-8859-1

How has Jetty to be configured to route these requests to the Vaadin code that can handle those content?

Hi Martin,
Something basic and wrong in the way you or I are configuring webAppContext?

This is what I tried-

public final class WebServerContainer
{
	public static final String WEBAPP_CLASSES_DIR =
			System.getenv("MAVEN_PROJECTROOT") +
			"/target/classes/com/gsoler/vaadin/flow/jetty/test";
			
	...
				
    /** Start a Jetty Web Server on http://localhost:webServerPort */
    public final void startJetty()
    {
		...
		final WebAppContext webAppContext =
			new WebAppContext(WEBAPP_CLASSES_DIR, "/");
		webAppContext.setClassLoader(WebServerContainer.class.getClassLoader());
		webAppContext.setThrowUnavailableOnStartupException(true);
		webAppContext.setInitParameter("HelloWorldUI", HelloWorldUI.class.getCanonicalName());

		final MyServlet myServlet = new MyServlet();
		final ServletHolder servletHolder = new ServletHolder(myServlet);
		webAppContext.addServlet(servletHolder, "/*");

		// provide servlet annotation scanning for containers and web app jars by searching for @* annotation patterns
		AnnotationConfiguration annotationConfig = new AnnotationConfiguration();
		try { annotationConfig.configure(webAppContext); }
		catch(Exception ex)
		{
			System.err.println(annotationConfig);
			System.err.println(webAppContext);
			System.err.println("webAppContext configuration failed: " + ex.toString());
		}
		...
		this.jettyServer.start();
	}
	...
}

@VaadinServletConfiguration(ui = HelloWorldUI.class, productionMode = false)
public class MyServlet extends VaadinServlet
{
...
    @Override
    protected void servletInitialized() throws ServletException
    {
	...
	}
...
}

Andrei Bondarev was kind to post an example (scala?) in this thread: https://vaadin.com/forum/thread/17311276/17414768

I tried it but I still get the same null pointer exception in webAppContext, notice the debug prints, there’s something not initialized in o.e.j.w.WebAppContext {/,null,UNAVAILABLE}

org.eclipse.jetty.annotations.AnnotationConfiguration@5f058f00

o.e.j.w.WebAppContext@192d43ce{/,null,UNAVAILABLE}{/Users/soler/projects/maven_projects/vaadin/flow-jetty-test/target/classes/com/gsoler/vaadin/flow/jetty/test}

webAppContext configuration failed: java.lang.NullPointerException

RouteRegistryInitializer: javax.servlet.ServletException: Exception while registering Routes on servlet startup
Initializing servlet session: com.vaadin.flow.server.VaadinSession@722a5639

The browser javascript console shows several messages like

GET http://zeus.local:8080/VAADIN/static/client/client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js net::ERR_ABORTED 404 (Not Found)

Hi George,
I did something similar. It is still a bit experimental stuff and some may be removed later. It’s what I found on the internet in different forums.

		Server server = new Server(SERVER_SOCKET);

		Configuration.ClassList classlist = Configuration.ClassList.setServerDefault(server);
		classlist.addAfter(
				"org.eclipse.jetty.webapp.FragmentConfiguration",
				"org.eclipse.jetty.plus.webapp.EnvConfiguration",
				"org.eclipse.jetty.plus.webapp.PlusConfiguration");
		classlist.addBefore(
				"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
				"org.eclipse.jetty.annotations.AnnotationConfiguration");

		WebAppContext context = new WebAppContext();
		context.setContextPath("/");
		server.setHandler(context);

		context.setAttribute(
				"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
				// ".*/[^/]
*servlet-api-[^/]
*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]
*taglibs.*\\.jar$");
				".*/");

		File warFile = new File("../ray.base.showcase.vaadin10/bin");
		if (!warFile.exists()) {
			throw new RuntimeException("Unable to find WAR File: " + warFile.getAbsolutePath());
		}
		context.setWar(warFile.getAbsolutePath());
		
		ServletHolder servletHolder = new ServletHolder(ShowCaseVaadinServlet.class);
		servletHolder.setInitParameter("filterMappingUrlPattern", "/VAADIN/*");
		context.addServlet(servletHolder, "/VAADIN/*");
		
		servletHolder = new ServletHolder(ShowCaseVaadinServlet.class);
		servletHolder.setInitParameter("filterMappingUrlPattern", "/*");
		context.addServlet(servletHolder, "/*");
		
		server.setStopAtShutdown(true);
		server.start();

I checked the Vaadin10 jars and found out that the “missing” files are all contained.
So my missing VAADIN/static/client/client-CAF80B1F92BD4481C6D5EC01BC2D55AE.cache.js is contained in flow-client-1.0.4.jar
And my missing frontend/bower_components/vaadin-button/src/vaadin-button.html is contained in vaadin-button-2.0.1.jar
Debugging through the Jetty code I saw that requesting these files a new instance of my Vaadin servlet is created and accessed.

I still can’t say why these files are not found. Some configuration seems to be missing.

I am a step further. As I found out all the “missing” stuff are static files contained in several Vaadin JARs. Because they have to be delivered by Jetty my idea was that they have to be part of the web application.
So I extracted all the js and html files from the Vaadin JARs and placed them in my war folder (“…/ray.base.showcase.vaadin10/bin” in my above code snippet) which is the build folder of my Eclipse project.

This is not nice because all these files will be removed once I clean the Eclipse project. But this is how my Vaadin 10 application is running in an embedded Jetty instance.

Has anyone a better idea to provide the static Vaadin files to my war application without extracting them from the JAR files?

Thank you Martin.
I agree extracting the js files and copying them etc is not ideal. There has got to be a better way to get embedded Jetty to play with Vaadin. I think if we could figure out how to properly configure WebAppContext maybe we don’t need to do all this cumbersome copying etc.