Skip to content

Commit

Permalink
Add start of Java Web Build Examples page (#3274)
Browse files Browse the repository at this point in the history
Simple hello-world projects using Spring Boot and Jetty. We can expand
it more in future
  • Loading branch information
lihaoyi authored Jul 22, 2024
1 parent 86cf90d commit 4e94cc2
Show file tree
Hide file tree
Showing 23 changed files with 604 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@ object example extends MillScalaModule {
object cross extends Cross[ExampleCrossModule](listIn(millSourcePath / "cross"))
object misc extends Cross[ExampleCrossModule](listIn(millSourcePath / "misc"))
object web extends Cross[ExampleCrossModule](listIn(millSourcePath / "web"))
object javaweb extends Cross[ExampleCrossModule](listIn(millSourcePath / "javaweb"))

trait ExampleCrossModuleJava extends ExampleCrossModule {

Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* xref:Java_Builtin_Commands.adoc[]
* xref:Java_Build_Examples.adoc[]
* xref:Java_Module_Config.adoc[]
* xref:Java_Web_Build_Examples.adoc[]
// This section is all about developing a deeper understanding of specific
// topics in Mill. This is the opposite of `Quick Start` above: while we touch
Expand Down
18 changes: 18 additions & 0 deletions docs/modules/ROOT/pages/Java_Web_Build_Examples.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
= Web Build Examples

This page contains examples of using Mill as a build tool for web-applications.
It covers setting up a basic backend server with a variety of server frameworks

== Jetty Hello World App

include::example/javaweb/1-hello-jetty.adoc[]


== Spring Boot Hello World App

include::example/javaweb/2-hello-spring-boot.adoc[]

== Spring Boot TodoMvc App

include::example/javaweb/3-todo-spring-boot.adoc[]

28 changes: 28 additions & 0 deletions example/javaweb/1-hello-jetty/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import mill._, javalib._, publish._

object hello extends RootModule with JavaModule {
def ivyDeps = Agg(
ivy"org.eclipse.jetty:jetty-server:9.4.43.v20210629",
ivy"javax.servlet:javax.servlet-api:4.0.1"
)

object test extends JavaModuleTests with TestModule.Junit4
}

// This example demonstrates how to set up a simple Jetty webserver,
// able to handle a single HTTP request at `/` and reply with a single response.


/** Usage
> mill test
...HelloJettyTest.testHelloJetty finished...
> mill runBackground
> curl http://localhost:8085
...<h1>Hello, World!</h1>...
> mill clean runBackground
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
server.port=8084
24 changes: 24 additions & 0 deletions example/javaweb/1-hello-jetty/src/HelloJetty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloJetty extends AbstractHandler {

public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().println("<h1>Hello, World!</h1>");
}

public static void main(String[] args) throws Exception {
Server server = new Server(8085);
server.setHandler(new HelloJetty());
server.start();
server.join();
}
}
52 changes: 52 additions & 0 deletions example/javaweb/1-hello-jetty/test/src/HelloJettyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import static org.junit.Assert.assertEquals;

public class HelloJettyTest {
private Server server;
private int port;

@Before
public void setUp() throws Exception {
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0); // dynamically assign port
server.addConnector(connector);

HandlerList handlers = new HandlerList();
handlers.addHandler(new HelloJetty());
server.setHandler(handlers);

server.start();
port = connector.getLocalPort();
}

@After
public void tearDown() throws Exception {
if (server != null) {
server.stop();
}
}

@Test
public void testHelloJetty() throws IOException {
URL url = new URL("http://localhost:" + port + "/");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

connection.setRequestMethod("GET");
connection.connect();

int responseCode = connection.getResponseCode();
assertEquals(HttpURLConnection.HTTP_OK, responseCode);
}
}
32 changes: 32 additions & 0 deletions example/javaweb/2-hello-spring-boot/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import mill._, javalib._, publish._

object hello extends RootModule with JavaModule {
def ivyDeps = Agg(
ivy"org.springframework.boot:spring-boot-starter-web:2.5.6",
ivy"org.springframework.boot:spring-boot-starter-actuator:2.5.6"
)

object test extends JavaModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"org.springframework.boot:spring-boot-starter-test:2.5.6"
)
}
}

// This example demonstrates how to set up a simple Spring Boot webserver,
// able to handle a single HTTP request at `/` and reply with a single response.


/** Usage
> mill test
...com.example.HelloSpringBootTest#shouldReturnDefaultMessage() finished...
> mill runBackground
> curl http://localhost:8086
...<h1>Hello, World!</h1>...
> mill clean runBackground
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
server.port=8086
22 changes: 22 additions & 0 deletions example/javaweb/2-hello-spring-boot/src/HelloSpringBoot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class HelloSpringBoot {

public static void main(String[] args) {
SpringApplication.run(HelloSpringBoot.class, args);
}

@RestController
class HelloController {
@GetMapping("/")
public String hello() {
return "<h1>Hello, World!</h1>";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloSpringBootTest {

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate restTemplate;

@Test
public void shouldReturnDefaultMessage() {
String response = restTemplate.getForObject("http://localhost:" + port + "/", String.class);
assertEquals("<h1>Hello, World!</h1>", response);
}
}
48 changes: 48 additions & 0 deletions example/javaweb/3-todo-spring-boot/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import mill._, javalib._, publish._

object hello extends RootModule with JavaModule {
def ivyDeps = Agg(
ivy"org.springframework.boot:spring-boot-starter-data-jpa:2.5.4",
ivy"org.springframework.boot:spring-boot-starter-thymeleaf:2.5.4",
ivy"org.springframework.boot:spring-boot-starter-validation:2.5.4",
ivy"org.springframework.boot:spring-boot-starter-web:2.5.4",

ivy"javax.xml.bind:jaxb-api:2.3.1",

ivy"org.webjars:webjars-locator:0.41",
ivy"org.webjars.npm:todomvc-common:1.0.5",
ivy"org.webjars.npm:todomvc-app-css:2.4.1",

ivy"com.h2database:h2:2.3.230"
)

object test extends JavaModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"org.springframework.boot:spring-boot-starter-test:2.5.6"
)
}
}

// This is a larger example using Spring Boot, implementing the well known
// https://todomvc.com/[TodoMVC] example app. Apart from running a webserver,
// this example also demonstrates:
//
// * Serving HTML templates using Thymeleaf
// * Serving static Javascript and CSS using Webjars
// * Querying a SQL database using JPA and H2


/** Usage
> mill test
...com.example.TodomvcApplicationTests#homePageLoads() finished...
...com.example.TodomvcApplicationTests#addNewTodoItem() finished...
> mill runBackground
> curl http://localhost:8087
...<h1>todos</h1>...
> mill clean runBackground
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
spring.mvc.hiddenmethod.filter.enabled=true
server.port=8087
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="en">
<li th:fragment="todoItem(item)" th:classappend="${item.completed?'completed':''}">
<div class="view">
<form th:action="@{/{id}/toggle(id=${item.id})}" th:method="put">
<input class="toggle" type="checkbox"
onchange="this.form.submit()"
th:attrappend="checked=${item.completed?'true':null}">
<label th:text="${item.title}">Taste JavaScript</label>
</form>
<form th:action="@{/{id}(id=${item.id})}" th:method="delete">
<button class="destroy"></button>
</form>
</div>
<input class="edit" value="Create a TodoMVC template">
</li>
</html>
84 changes: 84 additions & 0 deletions example/javaweb/3-todo-spring-boot/resources/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Template • TodoMVC</title>
<link rel="stylesheet" th:href="@{/webjars/todomvc-common/base.css}">
<link rel="stylesheet" th:href="@{/webjars/todomvc-app-css/index.css}">
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<form th:action="@{/}" method="post" th:object="${item}">
<input class="new-todo" placeholder="What needs to be done?" autofocus
th:field="*{title}">
</form>
</header>
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main" th:if="${totalItemCount > 0}">
<form th:action="@{/toggle-all}" th:method="put">
<input id="toggle-all" class="toggle-all" type="checkbox"
onclick="this.form.submit()">
<label for="toggle-all">Mark all as complete</label>
</form>
<ul class="todo-list" th:remove="all-but-first">
<li th:insert="fragments :: todoItem(${item})" th:each="item : ${todoItems}" th:remove="tag">
</li>
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<li class="completed">
<div class="view">
<input class="toggle" type="checkbox" checked>
<label>Taste JavaScript</label>
<button class="destroy"></button>
</div>
<input class="edit" value="Create a TodoMVC template">
</li>
<li>
<div class="view">
<input class="toggle" type="checkbox">
<label>Buy a unicorn</label>
<button class="destroy"></button>
</div>
<input class="edit" value="Rule the web">
</li>
</ul>
</section>
<!-- This footer should be hidden by default and shown when there are todos -->
<footer class="footer" th:if="${totalItemCount > 0}">
<th:block th:unless="${activeItemCount == 1}">
<span class="todo-count"><strong th:text="${activeItemCount}">0</strong> items left</span>
</th:block>
<th:block th:if="${activeItemCount == 1}">
<span class="todo-count"><strong>1</strong> item left</span>
</th:block>
<ul class="filters">
<li>
<a th:href="@{/}"
th:classappend="${todoFilter.name() == 'ALL'?'selected':''}">All</a>
</li>
<li>
<a th:href="@{/active}"
th:classappend="${todoFilter.name() == 'ACTIVE'?'selected':''}">Active</a>
</li>
<li>
<a th:href="@{/completed}"
th:classappend="${todoFilter.name() == 'COMPLETED'?'selected':''}">Completed</a>
</li>
</ul>
<form th:action="@{/completed}" th:method="delete"
th:if="${completedItemCount > 0}">
<button class="clear-completed">Clear completed</button>
</form>
</footer>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
</footer>
<script th:src="@{/webjars/todomvc-common/base.js}"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions example/javaweb/3-todo-spring-boot/src/TodomvcApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TodomvcApplication {
public static void main(String[] args) {
SpringApplication.run(TodomvcApplication.class, args);
}
}
Loading

0 comments on commit 4e94cc2

Please sign in to comment.