Skip to content

Commit 152cce8

Browse files
committed
Initial Android support to lua-https
Code is untested.
1 parent ec8529c commit 152cce8

3 files changed

Lines changed: 362 additions & 0 deletions

File tree

src/android/AndroidClient.cpp

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#include "AndroidClient.h"
2+
3+
#ifdef USE_ANDROID_BACKEND
4+
5+
#include <sstream>
6+
#include <type_traits>
7+
8+
#include <dlfcn.h>
9+
10+
std::string replace(const std::string &str, const std::string &from, const std::string &to)
11+
{
12+
std::stringstream ss;
13+
size_t oldpos = 0;
14+
15+
while (true)
16+
{
17+
size_t pos = str.find(from, oldpos);
18+
19+
if (pos == std::string::npos)
20+
{
21+
ss << str.substr(oldpos);
22+
break;
23+
}
24+
25+
ss << str.substr(oldpos, pos - oldpos) << to;
26+
oldpos = pos + from.length();
27+
}
28+
29+
return ss.str();
30+
}
31+
32+
std::string fromJavaMUTF(const std::string &str)
33+
{
34+
static std::string null("", 1);
35+
return replace(str, "\xC0\x80", null);
36+
}
37+
38+
jstring newStringUTF(JNIEnv *env, const std::string &str)
39+
{
40+
static std::string null("", 1);
41+
42+
std::string newStr = replace(str, null, "\xC0\x80");
43+
jstring jstr = env->NewStringUTF(newStr.c_str());
44+
return jstr;
45+
}
46+
47+
std::string getStringUTF(JNIEnv *env, jstring str)
48+
{
49+
static std::string null("", 1);
50+
51+
const char *c = env->GetStringUTFChars(str, nullptr);
52+
std::string result = replace(c, "\xC0\x80", null);
53+
54+
env->ReleaseStringUTFChars(str, c);
55+
return result;
56+
}
57+
58+
AndroidClient::AndroidClient()
59+
: HTTPSClient()
60+
, SDL_AndroidGetJNIEnv(nullptr)
61+
{
62+
// Look for SDL_AndroidGetJNIEnv
63+
SDL_AndroidGetJNIEnv = (decltype(SDL_AndroidGetJNIEnv)) dlsym(RTLD_DEFAULT, "SDL_AndroidGetJNIEnv");
64+
}
65+
66+
bool AndroidClient::valid() const
67+
{
68+
if (SDL_AndroidGetJNIEnv)
69+
{
70+
JNIEnv *env = SDL_AndroidGetJNIEnv();
71+
72+
if (env)
73+
{
74+
jclass httpsClass = env->FindClass("org/love2d/luahttps/LuaHTTPS");
75+
env->DeleteLocalRef(httpsClass);
76+
return httpsClass != nullptr;
77+
}
78+
}
79+
80+
return false;
81+
}
82+
83+
HTTPSClient::Reply AndroidClient::request(const HTTPSClient::Request &req)
84+
{
85+
JNIEnv *env = SDL_AndroidGetJNIEnv();
86+
jclass httpsClass = env->FindClass("org/love2d/luahttps/LuaHTTPS");
87+
88+
if (httpsClass == nullptr)
89+
{
90+
env->ExceptionClear();
91+
throw std::runtime_error("Could not find class 'org.love2d.luahttps.LuaHTTPS'");
92+
}
93+
94+
jmethodID constructor = env->GetMethodID(httpsClass, "<init>", "()V");
95+
jmethodID setURL = env->GetMethodID(httpsClass, "setUrl", "(Ljava/lang/String;)V");
96+
jmethodID request = env->GetMethodID(httpsClass, "request", "()Z");
97+
jmethodID getInterleavedHeaders = env->GetMethodID(httpsClass, "getInterleavedHeaders", "()[Ljava/lang/String;");
98+
jmethodID getResponse = env->GetMethodID(httpsClass, "getResponse", "()[B");
99+
jmethodID getResponseCode = env->GetMethodID(httpsClass, "getResponseCode", "()I");
100+
101+
jobject httpsObject = env->NewObject(httpsClass, constructor);
102+
103+
// Set URL
104+
jstring url = env->NewStringUTF(req.url.c_str());
105+
env->CallVoidMethod(httpsObject, setURL, url);
106+
env->DeleteLocalRef(url);
107+
108+
// Set post data
109+
if (req.method == Request::POST)
110+
{
111+
jmethodID setPostData = env->GetMethodID(httpsClass, "setPostData", "([B)V");
112+
jbyteArray byteArray = env->NewByteArray((jsize) req.postdata.length());
113+
jbyte *byteArrayData = env->GetByteArrayElements(byteArray, nullptr);
114+
115+
memcpy(byteArrayData, req.postdata.data(), req.postdata.length());
116+
env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
117+
118+
env->CallVoidMethod(httpsObject, setPostData, byteArray);
119+
env->DeleteLocalRef(byteArray);
120+
}
121+
122+
// Set headers
123+
if (!req.headers.empty())
124+
{
125+
jmethodID addHeader = env->GetMethodID(httpsClass, "addHeader", "(Ljava/lang/String;Ljava/lang/String;)V");
126+
127+
for (auto &header : req.headers)
128+
{
129+
jstring headerKey = newStringUTF(env, header.first);
130+
jstring headerValue = newStringUTF(env, header.second);
131+
132+
env->CallVoidMethod(httpsObject, addHeader, headerKey, headerValue);
133+
env->DeleteLocalRef(headerKey);
134+
env->DeleteLocalRef(headerValue);
135+
}
136+
}
137+
138+
// Do request
139+
HTTPSClient::Reply response;
140+
jboolean status = env->CallBooleanMethod(httpsObject, request);
141+
142+
// Get response
143+
response.responseCode = env->CallIntMethod(httpsObject, getResponseCode);
144+
145+
if (status)
146+
{
147+
// Get headers
148+
jobjectArray interleavedHeaders = (jobjectArray) env->CallObjectMethod(httpsObject, getInterleavedHeaders);
149+
int len = env->GetArrayLength(interleavedHeaders);
150+
151+
for (int i = 0; i < len; i += 2)
152+
{
153+
jstring key = (jstring) env->GetObjectArrayElement(interleavedHeaders, i);
154+
jstring value = (jstring) env->GetObjectArrayElement(interleavedHeaders, i + 1);
155+
156+
response.headers[getStringUTF(env, key)] = getStringUTF(env, value);
157+
158+
env->DeleteLocalRef(key);
159+
env->DeleteLocalRef(value);
160+
}
161+
162+
env->DeleteLocalRef(interleavedHeaders);
163+
164+
// Get response data
165+
jbyteArray responseData = (jbyteArray) env->CallObjectMethod(httpsObject, getResponse);
166+
167+
if (responseData)
168+
{
169+
int len = env->GetArrayLength(responseData);
170+
jbyte *responseByte = env->GetByteArrayElements(responseData, nullptr);
171+
172+
response.body = std::string((char *) responseByte, len);
173+
174+
env->DeleteLocalRef(responseData);
175+
}
176+
}
177+
178+
return response;
179+
}
180+
181+
#endif

src/android/AndroidClient.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include "common/config.h"
4+
5+
#ifdef USE_ANDROID_BACKEND
6+
7+
#include <jni.h>
8+
9+
#include "common/HTTPSClient.h"
10+
11+
class AndroidClient: public HTTPSClient
12+
{
13+
public:
14+
AndroidClient();
15+
16+
bool valid() const override;
17+
HTTPSClient::Reply request(const HTTPSClient::Request &req) override;
18+
19+
private:
20+
JNIEnv *(*SDL_AndroidGetJNIEnv)();
21+
};
22+
23+
#endif
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package org.love2d.luahttps;
2+
3+
import android.text.TextUtils;
4+
import android.util.Log;
5+
6+
import androidx.annotation.Keep;
7+
8+
import java.io.ByteArrayOutputStream;
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.io.OutputStream;
12+
import java.net.HttpURLConnection;
13+
import java.net.MalformedURLException;
14+
import java.net.URL;
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
19+
@Keep
20+
class LuaHTTPS {
21+
static private String TAG = "LuaHTTPS";
22+
23+
private String urlString;
24+
private byte[] postData;
25+
private byte[] response;
26+
private int responseCode;
27+
private HashMap<String, String> headers;
28+
29+
public LuaHTTPS() {
30+
headers = new HashMap<String, String>();
31+
reset();
32+
}
33+
34+
public void reset() {
35+
urlString = null;
36+
postData = null;
37+
response = null;
38+
responseCode = 0;
39+
headers.clear();
40+
}
41+
42+
public void setUrl(String url) {
43+
urlString = url;
44+
}
45+
46+
public void setPostData(byte[] postData) {
47+
this.postData = postData;
48+
}
49+
50+
public void addHeader(String key, String value) {
51+
headers.put(key, value);
52+
}
53+
54+
public String[] getInterleavedHeaders() {
55+
String[] result = new String[headers.size() * 2];
56+
int i = 0;
57+
58+
for (Map.Entry<String, String> header: headers.entrySet()) {
59+
result[i * 2] = header.getKey();
60+
result[i * 2 + 1] = header.getValue();
61+
i++;
62+
}
63+
64+
return result;
65+
}
66+
67+
public int getResponseCode() {
68+
return responseCode;
69+
}
70+
71+
public byte[] getResponse() {
72+
return response;
73+
}
74+
75+
public boolean request() {
76+
if (urlString == null) {
77+
return false;
78+
}
79+
80+
URL url;
81+
try {
82+
url = new URL(urlString);
83+
84+
if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
85+
return false;
86+
}
87+
} catch (MalformedURLException e) {
88+
Log.e(TAG, "Error", e);
89+
return false;
90+
}
91+
92+
HttpURLConnection connection;
93+
try {
94+
connection = (HttpURLConnection) url.openConnection();
95+
} catch (IOException e) {
96+
Log.e(TAG, "Error", e);
97+
return false;
98+
}
99+
100+
// Set header
101+
for (Map.Entry<String, String> headerData: headers.entrySet()) {
102+
connection.setRequestProperty(headerData.getKey(), headerData.getValue());
103+
}
104+
105+
// Set post data
106+
if (postData != null) {
107+
connection.setDoOutput(true);
108+
connection.setChunkedStreamingMode(0);
109+
110+
try {
111+
OutputStream out = connection.getOutputStream();
112+
out.write(postData);
113+
} catch (Exception e) {
114+
Log.e(TAG, "Error", e);
115+
connection.disconnect();
116+
return false;
117+
}
118+
}
119+
120+
// Request
121+
try {
122+
InputStream in;
123+
124+
// Set response code
125+
responseCode = connection.getResponseCode();
126+
if (responseCode >= 400) {
127+
in = connection.getErrorStream();
128+
} else {
129+
in = connection.getInputStream();
130+
}
131+
132+
// Read response
133+
int readed;
134+
byte[] temp = new byte[4096];
135+
ByteArrayOutputStream response = new ByteArrayOutputStream();
136+
137+
while ((readed = in.read(temp)) != -1) {
138+
response.write(temp, 0, readed);
139+
}
140+
141+
this.response = response.toByteArray();
142+
response.close();
143+
144+
// Read headers
145+
headers.clear();
146+
for (Map.Entry<String, List<String>> header: connection.getHeaderFields().entrySet()) {
147+
headers.put(header.getKey(), TextUtils.join(", ", header.getValue()));
148+
}
149+
} catch (Exception e) {
150+
Log.e(TAG, "Error", e);
151+
connection.disconnect();
152+
return false;
153+
}
154+
155+
connection.disconnect();
156+
return true;
157+
}
158+
}

0 commit comments

Comments
 (0)