Java "httplib"

Admin User, created Mar 08. 2024
         
package liblet.util;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import nova.Runtime;
import nova.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Modern Albufeira Prolog Interpreter
* <p>
* Warranty & Liability
* To the extent permitted by applicable law and unless explicitly
* otherwise agreed upon, XLOG Technologies AG makes no warranties
* regarding the provided information. XLOG Technologies AG assumes
* no liability that any problems might be solved with the information
* provided by XLOG Technologies AG.
* <p>
* Rights & License
* All industrial property rights regarding the information - copyright
* and patent rights in particular - are the sole property of XLOG
* Technologies AG. If the company was not the originator of some
* excerpts, XLOG Technologies AG has at least obtained the right to
* reproduce, change and translate the information.
* <p>
* Reproduction is restricted to the whole unaltered document. Reproduction
* of the information is only allowed for non-commercial uses. Selling,
* giving away or letting of the execution of the library is prohibited.
* The library can be distributed as part of your applications and libraries
* for execution provided this comment remains unchanged.
* <p>
* Restrictions
* Only to be distributed with programs that add significant and primary
* functionality to the library. Not to be distributed with additional
* software intended to replace any components of the library.
* <p>
* Trademarks
* Jekejeke is a registered trademark of XLOG Technologies AG.
*/
public final class httplib {
/******************************************************************/
/* HTTP Client */
/******************************************************************/
/**
* os_open_promise_opts(P, L, S, Q):
* The predicate succeeds in Q with a promise for open input S
* on path P and option list L.
*/
private static boolean test_os_open_promise_opts(Object[] args) {
Object obj = Machine.deref(Machine.exec_build(args[0]));
Special.check_atom(obj);
String url = (String) obj;
HashMap<String, Object> opts = (HashMap<String, Object>) Machine.deref(Machine.exec_build(args[1]));
Runtime.Source stream = new Runtime.Source();
if (!Machine.exec_unify(args[2], stream))
return false;
Object buf = Machine.ctx;
Handler.Promise prom;
if (url.startsWith("http:") || url.startsWith("https:")) {
prom = open_http_promise_opts(buf, stream, url, opts);
} else {
prom = Runtime.open_file_promise(buf, stream, url);
}
return Machine.exec_unify(args[3], prom);
}
public static Handler.Promise open_http_promise_opts(Object buf, Runtime.Source stream, String url, HashMap<String, Object> opts) {
return new Handler.Promise(() -> {
Thread self = Thread.currentThread();
Machine.register_interrupt(buf, () -> self.interrupt());
try {
String method;
HttpRequest.BodyPublisher body;
if (opts.get("method") != null) {
method = opts.get("method").toString();
} else if (opts.get("body") != null) {
method = "POST";
} else {
method = "GET";
}
if (opts.get("body") != null) {
body = HttpRequest.BodyPublishers.ofString(
opts.get("body").toString(),
StandardCharsets.UTF_8);
} else {
body = HttpRequest.BodyPublishers.noBody();
}
HttpRequest.Builder builder = HttpRequest.newBuilder().method(method, body);
if (opts.get("headers") != null) {
HashMap<String, Object> headers = (HashMap<String, Object>) opts.get("headers");
Iterator<Map.Entry<String, Object>> it = headers.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
builder = builder.header(entry.getKey(), entry.getValue().toString());
}
}
HttpClient client = Runtime.getHttpClient();
HttpResponse<InputStream> response;
try {
response = client.send(builder.uri(URI.create(url)).build(),
HttpResponse.BodyHandlers.ofInputStream());
} catch (IOException err) {
Machine.register_signal(buf, Runtime.map_file_error(err, url));
return;
}
if (response.statusCode() != 200) {
Machine.register_signal(buf, Runtime.map_http_result(response.statusCode(), url));
} else {
stream.data = new InputStreamReader(response.body(), StandardCharsets.UTF_8);
stream.receive = (Handler.Factory) Runtime::file_read_promise;
stream.release = (Handler.Factory) Runtime::file_close_promise;
stream.flags |= Runtime.MASK_SRC_AREAD;
}
} catch (InterruptedException x) {
/* */
} finally {
Machine.register_interrupt(buf, () -> {});
}
});
}
/**
* os_open_sync_opts(P, M, L, S):
* The predicate succeeds. As a side effect the stream S is
* opened on the path P with the mode M and the option list L.
*/
private static boolean test_os_open_sync_opts(Object[] args) {
Object obj = Machine.deref(Machine.exec_build(args[0]));
Special.check_atom(obj);
String url = (String) obj;
Object mode = Machine.deref(Machine.exec_build(args[1]));
Special.check_atom(mode);
HashMap<String, Object> opts = (HashMap<String, Object>) Machine.deref(Machine.exec_build(args[2]));
Object stream;
if ("read".equals(mode)) {
throw Machine.make_error(new Store.Compound("resource_error",
new Object[]{"not_implemented"}));
} else if ("write".equals(mode)) {
stream = Runtime.open_write(url, false);
} else if ("append".equals(mode)) {
stream = Runtime.open_write(url, true);
} else {
throw Machine.make_error(new Store.Compound("domain_error",
new Object[]{"io_mode", mode}));
}
return Machine.exec_unify(args[3], stream);
}
/******************************************************************/
/* HTTP Server */
/******************************************************************/
/**
* http_server_new(S):
* The predicate succeeds in S with a new http server.
*/
private static boolean test_http_server_new(Object[] args) {
try {
HttpServer server = HttpServer.create(null, 0);
return Machine.exec_unify(args[0], server);
} catch (IOException e) {
throw Machine.make_error(new Store.Compound("resource_error",
new Object[]{"io_exception"}));
}
}
/**
* sys_http_server_on(S, T, C):
* The predicate succeeds. As a side effect the stackfull handler C
* is registered as listener to event T from server S.
*/
private static boolean test_sys_http_server_on(Object[] args) {
HttpServer server = (HttpServer) Machine.deref(Machine.exec_build(args[0]));
Object type = Machine.deref(Machine.exec_build(args[1]));
Special.check_atom(type);
Object clause = Machine.deref(Machine.exec_build(args[2]));
Runtime.check_clause(clause);
Machine.Context buf = new Machine.Context();
buf.engine.text_output = Store.engine.text_output;
buf.engine.text_error = Store.engine.text_error;
buf.engine.text_input = Store.engine.text_input;
if ("request".equals(type)) {
server.createContext("/", new BaseHandler(
(paras) -> Machine.launch_async(clause, buf, paras)));
}
return true;
}
private static class BaseHandler implements HttpHandler {
private final Handler.Check func;
public BaseHandler(Handler.Check func) {
this.func = func;
}
public void handle(HttpExchange t) {
new Handler.Coroutine(() -> func.run(new Object[]{t, t})).async();
}
}
/**
* http_server_listen(S, P):
* The predicate succeeds. As a side effect the server S
* starts listening on port P.
*/
private static boolean test_http_server_listen(Object[] args) {
try {
HttpServer server = (HttpServer) Machine.deref(Machine.exec_build(args[0]));
Object beta = Machine.deref(Machine.exec_build(args[1]));
Special.check_integer(beta);
int port = (!Special.is_bigint(beta) ? ((Integer) beta).intValue() : -1);
server.bind(new InetSocketAddress(port), 0);
server.setExecutor(null);
server.start();
return true;
} catch (IOException e) {
throw Machine.make_error(new Store.Compound("resource_error",
new Object[]{"io_exception"}));
}
}
/*******************************************************************/
/* HTTP Request */
/*******************************************************************/
/**
* http_current_method(S, P):
* The predicate succeeds in M with the method of the HTTP request S.
*/
private static boolean test_http_current_method(Object[] args) {
HttpExchange exchange = (HttpExchange) Machine.deref(Machine.exec_build(args[0]));
String res = exchange.getRequestMethod();
return Machine.exec_unify(args[1], res);
}
/**
* http_current_path(S, P):
* The predicate succeeds in P with the path of the HTTP request S.
*/
private static boolean test_http_current_path(Object[] args) {
HttpExchange exchange = (HttpExchange) Machine.deref(Machine.exec_build(args[0]));
String res = exchange.getRequestURI().toString();
return Machine.exec_unify(args[1], res);
}
/**
* http_input_promise(S, R, Q):
* The predicate succeeds in Q with a promise for a new
* text reader R on HTTP request S.
*/
private static boolean test_http_input_promise(Object[] args) {
HttpExchange exchange = (HttpExchange) Machine.deref(Machine.exec_build(args[0]));
Runtime.Source stream = new Runtime.Source();
if (!Machine.exec_unify(args[1], stream))
return false;
Object buf = Machine.ctx;
Handler.Promise prom = http_input_promise(buf, stream, exchange);
return Machine.exec_unify(args[2], prom);
}
/**
* http_input_sync(S, R):
* The predicate succeeds in R with a new text reader for the HTTP request S.
*/
private static Handler.Promise http_input_promise(Object buf, Runtime.Source stream, HttpExchange exchange) {
return new Handler.Promise(() -> {
InputStream input = exchange.getRequestBody();
stream.data = new InputStreamReader(input, StandardCharsets.UTF_8);
stream.receive = (Handler.Factory) Runtime::file_read_promise;
stream.release = (Handler.Factory) Runtime::file_close_promise;
stream.flags |= Runtime.MASK_SRC_AREAD;
});
}
/*******************************************************************/
/* HTTP Response */
/*******************************************************************/
/**
* sys_http_write_head(S, C, H):
* The predicate succeeds. As a side effect it writes the status
* code C and the headers map H to the HTTP response S.
*/
private static boolean test_sys_http_write_head(Object[] args) {
try {
HttpExchange exchange = (HttpExchange) Machine.deref(Machine.exec_build(args[0]));
Object code = Machine.deref(Machine.exec_build(args[1]));
Special.check_integer(code);
Object assoc = Machine.deref(Machine.exec_build(args[2]));
int status = (!Special.is_bigint(code) ? ((Integer) code).intValue() : -1);
Headers headers = exchange.getResponseHeaders();
Iterator<Map.Entry<String, Object>> it = ((HashMap<String, Object>) assoc).entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
headers.set(entry.getKey(), (String) entry.getValue());
}
exchange.sendResponseHeaders(status, 0);
return true;
} catch (IOException e) {
throw Machine.make_error(new Store.Compound("resource_error",
new Object[]{"io_exception"}));
}
}
/**
* http_output_new(S, W):
* The predicate succeeds in W with a new text writer for the HTTP response S.
*/
private static boolean test_http_output_new(Object[] args) {
HttpExchange exchange = (HttpExchange) Machine.deref(Machine.exec_build(args[0]));
OutputStream output = exchange.getResponseBody();
Runtime.Sink dst = new Runtime.Sink();
dst.data = new OutputStreamWriter(output, StandardCharsets.UTF_8);
dst.send = Runtime::file_write;
dst.release = Runtime::file_close;
dst.notify = Runtime::file_flush;
return Machine.exec_unify(args[1], dst);
}
/******************************************************************/
/* HTTP Lib Init */
/******************************************************************/
public static void main() {
Store.add("os_open_promise_opts", 4, Special.make_check(httplib::test_os_open_promise_opts));
Store.add("os_open_sync_opts", 4, Special.make_check(httplib::test_os_open_sync_opts));
Store.add("http_server_new", 1, Special.make_check(httplib::test_http_server_new));
Store.add("sys_http_server_on", 3, Special.make_check(httplib::test_sys_http_server_on));
Store.add("http_server_listen", 2, Special.make_check(httplib::test_http_server_listen));
Store.add("http_current_method", 2, Special.make_check(httplib::test_http_current_method));
Store.add("http_current_path", 2, Special.make_check(httplib::test_http_current_path));
Store.add("http_input_promise", 3, Special.make_check(httplib::test_http_input_promise));
Store.add("sys_http_write_head", 3, Special.make_check(httplib::test_sys_http_write_head));
Store.add("http_output_new", 2, Special.make_check(httplib::test_http_output_new));
}
}