Skip to content

Commit cc63bc9

Browse files
add java http server implementation
1 parent 8b33b5f commit cc63bc9

File tree

2 files changed

+286
-0
lines changed

2 files changed

+286
-0
lines changed

examples/front/counter.html

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Counting Stars</title>
7+
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"></script>
8+
<style>
9+
* {
10+
margin: 0;
11+
padding: 0;
12+
box-sizing: border-box;
13+
}
14+
15+
body {
16+
min-height: 100vh;
17+
display: flex;
18+
justify-content: center;
19+
align-items: center;
20+
background: radial-gradient(ellipse at center, #0a0010 0%, #000000 50%);
21+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22+
padding: 2rem;
23+
}
24+
25+
body::before {
26+
content: '';
27+
position: absolute;
28+
top: 0;
29+
left: 0;
30+
right: 0;
31+
bottom: 0;
32+
background: radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.15) 0%, transparent 50%),
33+
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.15) 0%, transparent 50%),
34+
radial-gradient(circle at 40% 80%, rgba(59, 130, 246, 0.2) 0%, transparent 50%);
35+
z-index: -1;
36+
}
37+
38+
.counter-card {
39+
background: linear-gradient(145deg,
40+
rgba(139, 92, 246, 0.05) 0%,
41+
rgba(59, 130, 246, 0.05) 50%,
42+
rgba(168, 85, 247, 0.05) 100%);
43+
backdrop-filter: blur(20px);
44+
border: 1px solid rgba(139, 92, 246, 0.3);
45+
border-radius: 24px;
46+
padding: 3rem 2.5rem;
47+
text-align: center;
48+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.9),
49+
0 0 0 1px rgba(139, 92, 246, 0.2),
50+
inset 0 1px 0 rgba(255, 255, 255, 0.05),
51+
0 0 30px rgba(139, 92, 246, 0.4);
52+
min-width: 300px;
53+
position: relative;
54+
}
55+
56+
.counter-card::before {
57+
content: '';
58+
position: absolute;
59+
top: 0;
60+
left: 0;
61+
right: 0;
62+
bottom: 0;
63+
background: linear-gradient(145deg,
64+
rgba(139, 92, 246, 0.02) 0%,
65+
transparent 50%,
66+
rgba(168, 85, 247, 0.02) 100%);
67+
border-radius: 22px;
68+
z-index: -1;
69+
}
70+
71+
.counter-title {
72+
background: linear-gradient(145deg,
73+
#f8fafc 0%,
74+
#8b5cf6 25%,
75+
#3b82f6 50%,
76+
#a855f7 75%,
77+
#ec4899 100%);
78+
-webkit-background-clip: text;
79+
background-clip: text;
80+
-webkit-text-fill-color: transparent;
81+
font-size: 2rem;
82+
font-weight: 600;
83+
margin-bottom: 2rem;
84+
text-shadow: 0 0 30px rgba(139, 92, 246, 0.8),
85+
0 0 60px rgba(59, 130, 246, 0.6),
86+
0 0 90px rgba(168, 85, 247, 0.4);
87+
letter-spacing: 2px;
88+
text-transform: uppercase;
89+
filter: drop-shadow(0 0 15px rgba(139, 92, 246, 0.7));
90+
position: relative;
91+
animation: titleGlow 3s ease-in-out infinite alternate;
92+
}
93+
94+
@keyframes titleGlow {
95+
0% {
96+
filter: drop-shadow(0 0 15px rgba(139, 92, 246, 0.7));
97+
text-shadow: 0 0 30px rgba(139, 92, 246, 0.8),
98+
0 0 60px rgba(59, 130, 246, 0.6),
99+
0 0 90px rgba(168, 85, 247, 0.4);
100+
}
101+
100% {
102+
filter: drop-shadow(0 0 25px rgba(139, 92, 246, 0.9));
103+
text-shadow: 0 0 40px rgba(139, 92, 246, 1),
104+
0 0 80px rgba(59, 130, 246, 0.8),
105+
0 0 120px rgba(168, 85, 247, 0.6);
106+
}
107+
}
108+
109+
.counter-display {
110+
background: linear-gradient(145deg, #8b5cf6, #3b82f6, #a855f7);
111+
-webkit-background-clip: text;
112+
background-clip: text;
113+
-webkit-text-fill-color: transparent;
114+
font-size: 4rem;
115+
font-weight: 700;
116+
margin: 2rem 0;
117+
text-shadow: 0 0 30px rgba(139, 92, 246, 0.8);
118+
filter: drop-shadow(0 0 10px rgba(139, 92, 246, 0.5));
119+
}
120+
121+
.counter-buttons {
122+
display: flex;
123+
gap: 1rem;
124+
justify-content: center;
125+
margin-top: 2rem;
126+
}
127+
128+
.counter-btn {
129+
background: linear-gradient(145deg,
130+
rgba(139, 92, 246, 0.15) 0%,
131+
rgba(59, 130, 246, 0.15) 50%,
132+
rgba(168, 85, 247, 0.15) 100%);
133+
border: 1px solid rgba(139, 92, 246, 0.3);
134+
border-radius: 50%;
135+
color: #ffffff;
136+
cursor: pointer;
137+
font-size: 1.8rem;
138+
font-weight: bold;
139+
height: 70px;
140+
width: 70px;
141+
transition: all 0.3s ease;
142+
display: flex;
143+
align-items: center;
144+
justify-content: center;
145+
position: relative;
146+
backdrop-filter: blur(10px);
147+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3),
148+
inset 0 1px 0 rgba(255, 255, 255, 0.1);
149+
}
150+
151+
.counter-btn:hover {
152+
transform: translateY(-2px) scale(1.05);
153+
background: linear-gradient(145deg,
154+
rgba(139, 92, 246, 0.25) 0%,
155+
rgba(59, 130, 246, 0.25) 50%,
156+
rgba(168, 85, 247, 0.25) 100%);
157+
border-color: rgba(139, 92, 246, 0.5);
158+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4),
159+
0 0 15px rgba(139, 92, 246, 0.3),
160+
inset 0 1px 0 rgba(255, 255, 255, 0.15);
161+
}
162+
163+
.counter-btn:active {
164+
transform: translateY(-1px) scale(1.02);
165+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5),
166+
0 0 10px rgba(139, 92, 246, 0.4),
167+
inset 0 2px 4px rgba(0, 0, 0, 0.2);
168+
}
169+
</style>
170+
171+
</head>
172+
<body>
173+
<div class="counter-card">
174+
<h1 class="counter-title">Counting Stars</h1>
175+
<div class="counter-display" data-on-load="@get('/counter')">
176+
<span id="counter">Loading...</span>
177+
</div>
178+
<div class="counter-buttons">
179+
<button class="counter-btn" data-on-click="@post('/decrement')"></button>
180+
<button class="counter-btn" data-on-click="@post('/increment')">+</button>
181+
</div>
182+
</div>
183+
184+
</body>
185+
</html>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import com.sun.net.httpserver.HttpExchange
2+
import com.sun.net.httpserver.HttpServer
3+
import dev.datastar.kotlin.sdk.Response
4+
import dev.datastar.kotlin.sdk.ServerSentEventGenerator
5+
import kotlinx.coroutines.flow.MutableStateFlow
6+
import kotlinx.coroutines.runBlocking
7+
import java.io.File
8+
import java.net.InetSocketAddress
9+
import java.util.concurrent.Executors
10+
11+
///usr/bin/env jbang "$0" "$@" ; exit $?
12+
//JAVA 21
13+
//KOTLIN 2.2.0
14+
//DEPS dev.cloudgt.datastar:kotlin-sdk:0.1.0
15+
//DEPS org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2
16+
17+
18+
fun main() {
19+
val server = server()
20+
server.start()
21+
println("Server started on port ${server.address.port}")
22+
}
23+
24+
fun server(
25+
counter: MutableStateFlow<Int> = MutableStateFlow(0),
26+
): HttpServer = HttpServer.create(
27+
InetSocketAddress(8080),
28+
0
29+
).apply {
30+
31+
executor = Executors.newVirtualThreadPerTaskExecutor()
32+
33+
val counterPage = File(
34+
"../front/counter.html"
35+
).readBytes()
36+
37+
createContext("/") { exchange ->
38+
exchange.responseHeaders.add("Content-Type", "text/html")
39+
exchange.sendResponseHeaders(200, counterPage.size.toLong())
40+
exchange.responseBody.use { os ->
41+
os.write(counterPage)
42+
}
43+
exchange.close()
44+
}
45+
46+
sseContext(path = "/counter") {
47+
runBlocking {
48+
counter.collect { event ->
49+
this@sseContext.patchElements(
50+
"""<span id="counter">${event}</span>"""
51+
)
52+
}
53+
}
54+
}
55+
56+
createContext("/increment") { exchange ->
57+
counter.value++
58+
exchange.sendResponseHeaders(204, -1)
59+
exchange.close()
60+
}
61+
62+
createContext("/decrement") { exchange ->
63+
counter.value--
64+
exchange.sendResponseHeaders(204, -1)
65+
exchange.close()
66+
}
67+
68+
}
69+
70+
private fun HttpServer.sseContext(
71+
path: String,
72+
sender: ServerSentEventGenerator.() -> Unit
73+
) {
74+
createContext(path) { exchange ->
75+
try {
76+
val generator = ServerSentEventGenerator(adaptResponse(exchange))
77+
sender(generator)
78+
} finally {
79+
exchange.close()
80+
}
81+
}
82+
}
83+
84+
private fun adaptResponse(exchange: HttpExchange): Response =
85+
object : Response {
86+
override fun sendConnectionHeaders(
87+
status: Int,
88+
headers: Map<String, List<String>>,
89+
) {
90+
exchange.responseHeaders.putAll(headers)
91+
exchange.sendResponseHeaders(status, 0)
92+
}
93+
94+
override fun write(text: String) {
95+
exchange.responseBody.write(text.toByteArray())
96+
}
97+
98+
override fun flush() {
99+
exchange.responseBody.flush()
100+
}
101+
}

0 commit comments

Comments
 (0)