A server-state reactive Java web framework for building real-time user interfaces and UI components.

Overview

RSP

javadoc maven version

About

RSP is a lightweight modern Java server-state web framework.

A popular approach for a Java backend based web UI is to build the client-side with tools like React or Vue and fetch the data from the server with some kind of remote API.

JavaScript client programming is extremely powerful, but this scheme introduces a lot of complexity. Any change made on the client-side potentially needs to be reflected on the API and the server-side. The project's build requires on-boarding non-Java dependency management and build tools.

RSP aims for developing the web UI in Java while keeping external dependencies and JavaScript usage to the minimum.

With RSP, after loading an initial page HTML, the browser feeds events to the server and updates the presentation to the incoming diff commands. The page's state is maintained on the server.

As the result:

  • coding and debugging the UI is just coding in plain Java and debugging Java;
  • fast initial page load no matter of the application's size;
  • your code always stays on your server;
  • SEO-friendly out of the box.

Maven

Use Java version 11 or newer.

Add the dependency:

    <dependency>
        <groupId>io.github.vadimvgroupId>
        <artifactId>rspartifactId>
        <version>0.7version>
    dependency>

Code examples

HTTP requests routing

An RSP application's initial page rendering request-response workflow consist of two explicitly defined phases:

  • routing an incoming request with a result of the page's immutable state object;
  • rendering this state object into the result HTTP response.

To dispatch the incoming request, create a Routing object and provide it as an application's constructor parameter:

db.getArticles().thenApply(articles -> State.ofArticles(articles))), get("/articles/:id", (__, id) -> db.getArticle(id).thenApply(article -> State.ofArticle(article))), get("/users/:id", (__, id) -> db.getUser(id).thenApply(user -> State.ofUser(user))), post("/users/:id(^\\d+$)", (req, id) -> db.setUser(id, req.queryParam("name")).thenApply(result -> State.userWriteSuccess(result)))); }">
    import static rsp.html.RoutingDsl.*;
    ...
    final App<State> app = new App<>(route(), render());
    ...
    private static Route<State> route()
        final var db = new Database();
        return concat(get("/articles", req -> db.getArticles().thenApply(articles -> State.ofArticles(articles))),
                      get("/articles/:id", (__, id) -> db.getArticle(id).thenApply(article -> State.ofArticle(article))),
                      get("/users/:id", (__, id) -> db.getUser(id).thenApply(user -> State.ofUser(user))),
                      post("/users/:id(^\\d+$)", (req, id) -> db.setUser(id, req.queryParam("name")).thenApply(result -> State.userWriteSuccess(result))));
    }

During a dispatch, routes verified one by one for a matching HTTP method and path pattern. Route path patterns can include zero, one or two path-variables, possibly combined with regexes and the wildcard symbol "*". The matched variables values become available as the correspondent handler functions String parameters alongside with the request details object. The route's handler function should return a CompletableFuture of the page's state:

CompletableFuture.completedFuture(State.ofUsers(List.of(user1, user2))))">
    get("/users/*", req -> CompletableFuture.completedFuture(State.ofUsers(List.of(user1, user2))))

If needed, extract a paths-specific routing section:

State.ofArticles(articles))), path("/articles/:id", id -> db.getArticle(id).thenApply(article -> State.ofArticle(article))); }">
    final Route<HttpRequest, State> routes = concat(get(__ -> paths()),
                                                    any(State.pageNotFound()));
    
    private static PathRoutes<State> paths() {
         return concat(path("/articles", db.getArticles().thenApply(articles -> State.ofArticles(articles))),
                       path("/articles/:id", id -> db.getArticle(id).thenApply(article -> State.ofArticle(article)));
    }

Use match() DSL function routes to implement custom matching logic, for example:

CompletableFuture.completedFuture(State.of(req.queryParam("name"))))">
    match(req -> req.queryParam("name").isPresent(), req -> CompletableFuture.completedFuture(State.of(req.queryParam("name"))))

The any() route matches every request.

HTML markup rendering Java DSL

On the rendering phase a state object to be converted to HTML.

RSP provides the Java internal domain-specific language (DSL) for declarative definition of an HTML page markup as functions composition.

For example, to re-write the HTML fragment below:

This is a paragraph

Some dynamic text

">
>
<html>    
    <body>
        <h1>This is a headingh1>
        <div class="par">
            <p>This is a paragraphp>
            <p>Some dynamic textp>
        div>
    body>
html> 

provide the Java code:

    import static rsp.html.HtmlDsl.*;
    ...
    public Component<State> render() {
        return s -> html(
                      body(
                           h1("This is a heading"),
                           div(attr("class", "par"), 
                               p("This is a paragraph"),
                               p(s.get().text)) // adds a paragraph with a text from the state object's 'text' field
                          ) 
                    );
    }

where:

  • HTML tags are represented by the rsp.html.HtmlDsl class' methods with same names, e.g.
    translates to div()
  • HTML attributes are represented by the rsp.html.HtmlDsl.attr(name, value) function, e.g. class="par" translates to attr("class", "par")
  • The lambda parameter's s.get() method reads the current state snapshot

The utility of() DSL function renders a Stream of objects, e.g. a list, or a table rows:

    import static rsp.html.HtmlDsl.*;
    ...
    s -> ul(of(s.get().items.stream().map(item -> li(item.name))))

An overloaded variant of of() accepts a CompletableFuture:

    final Function<Long, CompletableFuture<String>> service = userDetailsService(); 
    ...
         // let's consider that at this moment we know the current user's Id
    s -> div(of(service.apply(s.get().user.id).map(str -> text(str))))

Another overloaded of() function takes a Supplier as its argument and allows inserting code fragments with imperative logic.

    import static rsp.html.HtmlDsl.*;
    ...
    s -> of(() -> {
                     if (s.get().showInfo) {
                         return p(s.get().info);
                     } else {
                         return p("none");
                     }       
                 })

The when() DSL function conditionally renders (or not) an element:

    s -> when(s.get().showLabel, span("This is a label"))

Page state model

Model an RSP application page's state as a finite state machine (FSM). An HTTP request routing resolves an initial state. Page events, like user actions or timer events trigger state transitions.

The following example shows how a page state can be modelled using records, sealed interfaces and pattern matching in Java 17:

usersView() { return s -> span("Users list:" + s.get()); }">
    sealed interface State permits UserState, UsersState {}
    record UserState(User user) implements State {}
    record UsersState(List<User> users) implements State {}
    
    record User(long id, String name) {}

    /**
     * An event handler, makes the page's FSM transition.
     */
    public static void userSelected(UseState<State> s, User user) {
        s.accept(new UserState(user));
    }

    /**
     * The page's renderer, called by the framework as a result of a state transition.
     */
    static Component<State> render() {
        return s -> switch (s.get()) {
            case UserState  state  -> userView().render(state);
            case UsersState state  -> usersView().render(state);
        };
    }

    private static Component<UserState> userView() { return s -> span("User:" + s.get()); }
    private static Component<UsersState> usersView() { return s -> span("Users list:" + s.get()); }

Single-page application

RSP supports two types of web pages:

  • Single-page application (SPA) with establishing the page's live session and keeping its state on the server
  • Plain detached pages

An RSP web application can contain a mix of both types. For example, an admin part can be a single-page application page, and the client facing part made of plain pages.

The type of page to be rendered is determined by the page's head tag DSL function.

The head() function creates an HTML page head tag for an SPA. If the head() is not present in the page's markup, the simple SPA-type header is added automatically. This type of header injects a script, which establishes a WebSocket connection between the browser's page and the server and enables reacting to the browser events.

To respond to browser events register a page DOM event handler by adding an on(eventType, handler) to an HTML tag:

{ System.out.println("Clicked " + s.get().counter + " times"); s.accept(new State(s.get().counter + 1)); })); ... static class State { final int counter; State(int counter) { this.counter = counter; } }">
    s -> a("#", "Click me", on("click", ctx -> {
                System.out.println("Clicked " + s.get().counter + " times");
                s.accept(new State(s.get().counter + 1));
            }));
    ...
    static class State { final int counter; State(int counter) { this.counter = counter; } }

When an event occurs:

  • the page sends the event data message to the server
  • the system fires its registered event handler's Java code.

An event handler's code usually sets a new application's state snapshot, calling one of the overloaded UseState.accept() methods on the application state accessor.

A new set state snapshot triggers the following sequence of actions:

  • the page's virtual DOM re-rendered on the server
  • the difference between the current and the previous DOM trees is calculated
  • the diff commands sent to the browser
  • the page's JavaScript program updates the presentation

The event handler's EventContext class parameter has a number of utility methods.

One of these methods allows access to client-side document elements properties values by elements references.

{ ctx.props(inputRef).getString("value").thenAccept(value -> System.out.println("Input's value: " + value)); }))">
    final ElementRef inputRef = createElementRef();
    ...
    input(inputRef,
          attr("type", "text")),
    a("#", "Click me", on("click", ctx -> {
            ctx.props(inputRef).getString("value").thenAccept(value -> System.out.println("Input's value: " + value));     
    }))

A reference to an object also can be created on-the-fly using RefDefinition.withKey() method.

There is the special window() reference for the page's window object.

The window().on(eventType, handler) method registers a window event handler:

{ System.out.println("window clicked"); }), ... )">
    html(window().on("click", ctx -> {
            System.out.println("window clicked");
        }),
        ...
        )

Some types of browser events, like a mouse move, may fire a lot of invocations. Sending all these notifications over the network and processing them on the server side may cause the system's overload. To filter the events before sending use the following event object's methods:

  • throttle(int timeFrameMs)
  • debounce(int waitMs, boolean immediate)
{ System.out.println("A throtteld page scroll event"); }).throttle(500), ... )">
    html(window().on("scroll", ctx -> {
            System.out.println("A throtteld page scroll event");
        }).throttle(500),
        ...
        )

The context's EventContext.eventObject() method reads the event's object as a JSON-like data structure:

{ // Prints the submitted form's input field value System.out.println(ctx.eventObject()); }), input(attr("type", "text"), attr("name", "val")), input(attr("type", "button"), attr("value", "Submit")) )">
    form(on("submit", ctx -> {
            // Prints the submitted form's input field value
            System.out.println(ctx.eventObject());
         }),
        input(attr("type", "text"), attr("name", "val")),
        input(attr("type", "button"), attr("value", "Submit"))
    )

Events code runs in a synchronized sections on a live page session state container object.

Plain HTML pages

Using plainHead() function instead of head() renders the markup with the head tag without injecting of this script resulting in a plain detached HTML page.

The statusCode() and addHeaders() methods enable to change result response HTTP status code and headers. For example:

    s -> html(   
              plainHead(title("404 page not found")),
              body(
                   div(
                       p("404 page not found")
                  ) 
                )
            ).statusCode(404);

UI Components

Pages are composed of components. A component is a Java class which implements the Component interface.

s.accept(new ButtonState()))); } public static class ButtonState {}">
    public static Component<ButtonState> buttonComponent(String text) {
        return s -> input(attr("type", "button"),
                           attr("class", "button"),
                           attr("value", text),      
                           on("click", ctx -> s.accept(new ButtonState())));
        
    }
    public static class ButtonState {}

The render() method of a component usually invokes render() methods of its descendant components, providing as parameters:

  • a descendant's component's state object, normally a part of the application's state object tree
  • a listener's code, a Consumer implementation, which propagates the new state change from a child component to its parent, up to the root component's context
s.accept(new ConfimPanelState(true))), buttonComponent("Cancel").render(new ButtonState(), buttonState -> s.accept(new ConfimPanelState(false))); } public static class ConfirmPanelState { public final boolean confirmed; public ConfirmPanelState(boolean confirmed) { this.confirmed = confirmed; } }">
    ...
    public static Component<ConfirmPanelState> confirmPanelComponent(String text) {
        return s -> div(attr("class", "panel"),
                         span(text),
                         buttonComponent("Ok").render(new ButtonState(), 
                                                      buttonState -> s.accept(new ConfimPanelState(true))),
                         buttonComponent("Cancel").render(new ButtonState(), 
                                                          buttonState -> s.accept(new ConfimPanelState(false)));
        
    }
    public static class ConfirmPanelState {
        public final boolean confirmed;
        public ConfirmPanelState(boolean confirmed) { this.confirmed = confirmed; }
    }

An application's top-level Component is the root of its component tree.

Navigation bar URL path

During a Single Page Application session, the current app state mapping to the browser's navigation bar path can be configured using the stateToPath method of an App object:

    final App<State> app = new App<>(route(),
                                     pages())  //  If the details are present, set the path to /{name}/{id} or set it to /{name}
            .stateToPath((state, prevPath) -> state.details.map(details -> Path.absolute(state.name, Long.toString(details.id)))
                                                           .or(Path.absolute(state.name)));

If not configured, the default state-to-path mapping sets an empty path for any state.

Page lifecycle events

Provide an implementation of the PageLifecycle interface as a parameter on an application's constructor. This allows to listen to the SPA page's lifecycle events:

  • before the page is created
  • after the page is closed
    final PageLifeCycle<Integer> plc = new PageLifeCycle.Default<Integer>() {
        @Override
        public void beforeLivePageCreated(QualifiedSessionId sid, UseState<Integer> useState) {
            final Thread t = new Thread(() -> {
                try {
                    Thread.sleep(10_000);
                    synchronized (useState) {
                        useState.accept(useState.get() + 1);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    };
    
    final App<Optional<FullName>> app = new App<>(route(), pages()).pageLifeCycle(plc);
    ...

Add these listeners, for example, when you need to subscribe to some messages stream on a page live session creation and unsubscribing when the page closes.

Application and server's configuration

Provide an instance of the rsp.AppConfig class as the parameter to the config method of an App object:

    final var app = new App(routes(), rootComponent()).config(AppConfig.DEFAULT);

A web server's rsp.jetty.JettyServer class constructor accepts parameters like the application's web context base path, an optional static resources' handler and a TLS/SSL connection's configuration:

    final var staticResources = new StaticResources(new File("src/main/java/rsp/tetris"), "/res/*");
    final var sslConfig = SslConfiguration("/keysore/path", "changeit");
    final var server = new JettyServer(8080, "/base", app, staticResources, sslConfig);
    server.start();
    server.join();

Logging

By default, internally, the project uses a console logger. Set the rsp.log.level system property to change the application's log level, for example -Drsp.log.level=trace.

To use an Slf4j logger instead of the default console logger, provide the Slf4jLogReporting logger implementation to the AppConfig application configuration.

To enable client-side detailed diagnostic data exchange logging, enter in the browser's console:

  RSP.setProtocolDebugEnabled(true)

Schedules

The EventContext.schedule() and EventContext.scheduleAtFixedRate() methods allow submitting of a delayed or periodic action that can be cancelled. A timer reference parameter may be provided when creating a new schedule. Later this reference could be used for the schedule cancellation. Scheduled tasks will be executed in threads from the internal thread pool, see the synchronized versions of accept() and acceptOptional() methods of the live page object accepting lambdas as parameters.

c.scheduleAtFixedRate(() -> System.out.println("Timer event")), TIMER_0, 0, 1, TimeUnit.SECONDS))), button(attr("type", "button"), text("Stop"), on("click", c -> c.cancelSchedule(TIMER_0)))">
    final static TimerRef TIMER_0 = TimerRef.createTimerRef();
    ...
    button(attr("type", "button"),
           text("Start"),
           on("click", c -> c.scheduleAtFixedRate(() -> System.out.println("Timer event")), TIMER_0, 0, 1, TimeUnit.SECONDS))),
    button(attr("type", "button"),
               text("Stop"),
               on("click", c -> c.cancelSchedule(TIMER_0)))

How to build the project and run tests

To build the project from the sources:

$ mvn clean package

To run all the tests:

$ mvn clean test -Ptest-all
You might also like...

True Object-Oriented Java Web Framework

True Object-Oriented Java Web Framework

Project architect: @paulodamaso Takes is a true object-oriented and immutable Java8 web development framework. Its key benefits, comparing to all othe

Dec 23, 2022

An Intuitive, Lightweight, High Performance Full Stack Java Web Framework.

mangoo I/O mangoo I/O is a Modern, Intuitive, Lightweight, High Performance Full Stack Java Web Framework. It is a classic MVC-Framework. The foundati

Oct 31, 2022

A simple expressive web framework for java. Spark has a kotlin DSL https://github.com/perwendel/spark-kotlin

Spark - a tiny web framework for Java 8 Spark 2.9.3 is out!! Changeset dependency groupIdcom.sparkjava/groupId artifactIdspark-core/a

Dec 29, 2022

The Grails Web Application Framework

Build Status Slack Signup Slack Signup Grails Grails is a framework used to build web applications with the Groovy programming language. The core fram

Jan 5, 2023

jetbrick web mvc framework

jetbrick-webmvc Web MVC framework for jetbrick. Documentation http://subchen.github.io/jetbrick-webmvc/ Dependency dependency groupIdcom.githu

Nov 15, 2022

πŸš€ The best rbac web framework. base on Spring Boot 2.4、 Spring Cloud 2020、 OAuth2 . Thx Give a star

πŸš€ The best rbac web framework. base on Spring Boot 2.4、 Spring Cloud 2020、 OAuth2 . Thx Give a star

Jan 8, 2023

Realtime Client Server Framework for the JVM, supporting WebSockets with Cross-Browser Fallbacks

Realtime Client Server Framework for the JVM, supporting WebSockets with Cross-Browser Fallbacks

Welcome to Atmosphere: The Event Driven Framework supporting WebSocket and HTTP The Atmosphere Framework contains client and server side components fo

Jan 3, 2023

RESTEasy is a JBoss project that provides various frameworks to help you build RESTful Web Services and RESTful Java applications

RESTEasy RESTEasy is a JBoss.org project aimed at providing productivity frameworks for developing client and server RESTful applications and services

Dec 23, 2022

Java Web Toolkit

What is JWt ? JWt is a Java library for developing web applications. It provides a pure Java component-driven approach to building web applications, a

Jul 16, 2022
Comments
  • Make the google closure compiler an optional dependency

    Make the google closure compiler an optional dependency

    The google closure compiler is probably not needed for most dependent projects.

            <dependency>
                <groupId>com.google.javascript</groupId>
                <artifactId>closure-compiler</artifactId>
                <optional>true</optional>
            </dependency>
    

    Ideally you would put the JsCompiler in its own maven module but it looks like this is not a multi-module project.

    Just to give you an idea how bloated the closure compiler (it uses basically every google oss project) is I did this to your todo-list app:

            <dependency>
                <groupId>io.github.vadimv</groupId>
                <artifactId>rsp</artifactId>
                <version>0.2</version>
                <exclusions>
                  <exclusion>
                    <groupId>com.google.javascript</groupId>
                    <artifactId>closure-compiler</artifactId>
                  </exclusion>
                </exclusions>
            </dependency>
    

    And it went from a 16 MB jar to 4 MB jar with dependencies.

    opened by agentgt 1
  • Korolev Java DSL

    Korolev Java DSL

    I found your mention about Korolev project. Years ago I tried to create Java wrapper for Korolev. Unfortunately I didn't get any response from Java community, and abandoned the branch. If you are interested in the approach we can join our forces. I sure Scala nature of Korolev can be completely hidden.

    https://github.com/fomkin/korolev/blob/java-dsl/examples/java-dsl-example/src/main/java/TodoListRenderer.java

    opened by fomkin 0
Releases(v0.7)
Owner
Vadim Vashkevich
Vadim Vashkevich
ZK is a highly productive Java framework for building amazing enterprise web and mobile applications

ZK ZK is a highly productive Java framework for building amazing enterprise web and mobile applications. Resources Documentation Tutorial ZK Essential

ZK 375 Dec 23, 2022
Firefly is an asynchronous web framework for rapid development of high-performance web application.

What is Firefly? Firefly framework is an asynchronous Java web framework. It helps you create a web application Easy and Quickly. It provides asynchro

Alvin Qiu 289 Dec 18, 2022
A Java Framework for Building Bots on Facebook's Messenger Platform.

Racter A Java Framework for Building Bots on Facebook's Messenger Platform. Documentation Installation To add a dependency using Maven, use the follow

Ahmed 18 Dec 19, 2022
Ninja is a full stack web framework for Java. Rock solid, fast and super productive.

_______ .___ _______ ____. _____ \ \ | |\ \ | | / _ \ / | \| |/ | \ | |/ /_\ \ / | \

Ninja Web Framework 1.9k Jan 5, 2023
The modular web framework for Java and Kotlin

∞ do more, more easily Jooby is a modern, performant and easy to use web framework for Java and Kotlin built on top of your favorite web server. Java:

jooby 1.5k Dec 16, 2022
A web MVC action-based framework, on top of CDI, for fast and maintainable Java development.

A web MVC action-based framework, on top of CDI, for fast and maintainable Java development. Downloading For a quick start, you can use this snippet i

Caelum 347 Nov 15, 2022
Javalin - A simple web framework for Java and Kotlin

Javalin is a very lightweight web framework for Kotlin and Java which supports WebSockets, HTTP2 and async requests. Javalin’s main goals are simplicity, a great developer experience, and first class interoperability between Kotlin and Java.

David (javalin.io) 6.2k Jan 6, 2023
Vaadin 6, 7, 8 is a Java framework for modern Java web applications.

Vaadin Framework Vaadin allows you to build modern web apps efficiently in plain Java, without touching low level web technologies. This repository co

Vaadin 1.7k Jan 5, 2023
Apache Wicket - Component-based Java web framework

What is Apache Wicket? Apache Wicket is an open source, java, component based, web application framework. With proper mark-up/logic separation, a POJO

The Apache Software Foundation 657 Dec 31, 2022
Micro Java Web Framework

Micro Java Web Framework It's an open source (Apache License) micro web framework in Java, with minimal dependencies and a quick learning curve. The g

Pippo 769 Dec 19, 2022