Skip to content

Commit dd264b2

Browse files
committed
feat(server): add NativeServer JNI-bridge scaffold (native HTTP server + WebUI)
Minimal structural wiring for the planned native server: NativeServer sits next to OpenAiCompatServer (the Java server) as the entry point for the upstream native HTTP transport (server-http.cpp + cpp-httplib) already compiled into libjllama — the only component that can serve the embedded WebUI. Scaffold only: start() throws UnsupportedOperationException until the upstream routes (server.cpp's registration) are wired to a JNI entry point; isRunning()/getHost()/getPort()/close() are model-free placeholders. The native methods + C++ implementation + lifecycle are a separate, detailed step. Adds a model-free smoke test (NativeServerSmokeTest, 3 tests). Verified locally: compile (Error Prone/NullAway/Checker), javadoc (failOnWarnings), SpotBugs Max/Low (0 bugs, @tostring clears IMC), ArchUnit (12/12). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LjWiKSyNzqqpobSKYRiew5
1 parent 9a1d493 commit dd264b2

2 files changed

Lines changed: 157 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
package net.ladenthin.llama.server;
6+
7+
import java.util.Objects;
8+
import lombok.ToString;
9+
10+
/**
11+
* Scaffold for the <em>native</em> HTTP server bridge — the planned counterpart to
12+
* {@link OpenAiCompatServer}.
13+
*
14+
* <p>{@link OpenAiCompatServer} implements the HTTP transport in Java (on the JDK's
15+
* {@code com.sun.net.httpserver}) and drives the native llama.cpp server <em>core</em> over JNI. This
16+
* class is instead the entry point for the upstream <em>native</em> HTTP transport that is already
17+
* compiled into {@code libjllama} (llama.cpp's {@code server-http.cpp} plus its {@code cpp-httplib}
18+
* backend). That native transport is the only component able to serve the embedded llama.cpp
19+
* <strong>WebUI</strong> (the {@code ui.cpp}/{@code ui.h} asset table compiled in behind
20+
* {@code LLAMA_UI_HAS_ASSETS}).</p>
21+
*
22+
* <p><strong>Status: scaffold only.</strong> The route registration that upstream performs in
23+
* {@code server.cpp} (deliberately excluded from this build) is not yet wired to a JNI entry point, so
24+
* {@link #start()} throws {@link UnsupportedOperationException} for now. This class only fixes the
25+
* package structure and the public API shape; the native {@code startServer}/{@code stopServer}
26+
* methods, their C++ implementation, the server lifecycle/threading and WebUI serving are a separate,
27+
* detailed step (see {@code CLAUDE.md}, "WebUI (llama.cpp Svelte UI) embedding").</p>
28+
*
29+
* <p>It is {@link AutoCloseable} so that, once implemented, callers can drive it with
30+
* try-with-resources exactly like {@link OpenAiCompatServer}.</p>
31+
*/
32+
@ToString
33+
public final class NativeServer implements AutoCloseable {
34+
35+
/** Message thrown by {@link #start()} until the native route-wiring lands. */
36+
static final String NOT_WIRED_MESSAGE =
37+
"NativeServer is a scaffold: the upstream native HTTP routes (server-http.cpp) are "
38+
+ "not yet wired to JNI. Use OpenAiCompatServer for now; the native server and "
39+
+ "embedded WebUI are a planned step.";
40+
41+
/** Immutable server configuration (bind host, port, ...) shared with {@link OpenAiCompatServer}. */
42+
private final OpenAiServerConfig config;
43+
44+
/**
45+
* Creates a native-server bridge for the given configuration.
46+
*
47+
* <p>Construction performs no native work and binds no socket; it only captures the configuration.
48+
* Call {@link #start()} to launch the server (not implemented yet).</p>
49+
*
50+
* @param config the server configuration (host, port, ...); must not be {@code null}
51+
*/
52+
public NativeServer(OpenAiServerConfig config) {
53+
this.config = Objects.requireNonNull(config, "config");
54+
}
55+
56+
/**
57+
* Starts the native HTTP server and begins serving the embedded WebUI.
58+
*
59+
* <p><strong>Not implemented yet</strong> — this is a scaffold. The native route registration and
60+
* its JNI binding are a planned step, so this method always throws until then.</p>
61+
*
62+
* @return this server instance (for fluent / try-with-resources use), once implemented
63+
* @throws UnsupportedOperationException always, until the native routes are wired to JNI
64+
*/
65+
// Scaffold: start() intentionally always throws for now, but must stay callable (not @DoNotCall)
66+
// so the real implementation and its callers/tests keep the same signature.
67+
@SuppressWarnings("DoNotCallSuggester")
68+
public NativeServer start() {
69+
throw new UnsupportedOperationException(NOT_WIRED_MESSAGE);
70+
}
71+
72+
/**
73+
* Reports whether the native server is currently running.
74+
*
75+
* @return {@code false} — the scaffold never starts a server yet
76+
*/
77+
public boolean isRunning() {
78+
return false;
79+
}
80+
81+
/**
82+
* Returns the host the server is configured to bind to.
83+
*
84+
* @return the configured bind host
85+
*/
86+
public String getHost() {
87+
return config.getHost();
88+
}
89+
90+
/**
91+
* Returns the port the server is configured to bind to.
92+
*
93+
* @return the configured port
94+
*/
95+
public int getPort() {
96+
return config.getPort();
97+
}
98+
99+
/**
100+
* Stops the native server if it is running.
101+
*
102+
* <p>No-op in the scaffold (nothing is ever started), so it is always safe to call, including from
103+
* try-with-resources. Real lifecycle teardown is part of the planned native-server implementation.</p>
104+
*/
105+
@Override
106+
public void close() {
107+
// Nothing is started yet, so there is nothing to release.
108+
}
109+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
package net.ladenthin.llama.server;
6+
7+
import static org.hamcrest.MatcherAssert.assertThat;
8+
import static org.hamcrest.Matchers.containsString;
9+
import static org.hamcrest.Matchers.is;
10+
import static org.junit.jupiter.api.Assertions.assertThrows;
11+
12+
import org.junit.jupiter.api.Test;
13+
14+
/**
15+
* Model-free smoke test for the {@link NativeServer} scaffold: it must construct without any native
16+
* work, expose its configured host/port, never report itself running, throw a clear
17+
* {@link UnsupportedOperationException} from {@link NativeServer#start()} until the native routes are
18+
* wired, and be a safe no-op {@link AutoCloseable}. No model and no {@code libjllama} required.
19+
*/
20+
public class NativeServerSmokeTest {
21+
22+
private static OpenAiServerConfig config() {
23+
return OpenAiServerConfig.builder().host("127.0.0.1").port(1234).build();
24+
}
25+
26+
@Test
27+
public void exposesConfiguredHostAndPortWithoutStarting() {
28+
NativeServer server = new NativeServer(config());
29+
assertThat(server.getHost(), is("127.0.0.1"));
30+
assertThat(server.getPort(), is(1234));
31+
assertThat(server.isRunning(), is(false));
32+
}
33+
34+
@Test
35+
public void startThrowsUntilNativeRoutesAreWired() {
36+
NativeServer server = new NativeServer(config());
37+
UnsupportedOperationException ex = assertThrows(UnsupportedOperationException.class, server::start);
38+
assertThat(ex.getMessage(), containsString("not yet wired"));
39+
assertThat(server.isRunning(), is(false));
40+
}
41+
42+
@Test
43+
public void closeIsSafeNoOpEvenViaTryWithResources() {
44+
try (NativeServer server = new NativeServer(config())) {
45+
assertThat(server.isRunning(), is(false));
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)