mkxp-z/binding/http-binding.cpp

242 lines
6.6 KiB
C++

//
// http-binding.cpp
// mkxp-z
//
// Created by ゾロアーク on 12/29/20.
//
#include <stdio.h>
#include "binding-util.h"
#include "net/net.h"
#include "util/json5pp.hpp"
RB_METHOD(httpGet) {
RB_UNUSED_PARAM;
VALUE path;
rb_scan_args(argc, argv, "1", &path);
SafeStringValue(path);
VALUE ret;
try {
mkxp_net::HTTPRequest req(RSTRING_PTR(path));
auto res = req.get();
ret = rb_hash_new();
rb_hash_aset(ret, ID2SYM(rb_intern("status")), INT2NUM(res.status()));
rb_hash_aset(ret, ID2SYM(rb_intern("body")), rb_str_new_cstr(res.body().c_str()));
VALUE headers = rb_hash_new();
for (auto header : res.headers()) {
VALUE key = rb_str_new_cstr(header.first.c_str());
VALUE val = rb_str_new_cstr(header.second.c_str());
rb_hash_aset(headers, key, val);
}
rb_hash_aset(ret, ID2SYM(rb_intern("headers")), headers);
} catch (Exception &e) {
raiseRbExc(e);
}
return ret;
}
RB_METHOD(httpPost) {
RB_UNUSED_PARAM;
VALUE path, postDataHash;
rb_scan_args(argc, argv, "2", &path, &postDataHash);
SafeStringValue(path);
Check_Type(postDataHash, T_HASH);
VALUE ret;
try {
mkxp_net::HTTPRequest req(RSTRING_PTR(path));
mkxp_net::StringMap postData;
VALUE keys = NUM2INT(rb_funcall(postDataHash, rb_intern("keys"), 0));
for (int i = 0; i < RARRAY_LEN(keys); i++) {
VALUE k = rb_ary_entry(keys, i);
VALUE v = rb_hash_aref(postDataHash, k);
SafeStringValue(k);
SafeStringValue(v);
postData.emplace(RSTRING_PTR(k), RSTRING_PTR(v));
}
auto res = req.post(postData);
ret = rb_hash_new();
rb_hash_aset(ret, ID2SYM(rb_intern("status")), INT2NUM(res.status()));
rb_hash_aset(ret, ID2SYM(rb_intern("body")), rb_str_new_cstr(res.body().c_str()));
VALUE headers = rb_hash_new();
for (auto header : res.headers()) {
VALUE key = rb_str_new_cstr(header.first.c_str());
VALUE val = rb_str_new_cstr(header.second.c_str());
rb_hash_aset(headers, key, val);
}
rb_hash_aset(ret, ID2SYM(rb_intern("headers")), headers);
} catch (Exception &e) {
raiseRbExc(e);
}
return ret;
}
RB_METHOD(httpPostBody) {
RB_UNUSED_PARAM;
VALUE path, body, ctype;
rb_scan_args(argc, argv, "3", &path, &body, &ctype);
SafeStringValue(path);
SafeStringValue(body);
SafeStringValue(ctype);
VALUE ret;
try {
mkxp_net::HTTPRequest req(RSTRING_PTR(path));
mkxp_net::StringMap postData;
auto res = req.post(RSTRING_PTR(body), RSTRING_PTR(ctype));
ret = rb_hash_new();
rb_hash_aset(ret, ID2SYM(rb_intern("status")), INT2NUM(res.status()));
rb_hash_aset(ret, ID2SYM(rb_intern("body")), rb_str_new_cstr(res.body().c_str()));
VALUE headers = rb_hash_new();
for (auto header : res.headers()) {
VALUE key = rb_str_new_cstr(header.first.c_str());
VALUE val = rb_str_new_cstr(header.second.c_str());
rb_hash_aset(headers, key, val);
}
rb_hash_aset(ret, ID2SYM(rb_intern("headers")), headers);
} catch (Exception &e) {
raiseRbExc(e);
}
return ret;
}
VALUE json2rb(json5pp::value v) {
if (v.is_null())
return Qnil;
if (v.is_number())
return rb_float_new(v.as_number());
if (v.is_string())
return rb_str_new_cstr(v.as_string().c_str());
if (v.is_boolean())
return rb_bool_new(v.as_boolean());
if (v.is_integer())
return LL2NUM(v.as_integer());
if (v.is_array()) {
auto &a = v.as_array();
VALUE ret = rb_ary_new();
for (auto item : a) {
rb_ary_push(ret, json2rb(item));
}
return ret;
}
if (v.is_object()) {
auto &o = v.as_object();
VALUE ret = rb_hash_new();
for (auto pair : o) {
rb_hash_aset(ret, rb_str_new_cstr(pair.first.c_str()), json2rb(pair.second));
}
return ret;
}
}
json5pp::value rb2json(VALUE v) {
if (v == Qnil)
return json5pp::value(nullptr);
if (RB_TYPE_P(v, RUBY_T_FLOAT))
return json5pp::value(RFLOAT_VALUE(v));
if (RB_TYPE_P(v, RUBY_T_STRING))
return json5pp::value(RSTRING_PTR(v));
if (v == Qtrue || v == Qfalse)
return json5pp::value(RTEST(v));
if (RB_TYPE_P(v, RUBY_T_FIXNUM))
return json5pp::value(NUM2DBL(v));
if (RB_TYPE_P(v, RUBY_T_ARRAY)) {
json5pp::value ret_value = json5pp::array({});
auto &ret = ret_value.as_array();
for (int i = 0; i < RARRAY_LEN(v); i++) {
ret.push_back(rb2json(rb_ary_entry(v, i)));
}
return ret_value;
}
if (RTEST(rb_funcall(v, rb_intern("is_a?"), 1, rb_cHash))) {
json5pp::value ret_value = json5pp::object({});
auto &ret = ret_value.as_object();
VALUE keys = rb_funcall(v, rb_intern("keys"), 0);
for (int i = 0; i < RARRAY_LEN(keys); i++) {
VALUE key = rb_ary_entry(keys, i); SafeStringValue(key);
VALUE val = rb_hash_aref(v, key);
ret.emplace(RSTRING_PTR(key), rb2json(val));
}
return ret_value;
}
raiseRbExc(Exception(Exception::MKXPError, "Invalid value for JSON: %s", RSTRING_PTR(rb_inspect(v))));
}
RB_METHOD(httpJsonParse) {
RB_UNUSED_PARAM;
VALUE jsonv;
rb_scan_args(argc, argv, "1", &jsonv);
SafeStringValue(jsonv);
json5pp::value v;
try {
v = json5pp::parse5(RSTRING_PTR(jsonv));
}
catch (...) {
raiseRbExc(Exception(Exception::MKXPError, "Failed to parse JSON"));
}
return json2rb(v);
}
RB_METHOD(httpJsonStringify) {
RB_UNUSED_PARAM;
VALUE obj;
rb_scan_args(argc, argv, "1", &obj);
json5pp::value v = rb2json(obj);
return rb_str_new_cstr(v.stringify().c_str());
}
void httpBindingInit() {
VALUE mNet = rb_define_module("HTTPLite");
_rb_define_module_function(mNet, "get", httpGet);
_rb_define_module_function(mNet, "post", httpPost);
_rb_define_module_function(mNet, "post_body", httpPostBody);
VALUE mNetJSON = rb_define_module_under(mNet, "JSON");
_rb_define_module_function(mNetJSON, "stringify", httpJsonStringify);
_rb_define_module_function(mNetJSON, "parse", httpJsonParse);
}