mirror of
https://github.com/ethauvin/rife2.git
synced 2025-05-01 11:08:11 -07:00
223 lines
7.5 KiB
Markdown
223 lines
7.5 KiB
Markdown
<p align="center"><img src="https://github.com/gbevin/rife2/raw/main/rife2_logo.png" width="200"></p>
|
|
|
|
# Welcome
|
|
|
|
RIFE2 is a full-stack, no-declaration, framework to quickly and effortlessly create web applications with modern Java.
|
|
|
|
RIFE2 is built on the foundations of the original RIFE framework that was popular from 2002-2010.
|
|
Since then, the world and Java have changed and many of the original RIFE APIs can finally be replaced with pure Java, no-XML, no-YAML, leaving only type-safe expressive code.
|
|
|
|
**This is a quick tutorial, the [full documentation](https://github.com/gbevin/rife2/wiki)
|
|
contains a lot more information.**
|
|
|
|
_NOTE: The documentation and this readme are work-in-progress_
|
|
|
|
## Hello World Example
|
|
|
|
This is how you get started with a `Hello World` site.
|
|
|
|
```java
|
|
public class HelloWorld extends Site {
|
|
public void setup() {
|
|
get("/hello", c -> c.print("Hello World"));
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
new Server().start(new HelloWorld());
|
|
}
|
|
}
|
|
```
|
|
|
|
The `main` method spins up the integrated embedded Jetty server, so that you can immediately start coding. The same `HelloWorld` class can be added as a
|
|
parameter value to your `web.xml`, requiring absolute no changes to your code between development and production.
|
|
|
|
Out-of-container testing is a first-class citizen in RIFE2, directly interacting with your `Site` class to simulate full request-response interactions,
|
|
without having to spin up a servlet container.
|
|
|
|
Let's test the example above with JUnit 5:
|
|
|
|
```java
|
|
class HelloTest {
|
|
@Test void verifyHelloWorld() {
|
|
var m = new MockConversation(new HelloWorld());
|
|
assertEquals("Hello World", m.doRequest("/hello").getText());
|
|
}
|
|
}
|
|
```
|
|
|
|
Here's an example snippet that should help you compile and run this example with Gradle.
|
|
Please make sure to adapt the artifact versions to the latest ones.
|
|
|
|
```kotlin
|
|
application {
|
|
mainClass.set("HelloWorld")
|
|
}
|
|
|
|
dependencies {
|
|
implementation("com.uwyn.rife2:rife2:0.9.1")
|
|
testImplementation("org.junit.jupiter:junit-jupiter:5.8.2")
|
|
testImplementation("org.jsoup:jsoup:1.15.3")
|
|
runtimeOnly("org.eclipse.jetty:jetty-server:11.0.12")
|
|
runtimeOnly("org.eclipse.jetty:jetty-servlet:11.0.12")
|
|
}
|
|
```
|
|
|
|
RIFE2 doesn't publish dependencies for _jsoup_ nor _Jetty_ because neither of them should be packaged with a production deployment.
|
|
|
|
You will want to:
|
|
* depend on `org.jsoup:jsoup` if you want to parse HTML pages in RIFE2's web testing API
|
|
* depend on `org.eclipse.jetty` if you're launching the embedded server like in these examples
|
|
|
|
RIFE2 also ships with example Gradle projects that should help you get set up quickly.
|
|
|
|
Please take a look here:
|
|
* Hello World app : https://github.com/gbevin/rife2/tree/main/app
|
|
* Run Hello World standalone : https://github.com/gbevin/rife2/tree/main/standalone
|
|
* Package Hello World war : https://github.com/gbevin/rife2/tree/main/war
|
|
|
|
Once you've got everything set up, give it a try and visit [http://localhost:8080/hello](http://localhost:8080/hello)
|
|
|
|
## Type-safe Links and URLs
|
|
|
|
One of the most brittle aspects of web application development is typing links and URLs as text literals, without anything guaranteeing they remain correct
|
|
when your routes change or when you deploy your application in different web application contexts. RIFE2's routing API allows all your application links to be
|
|
generated correctly without any effort on your behalf.
|
|
|
|
Let's add a new route that contains an HTML link towards the previous Hello World route.
|
|
|
|
You can see that routes don't have to be created inside the `setup()` method, but can also be created as part of your `Site`'s construction,
|
|
allowing the routes to be stored in fields.
|
|
|
|
```java
|
|
public class HelloLink extends Site {
|
|
Route hello = get("/hello", c -> c.print("Hello World"));
|
|
Route link = get("/link", c-> c.print("<a href='" + c.urlFor(hello) + "'>Hello</a>"));
|
|
|
|
public static void main(String[] args) {
|
|
new Server().start(new HelloLink());
|
|
}
|
|
}
|
|
```
|
|
|
|
We can now test this as such:
|
|
|
|
```java
|
|
class HelloTest {
|
|
@Test void verifyHelloLink() {
|
|
var m = new MockConversation(new HelloLink());
|
|
assertEquals("Hello World", m.doRequest("/link")
|
|
.getParsedHtml().getLinkWithText("Hello")
|
|
.follow().getText());
|
|
}
|
|
}
|
|
```
|
|
|
|
## Bidirectional Logic-Less Templates
|
|
|
|
The main impetus that had me resume work on RIFE2, was the unique template engine.
|
|
|
|
RIFE2's templates contain two main concepts:
|
|
* *values* - that can be filled in with content and data
|
|
* *blocks* - that will be stripped away and that provide content snippets
|
|
|
|
Your Java code will compose the final layout by assigning and appending blocks, and by putting data into values.
|
|
Let's rewrite the `HelloLink` example above with a template.
|
|
|
|
In this example, no template manipulation is done in Java yet.
|
|
|
|
Instead, it introduces the `{{v route:hello/}}` value tag, which will automatically be replaced with the URL of the route that is available with that field name in your active `Site`.
|
|
|
|
```java
|
|
public class HelloTemplate extends Site {
|
|
Route hello = get("/hello", c -> c.print("Hello World"));
|
|
Route link = get("/link", c-> c.print(c.template("HelloTemplate")));
|
|
|
|
public static void main(String[] args) {
|
|
new Server().start(new HelloTemplate());
|
|
}
|
|
}
|
|
```
|
|
With `HelloTemplate.html` being:
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<body>
|
|
<a href="{{v route:hello/}}">Hello</a>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
Note that RIFE2 internally transforms your templates into Java classes by generating heavily optimized bytecode.
|
|
|
|
This happens on-the-fly during development. For production, templates can be pre-compiled, making them incredibly fast.
|
|
|
|
## Template Manipulation
|
|
|
|
Let's change the example some more and create a single route that can respond to both `get` and `post` requests.
|
|
|
|
* the `get` request will display a form with a single button to click.
|
|
* the `post` request will receive the form's submission and display `Hello World`.
|
|
|
|
```java
|
|
public class HelloForm extends Site {
|
|
Route hello = route("/hello", c -> {
|
|
var t = c.template("HelloForm");
|
|
switch (c.method()) {
|
|
case GET -> t.setBlock("content", "form");
|
|
case POST -> t.setBlock("content", "text");
|
|
}
|
|
c.print(t);
|
|
});
|
|
|
|
public static void main(String[] args) {
|
|
new Server().start(new HelloForm());
|
|
}
|
|
}
|
|
```
|
|
|
|
With `HelloForm.html` being:
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<body>
|
|
<!--v content/-->
|
|
<!--b form-->
|
|
<form action="{{v route:hello/}}" method="post" name="hello">
|
|
<input type="submit" name="Submit">
|
|
</form>
|
|
<!--/b-->
|
|
<!--b text--><p id="greeting">Hello World</p><!--/b-->
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
You can see that the template contains all the pieces to create both pages:
|
|
|
|
* the value named `content`
|
|
* the block named `form`
|
|
* the block named `text`
|
|
|
|
In Java, we simply assign either block to the value, depending on what we want to display.
|
|
|
|
Another benefit is that RIFE2's template tags can be HTML comments, making them completely invisible.
|
|
This allows you to work on your HTML design as usual and preview the template file with a regular browser.
|
|
|
|
Finally, let's include a test for this functionality:
|
|
|
|
```java
|
|
class HelloTest {
|
|
@Test void verifyHelloForm() {
|
|
var m = new MockConversation(new HelloForm());
|
|
var r = m.doRequest("/hello").getParsedHtml()
|
|
.getFormWithName("hello").submit();
|
|
assertEquals("Hello World", r.getParsedHtml()
|
|
.getDocument().body()
|
|
.getElementById("greeting").text());
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
**Read more in the [full documentation](https://github.com/gbevin/rife2/wiki).**
|