Showing posts with label vaadin. Show all posts
Showing posts with label vaadin. Show all posts

Saturday, October 8, 2022

Running a vaadin tomcat cluster behind a reverse proxy

Vaadin is full stack java framework to write web applications.

One of the interesting parts is, that you can write the UI completly in java, so you won't have to mess with different technologies and languages.

On the other side, it's easy to include webcomponents or write your own and connect them to your java application.

Vaadin also has a powerfull integrated push system, which allows you to push UI updates/notifications to the client when they are "ready". It's based on athmosphere and can benefit from websockets, but also works/falls back to standard http long polling http requests if nothing else works.

If you are using a single tomcat / servlet container instance, then you either expose that one to your customers or put some proxy in front of it. It's pretty standard and you find a lot of examples on how to do this with nginx or apache. You can either use plain http+websocket proxy, or (in the case of apache) also the ajp connector to the backend service.

But if you have multiple backend tomcat / sevlet containers running for redundacy/scaling/... then things get more complicated.

The vaadin framework is usually statefull, you you just can't route each new request to any one of your backend servers. You will have to always route the requests to the same servlet engine.

This is called "sticky session", since a session is sticking on the same backend server.

When you use nginx as the frontend/loadbalancer service, then you will get sticky sessions too, but only based in the ip (and port) of the client. https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/

This works fine as long as you users are spread over the world (or better use different IP addresses), the load is distributed amoung the backend servers.

But if you have a company application, which is for example used at two locations in the world, with each 50 users, the distribution of the requests can occur to route all to exaclty one of the backend servers and the others waiting for work to do.

This is because nginx uses the hash of the client public ip address, so all users behind the same ip will be routed to the same backend, which isn't what we intend.

Nginx also has the ability to route the requests based on a cookie you define (on in case of servlet engines is caled JSESSIONID). But that feature is not available in the free nginx version, it's premium feature requiring a pricy subscription. https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#enabling-session-persistence

So if you wan't to stay free of costs for the load balancer you have to choose something else.

There exist dedicated HA proxies for this, but we did choose to use the Apache webserver for this.

The basic confirguration for a apache reverse proxy is simple, a bit more complex with load balancing and even less documented for load balancing with websockets.

This is why this post exists, I did search (most of the?) internet for informations on how to achieve this, but only did find fragments of the solution and often with wrong parts in it.

So our solution does correctly proxy http/http2/websocket requests to the backend servers in a sticky way. The ssl/https configuration is not part of this post, but you can use standard ways for this.

This setup is valid for all Vaadin Flow 23.0 and 23.1 and 23.2. setups. For Vaadin 23.3 and later, the push endpoints have been simplified for a better lb configuration. https://github.com/vaadin/flow/issues/14641

So here what our config looks like, we will explaing the severals parts later:

# 1

ProxyRequests Off

#2
ProxyPass /images/ !
ProxyPass /.well-known/ !

#3

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/ [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*)       balancer://backend-ws/$1 [P,L]
 

#4
ProxyPass / balancer://backend/
ProxyPassReverse / balancer://backend/

#5

<Proxy balancer://backend>
    BalancerMember http://192.168.1.50:8080 route=backend1
    BalancerMember http://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>
 

#6
<Proxy balancer://backend-ws>
    BalancerMember ws://192.168.1.50:8080 route=backend1
    BalancerMember ws://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>

And in your tomcat server.xml on the backend servers:

<Engine name="Catalina" defaultHost="backend.service.ch" jvmRoute="backend1">

...

</Engine>

So lets explain the parts:

1. General proxy config

ProxyRequests Off

Make sure to have this in your config, otherwise your webserver can be missused to proxy any request to the internet, turning your server in an open proxy.

#2
ProxyPass /images/ !
ProxyPass /.well-known/ !

With these you can serve static content direcly from you apache webserver (As long as it has access to that content)

The .well-known this is usually needed when you use letsencrypt certificates for https

#3

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/ [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*)       balancer://backend-ws/$1 [P,L]

The roles above make sure to correctly handle http->websocket upgrade requests and send them to the websocket balancer.

Depending on you application/backend you will need to tune the rewrites, but these here work for a vaadin application.

It is known that a rewrite rule is not optimal from a performance point of view, but so far I know of no other solution, until Vaadin 24 will hopefully use a dedicated push/websocket endpoint.

#4
ProxyPass / balancer://backend/
ProxyPassReverse / balancer://backend/

Here we route the normal http and http2 requests to the http balancer. Please take care to include the trailing / after the backend, otherwise you will receive strange errors like "No protocol handler was valid for the URL /home (scheme 'balancer')" in your server error log

#5

<Proxy balancer://backend>
    BalancerMember http://192.168.1.50:8080 route=backend1
    BalancerMember http://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>
This is the load balancer to route the requests to the two backend servers, you can add more if you have more of them.

The stickysession indicates to use the JSESSIONID cookie to match the requests to the correct backend. The name of the route should match your jvmRoute entry in the server.xml file for each backend.

#6
<Proxy balancer://backend-ws>
    BalancerMember ws://192.168.1.50:8080 route=backend1
    BalancerMember ws://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>

Same as #5, but for the websocket requests.

As you see in the balancer definitions, the backend servers are connected via unencrypted http/ws. If you need to use https/wss toward the backend servers too, then you can just replace the backend server definitions with https/wss. But of course you will then also have to handle the certificates on tomcat side too.

Required apache modules for this to work:

Apache should use the event mpm if possible, for better handling of http2 and websokets. Other mpm might work, I have not tested them.

As for the modules themself, enable these:

http2 -> For http/2 of course

proxy -> General basic proxy funcionality

proxy_balancer -> To used balancers toward the backend

proxy_http -> For http 1.x proxy requests

proxy_http2 -> For http/2 proxy requests

proxy_wstunnel -> For websocket proxy stuff

rewrite -> To identify and redirect websocket requests to the ws balancer

lbmethod_byrequests -> type of loadbalancing to use

In debian you can just use a2enmod <module_name> to enable them, on other distributions when commands vary, but finally these modules must be active to have a full http/http2/ws load balancer for Vaadin.

This setup work for Vaadin 23.1.x, for Vaadin 24 there is a discussion going on about having dedicated endpoint for push/websockets. https://github.com/vaadin/flow/issues/14641#issuecomment-1266519119

And a documentation (still to be done) about Vaadin and reverse proxy setups

https://github.com/vaadin/docs/issues/1776#issuecomment-1272384234


Thursday, August 21, 2014

Using "related" fields/properties in vaadin tables with JPA containers

Vaadin is a Java framework for building modern web applications that look great, perform well and make you and your users happy.

It has a lot of features which help you in building data driven applications, without having to code everything yourself.
As with any powerfull frameworks you often come to a plcae where a "simple" thing isn't that simple to implement.
Perhaps the framework is just not prepared for that simple feature you wish to use, or you don't find the way to use it correctly.

Vaadin has containers which allow you do present data in forms and tables, without needing to code everything yourself.
There exist different types of containers, depending on your original data source, for example you can have SQL database, Bean objects and many others as a source.

With the JPA container, you can use to handle then whole data stuff with JPA.
So you could for example use hibernate or eclispelink to back your java objects in a sql database.

There is a whole chapter in the book of vaadin describing the JPA container.
When you have a object which has relations to other objects, then you can also specify the JPA container about such "related" properties or fields.
For example when you have a Person object, which has a relation to a country object, you can teel the JPA container about the additional fields available from the Country object.

// Have a persistent container
JPAContainer<Person> persons =
    JPAContainerFactory.make(Person.class, "book-examples");

// Add a nested property to a many-to-one property
persons.addNestedContainerProperty("country.name");
        
// Show the persons in a table, except the "country" column,
// which is an object - show the nested property instead
Table personTable = new Table("The Persistent People", persons);
personTable.setVisibleColumns(new String[]{"name","age",
                                           "country.name"});


// Have a nicer caption for the country.name column
personTable.setColumnHeader("country.name", "Nationality");


The Vaadin JPA container automagically knows to go via the object/database relation and retrieve the correct values.

When you use the filtering table add on available from the vaadin add ons, you can also implement filters on these additional fields.

For this you have to implement the FilterGenerator interface and then tell the table which properties are handled with this filter.
Of course your filter code must then generate the correct filter criterias.

@Override
public Container.Filter generateFilter(Object propertyId, Object value)
{
    if ("country.name".equals(propertyId))
    {
        if (value != null && value instanceof String)
        {
            return new Like("name", value.toString()+"%");
        }
    }
}


Thursday, January 23, 2014

Vaadin JPAContainer, Filterable table and related entities

In the web 2.0 framework Vaadin you have containers which provide data to be displayed in your application.
These containers are very flexible and can for example be a databasebackend, a JPA system or your own implementation.

In my past post I showed you how to implement filtering for related fields.

Vaadin also provides many data aware components, for example a table component.
The table component is very sophisticated and allows displaying huge amounts of data in the webbrowser. The table has a lazy loading system, so as only the visible parts of a table are retrieved from the backend and sent to the webbrowser.

There also exists a addon component which has built in filter and sort support.

When using the table with jpa container, then you have to use several tricks to allow filtering on related fields.

The first trick is to display the related fields in the table

For this the simplest way is to add these related fields to the main entity you display.
You don't have to store the property, it's enough for the JPA container when you have a getXXX() method.

That way it displays the additional properties in the table. You could also use this way to show calculated related fields in the table.

This could look like this:
public String getProjectInfos()
{
    return projectNr+" - "+name;
}


When you now display these in the table, you can also filter on that field.
But then, you will get a error message, telling you that the sql select did not find the field for the where condition.

For this to work, you have to use the second trick

You can build your own FilterGenerator which then builds the correct criterias for your tables and relations.

contentTable.setFilterGenerator(new MyFilterGenerator());

The filter generator has different methods, when you don't want to override them, you can just return NULL and then the default behaviour is done.

For us the interesting method is the generateFilter() method.
Here you can implement your own conditions.

If you for example wish to filter with the LIKE statement, then you can do it this way:

@Override
public Container.Filter generateFilter(Object propertyId, Object value)
{
    if ("contract".equals(propertyId))
    {
        if (value != null && value instanceof String)
        {
            return new Like("contractID", value.toString()+"%");
        }
    }
    return null;

}

To now filter on a related table, you can use the IN condition, which then builds the correct sql statements.

@Override
public Container.Filter generateFilter(Object propertyId, Object value)
{
       if ("contracts".equals(propertyId))
        {
            if (value != null && value instanceof String)
            {
                String  lsNr= (String) value;
                EntityManager em= ((MyVaadinUI)UI.getCurrent()).getEntityManager();
                TypedQuery<Shippings> tq= em.createNamedQuery("Shippings.findByLikeProjectNr", Shippings.class);
                tq.setParameter("projectNr", lsNr+"%");
                List<Shippings> rs= tq.getResultList();
                if (rs.isEmpty())
                {
                    return new IsNull("shipping");
                }
                else
                {
                    if (rs.size() > 2000)
                    {
                        Notification.show("To many entries", "\n\nMake more restrictions", Notification.Type.WARNING_MESSAGE);
                        return new IsNull("shipping");
                    }
                    else
                    {
                        return new In("shipping", rs);
                    }
                }
            }
        }

    return null;
}

So when the property contracts has some value, we filter the contracts by projectNr and use the resulting result set to specify as the IN criteria.

If you are still with me, then you probably have a compiler error when you try this code.
The reason is, that the default JPAContainer has no implementation of the IN criteria.
Unfortunally the design of the JPAContainer does not allow to expand the capabilities in that area.

Fortunally there exists a fork of the JPAContainer which just provides the required IN() clause.
You can download the sources and add them to your project.

The source can be found here: https://github.com/lelmarir/jpacontainer

You can learn how to show related / nested properties in this post.

Wednesday, April 27, 2011

Java server side connector for CKEditor

We just published our first version of a java connector for CKEditor V3.x

You can find it here: http://sourceforge.net/projects/jckconnector/

It allows you to integrate file browsing/linking in your java server application.
Technically it uses vaadin the webinterface to the user.

You can provide your own version of the file store and link store.

It's the alpha release with probably some security holes "included"