Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add minimal native HTTP client #14

Merged
merged 1 commit into from
Oct 26, 2024
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
8 changes: 8 additions & 0 deletions projects/catculator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Catculator

Native implementations of some of Kit Tunes features.

## Development

Test new binaries by setting the `catculator.natives_path` system property when running the game:
Example: `-Dcatculator.natives_path=/home/lilly/projects/kit-tunes/projects/catculator/target/debug`
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.pixaurora.catculator.api.error;

public class ClientResponseException extends Exception {
public ClientResponseException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.pixaurora.catculator.api.http;

import net.pixaurora.catculator.impl.http.ClientImpl;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

public interface Client extends AutoCloseable {
static @NotNull Client create(String userAgent) throws IOException {
return new ClientImpl(userAgent);
}

@NotNull RequestBuilder get(String url);
@NotNull RequestBuilder post(String url);

@Override
void close(); // Remove throws Exception from AutoCloseable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.pixaurora.catculator.api.http;

import net.pixaurora.catculator.api.error.ClientResponseException;
import org.jetbrains.annotations.NotNull;

public interface RequestBuilder {
@NotNull Response send() throws ClientResponseException;

@NotNull RequestBuilder body(byte[] data);
@NotNull RequestBuilder query(String key, String value);
@NotNull RequestBuilder header(String key, String value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.pixaurora.catculator.api.http;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public interface Response {
int status();
byte[] body();
@Nullable String header(@NotNull String name);

default boolean ok() {
return this.status() >= 200 && this.status() < 300;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static Server create() {
return new ServerImpl();
}

public String runServer() throws IOException;
public String run() throws IOException;

public void close();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package net.pixaurora.catculator.impl.http;

import net.pixaurora.catculator.api.http.Client;
import net.pixaurora.catculator.api.http.RequestBuilder;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

public class ClientImpl implements Client {
private final long ptr;
private boolean active = true;

public ClientImpl(String userAgent) throws IOException {
this.ptr = create(userAgent);
}

@Override
public @NotNull RequestBuilder get(String url) {
if (this.active) {
return this.request("GET", url);
} else {
throw new RuntimeException("HTTP client inactive.");
}
}

@Override
public @NotNull RequestBuilder post(String url) {
if (this.active) {
return this.request("POST", url);
} else {
throw new RuntimeException("HTTP client inactive.");
}
}

@Override
public void close() {
if (!this.active) {
return;
}

this.drop();
this.active = false;
}

private native @NotNull RequestBuilder request(String method, String url);

private static native long create(@NotNull String userAgent) throws IOException;
private native void drop();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package net.pixaurora.catculator.impl.http;

import net.pixaurora.catculator.api.http.RequestBuilder;
import net.pixaurora.catculator.api.http.Response;
import org.jetbrains.annotations.NotNull;

public class RequestBuilderImpl implements RequestBuilder {
private long ptr;

private RequestBuilderImpl(long ptr) {
this.ptr = ptr;
}

@Override
public @NotNull Response send() {
return this.send0();
}

@Override
public @NotNull RequestBuilder body(byte[] data) {
this.body0(data);
return this;
}

@Override
public @NotNull RequestBuilder query(String key, String value) {
this.query0(key, value);
return this;
}

@Override
public @NotNull RequestBuilder header(String key, String value) {
this.header0(key, value);
return this;
}

private native @NotNull Response send0();

private native void body0(byte[] data);
private native void query0(String key, String value);
private native void header0(String key, String value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package net.pixaurora.catculator.impl.http;

import net.pixaurora.catculator.api.http.Response;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;

public class ResponseImpl implements Response {
private final int status;
private final byte[] body;
private final Map<String, String> headers;

private ResponseImpl(int status, byte[] body, Map<String, String> headers) {
this.status = status;
this.body = body;
this.headers = headers;
}

@Override
public int status() {
return this.status;
}

@Override
public byte[] body() {
return this.body;
}

@Override
public @Nullable String header(@NotNull String name) {
return this.headers.get(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
import net.pixaurora.catculator.api.http.Server;

public class ServerImpl implements Server {
private final long pointer;
private final long ptr;

public ServerImpl() {
this.pointer = create();
this.ptr = create();
}

private static native long create();

@Override
public String runServer() {
return this.runServer0();
public String run() {
return this.run0();
}

private native String runServer0();
private native String run0();

@Override
public void close() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.pixaurora.catculator.impl.util;

import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;

public class JniUtil {
private static @NotNull Map<String, String> newMap() {
return new HashMap<>();
}
}
9 changes: 0 additions & 9 deletions projects/catculator/src/main/rust/bridge.rs

This file was deleted.

1 change: 0 additions & 1 deletion projects/catculator/src/main/rust/bridge/http.rs

This file was deleted.

83 changes: 83 additions & 0 deletions projects/catculator/src/main/rust/bridge/http/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use jni::{
objects::{JClass, JObject, JString, JValue},
sys::jlong,
JNIEnv,
};
use reqwest::{blocking::Client, Method};

use crate::{
bridge::{drop_box, pack_box, pull_box},
utils::JStringToString,
Error, Result,
};

const REQUEST_BUILDER_CLASS: &str = "net/pixaurora/catculator/impl/http/RequestBuilderImpl";

fn create(env: &mut JNIEnv, user_agent: JString) -> Result<jlong> {
let client = Client::builder()
.https_only(true)
.user_agent(user_agent.to_string(env)?)
.build()?;

Ok(pack_box(client))
}

#[no_mangle]
pub extern "system" fn Java_net_pixaurora_catculator_impl_http_ClientImpl_create<'r>(
mut env: JNIEnv<'r>,
_class: JClass<'r>,
user_agent: JString<'r>,
) -> jlong {
match create(&mut env, user_agent) {
Ok(ptr) => return ptr,
Err(error) => error.throw(&mut env),
}

jlong::default()
}

#[no_mangle]
pub extern "system" fn Java_net_pixaurora_catculator_impl_http_ClientImpl_drop<'r>(
mut env: JNIEnv<'r>,
this: JObject<'r>,
) -> () {
if let Err(error) = drop_box::<Client>(&mut env, &this) {
panic!("Couldn't drop http client due to an error! {}", error);
}
}

fn request<'r>(
env: &mut JNIEnv<'r>,
this: &JObject<'r>,
method: JString<'r>,
url: JString<'r>,
) -> Result<JObject<'r>> {
let client = pull_box::<Client>(env, this)?;

let method = match Method::from_bytes(method.to_string(env)?.as_bytes()) {
Ok(method) => method,
Err(_) => return Err(Error::String(String::from("Invalid HTTP method."))),
};

let ptr = pack_box(client.request(method, url.to_string(env)?));

let class = env.find_class(REQUEST_BUILDER_CLASS)?;
let instance = env.new_object(class, "(J)V", &[JValue::Long(ptr)])?;

Ok(instance)
}

#[no_mangle]
pub extern "system" fn Java_net_pixaurora_catculator_impl_http_ClientImpl_request<'r>(
mut env: JNIEnv<'r>,
this: JObject<'r>,
method: JString<'r>,
url: JString<'r>,
) -> JObject<'r> {
match request(&mut env, &this, method, url) {
Ok(object) => return object,
Err(error) => error.throw(&mut env),
};

JObject::null()
}
Loading