Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions cli/src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <odr/html.hpp>
#include <odr/http_server.hpp>

#include <cstdint>
#include <filesystem>
#include <iostream>
#include <string>

Expand Down Expand Up @@ -40,8 +42,18 @@ int main(const int argc, char **argv) {
}
}

HttpServer::Config server_config;
HttpServer server(server_config);
const HttpServer server{{}, logger};

// the server does not own a cache any more, so the translation goes
// somewhere of our choosing
const std::filesystem::path cache_path =
std::filesystem::temp_directory_path() / "odr-server";
std::filesystem::remove_all(cache_path);

// bind before anything is printed: the port is only known once the socket
// is, and it is not necessarily the one that was asked for
const std::uint32_t port = server.bind("localhost", 8080);
const std::string base_url = "http://localhost:" + std::to_string(port);

HtmlConfig html_config;
html_config.embed_images = false;
Expand All @@ -52,12 +64,16 @@ int main(const int argc, char **argv) {

{
const std::string prefix = "file";
const HtmlViews views =
server.serve_file(decoded_file, prefix, html_config);
const std::string prefix_cache_path = (cache_path / prefix).string();
std::filesystem::create_directories(prefix_cache_path);

const HtmlService service =
html::translate(decoded_file, prefix_cache_path, html_config, logger);
server.connect_service(service, prefix);
const HtmlViews views = service.list_views();
ODR_INFO(*logger, "hosted decoded file with id: " << prefix);
for (const auto &view : views) {
ODR_INFO(*logger,
"http://localhost:8080/file/" << prefix << "/" << view.path());
ODR_INFO(*logger, base_url << "/file/" << prefix << "/" << view.path());
}
}

Expand All @@ -68,18 +84,19 @@ int main(const int argc, char **argv) {
: decoded_file.as_archive_file().archive().as_filesystem();

const std::string prefix = "filesystem";
const std::string prefix_cache_path = (cache_path / prefix).string();
std::filesystem::create_directories(prefix_cache_path);

const HtmlService filesystem_service =
html::translate(filesystem, server_config.cache_path + "/" + prefix,
html_config, logger);
html::translate(filesystem, prefix_cache_path, html_config, logger);
server.connect_service(filesystem_service, prefix);
ODR_INFO(*logger, "hosted filesystem with id: " << prefix);
for (const auto &view : filesystem_service.list_views()) {
ODR_INFO(*logger,
"http://localhost:8080/file/" << prefix << "/" << view.path());
ODR_INFO(*logger, base_url << "/file/" << prefix << "/" << view.path());
}
}

server.listen("localhost", 8080);
server.listen();

return 0;
} catch (const std::exception &e) {
Expand Down
67 changes: 39 additions & 28 deletions jni/java/app/opendocument/core/HttpServer.java
Original file line number Diff line number Diff line change
@@ -1,47 +1,60 @@
package app.opendocument.core;

import java.util.ArrayList;
import java.util.List;

/**
* Serves translated files over HTTP. Mirrors {@code odr::HttpServer}. Only
* available when the native library was built with the HTTP server
* ({@link Odr#hasHttpServer()}).
*/
public final class HttpServer extends NativeResource {
/** Mirrors {@code odr::HttpServer::Config}. */
public static final class Config {
public String cachePath = "/tmp/odr";
/**
* Mirrors {@code odr::HttpServer::Config}. Empty since the cache path went with
* {@code serveFile}: what a service was translated into belongs to whoever
* translated it.
*/
public static final class Config {}

/** Mirrors {@code odr::HttpServer::Options}: socket options for {@link #bind}. */
public static final class Options {
/** SO_REUSEADDR, so a port held only by sockets in TIME_WAIT can be bound again. */
public boolean reuseAddress = true;

/**
* SO_REUSEPORT where the platform has it. Two live servers on one port share the
* incoming connections between them, hence off.
*/
public boolean reusePort = false;
}

public HttpServer(Config config) {
super(create(config.cachePath), null, HttpServer::destroy);
public HttpServer() {
this(new Config());
}

public Config config() {
Config config = new Config();
config.cachePath = cachePathNative(handle());
return config;
public HttpServer(Config config) {
super(create(), null, HttpServer::destroy);
}

/** Hosts the service under {@code /<prefix>/<view path>}. */
public void connectService(HtmlService service, String prefix) {
connectServiceNative(handle(), service.handle(), prefix);
}

/** Translates a decoded file and hosts it under {@code /file/<prefix>/<view path>}. */
public List<HtmlView> serveFile(DecodedFile file, String prefix, HtmlConfig config) {
long[] handles = serveFileNative(handle(), file.handle(), prefix, config);
List<HtmlView> result = new ArrayList<>(handles.length);
for (long h : handles) {
result.add(new HtmlView(h, this));
}
return result;
/**
* Binds the socket and returns the port it got - pass {@code 0} for any free one.
* Connections are accepted into the backlog from here on, before {@link #listen()}
* runs, so a caller can hand out the port as soon as this returns.
*/
public int bind(String host, int port) {
return bind(host, port, new Options());
}

/** Binds with explicit socket options. */
public int bind(String host, int port, Options options) {
return bindNative(handle(), host, port, options.reuseAddress, options.reusePort);
}

/** Blocks serving requests until {@link #stop()} is called from another thread. */
public void listen(String host, int port) {
listenNative(handle(), host, port);
public void listen() {
listenNative(handle());
}

public void clear() {
Expand All @@ -52,18 +65,16 @@ public void stop() {
stopNative(handle());
}

private static native long create(String cachePath);
private static native long create();

private static native void destroy(long handle);

private native String cachePathNative(long handle);

private native void connectServiceNative(long handle, long serviceHandle, String prefix);

private native long[] serveFileNative(
long handle, long fileHandle, String prefix, HtmlConfig config);
private native int bindNative(
long handle, String host, int port, boolean reuseAddress, boolean reusePort);

private native void listenNative(long handle, String host, int port);
private native void listenNative(long handle);

private native void clearNative(long handle);

Expand Down
82 changes: 24 additions & 58 deletions jni/src/jni_http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@
#include <odr/html.hpp>
#include <odr/http_server.hpp>

#include <vector>

namespace {

using odr_jni::destroy_handle;
using odr_jni::from_handle;
using odr_jni::guarded;
using odr_jni::make_handle;
using odr_jni::to_jstring;
using odr_jni::to_string;

odr::HttpServer &server(jlong handle) {
Expand All @@ -30,27 +27,16 @@ Java_app_opendocument_core_Odr_hasHttpServer(JNIEnv *, jclass) {
return JNI_TRUE;
}

extern "C" JNIEXPORT jlong JNICALL Java_app_opendocument_core_HttpServer_create(
JNIEnv *env, jclass, jstring cache_path) {
return guarded(env, [&] {
odr::HttpServer::Config config;
config.cache_path = to_string(env, cache_path);
return make_handle(odr::HttpServer(config));
});
extern "C" JNIEXPORT jlong JNICALL
Java_app_opendocument_core_HttpServer_create(JNIEnv *env, jclass) {
return guarded(env, [&] { return make_handle(odr::HttpServer()); });
}

extern "C" JNIEXPORT void JNICALL
Java_app_opendocument_core_HttpServer_destroy(JNIEnv *, jclass, jlong handle) {
destroy_handle<odr::HttpServer>(handle);
}

extern "C" JNIEXPORT jstring JNICALL
Java_app_opendocument_core_HttpServer_cachePathNative(JNIEnv *env, jobject,
jlong handle) {
return guarded(
env, [&] { return to_jstring(env, server(handle).config().cache_path); });
}

extern "C" JNIEXPORT void JNICALL
Java_app_opendocument_core_HttpServer_connectServiceNative(JNIEnv *env, jobject,
jlong handle,
Expand All @@ -62,39 +48,25 @@ Java_app_opendocument_core_HttpServer_connectServiceNative(JNIEnv *env, jobject,
});
}

extern "C" JNIEXPORT jlongArray JNICALL
Java_app_opendocument_core_HttpServer_serveFileNative(JNIEnv *env, jobject,
jlong handle,
jlong file_handle,
jstring prefix,
jobject config) {
extern "C" JNIEXPORT jint JNICALL
Java_app_opendocument_core_HttpServer_bindNative(JNIEnv *env, jobject,
jlong handle, jstring host,
jint port,
jboolean reuse_address,
jboolean reuse_port) {
return guarded(env, [&] {
const odr::HtmlViews views = server(handle).serve_file(
*from_handle<odr::DecodedFile>(file_handle), to_string(env, prefix),
odr_jni::html_config_from_java(env, config));
std::vector<jlong> handles;
handles.reserve(views.size());
for (const odr::HtmlView &view : views) {
handles.push_back(make_handle(odr::HtmlView(view)));
}
jlongArray result = env->NewLongArray(static_cast<jsize>(handles.size()));
if (result == nullptr) {
return jlongArray{};
}
env->SetLongArrayRegion(result, 0, static_cast<jsize>(handles.size()),
handles.data());
return result;
odr::HttpServer::Options options;
options.reuse_address = reuse_address == JNI_TRUE;
options.reuse_port = reuse_port == JNI_TRUE;
return static_cast<jint>(server(handle).bind(
to_string(env, host), static_cast<std::uint32_t>(port), options));
});
}

extern "C" JNIEXPORT void JNICALL
Java_app_opendocument_core_HttpServer_listenNative(JNIEnv *env, jobject,
jlong handle, jstring host,
jint port) {
guarded(env, [&] {
server(handle).listen(to_string(env, host),
static_cast<std::uint32_t>(port));
});
jlong handle) {
guarded(env, [&] { server(handle).listen(); });
}

extern "C" JNIEXPORT void JNICALL
Expand Down Expand Up @@ -127,36 +99,30 @@ Java_app_opendocument_core_Odr_hasHttpServer(JNIEnv *, jclass) {
}

extern "C" JNIEXPORT jlong JNICALL
Java_app_opendocument_core_HttpServer_create(JNIEnv *env, jclass, jstring) {
Java_app_opendocument_core_HttpServer_create(JNIEnv *env, jclass) {
return odr_jni::guarded(env, [&]() -> jlong { unsupported(); });
}

extern "C" JNIEXPORT void JNICALL
Java_app_opendocument_core_HttpServer_destroy(JNIEnv *, jclass, jlong) {}

extern "C" JNIEXPORT jstring JNICALL
Java_app_opendocument_core_HttpServer_cachePathNative(JNIEnv *env, jobject,
jlong) {
return odr_jni::guarded(env, [&]() -> jstring { unsupported(); });
}

extern "C" JNIEXPORT void JNICALL
Java_app_opendocument_core_HttpServer_connectServiceNative(JNIEnv *env, jobject,
jlong, jlong,
jstring) {
odr_jni::guarded(env, [&] { unsupported(); });
}

extern "C" JNIEXPORT jlongArray JNICALL
Java_app_opendocument_core_HttpServer_serveFileNative(JNIEnv *env, jobject,
jlong, jlong, jstring,
jobject) {
return odr_jni::guarded(env, [&]() -> jlongArray { unsupported(); });
extern "C" JNIEXPORT jint JNICALL
Java_app_opendocument_core_HttpServer_bindNative(JNIEnv *env, jobject, jlong,
jstring, jint, jboolean,
jboolean) {
return odr_jni::guarded(env, [&]() -> jint { unsupported(); });
}

extern "C" JNIEXPORT void JNICALL
Java_app_opendocument_core_HttpServer_listenNative(JNIEnv *env, jobject, jlong,
jstring, jint) {
Java_app_opendocument_core_HttpServer_listenNative(JNIEnv *env, jobject,
jlong) {
odr_jni::guarded(env, [&] { unsupported(); });
}

Expand Down
Loading
Loading