This commit is contained in:
源文雨 2024-04-15 02:39:28 +09:00
parent 8550e86395
commit 9cf8968a82
7 changed files with 218 additions and 0 deletions

8
.gitignore vendored
View File

@ -19,3 +19,11 @@
# Go workspace file # Go workspace file
go.work go.work
# Cgo files
*.a
*.h
test
# MacOS system files
.DS_Store

33
Makefile Normal file
View File

@ -0,0 +1,33 @@
PROJECT_NAME := comandy
BUILD_PATH := build
GOOS := android
GOARCH := arm64
BUILD_MACHINE := darwin
BUILD_ARCH := x86_64
NDK_VERSION := 26.3.11579264
TARGET_SDK := android23
CGO_ENABLED := 1
GO_SRC := $(shell find . -name '*.go')
NDK_TOOLCHAIN := ~/Library/Android/sdk/ndk/$(NDK_VERSION)/toolchains/llvm/prebuilt/$(BUILD_MACHINE)-$(BUILD_ARCH)
CC := $(NDK_TOOLCHAIN)/bin/aarch64-linux-$(TARGET_SDK)-clang
TEST_OUTPUT = '$(shell cd $(BUILD_PATH) && ./test)'
TEST_EXPECTED = '{"code":500,"data":"aW52YWxpZCB1cmwgJyc="}'
all: shared
shared: $(GO_SRC) dir
GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=$(CGO_ENABLED) NDK_TOOLCHAIN=$(NDK_TOOLCHAIN) CC=$(CC) go build -buildmode=c-shared -o $(BUILD_PATH)/lib$(PROJECT_NAME).so $(GO_SRC)
test: dir
@GOOS=$(BUILD_MACHINE) CC=cc NDK_TOOLCHAIN="" $(MAKE) -e shared
cc -o $(BUILD_PATH)/test $(BUILD_PATH)/test.c -l$(PROJECT_NAME) -L$(BUILD_PATH)
runtest: test
@if [ $(TEST_OUTPUT) = $(TEST_EXPECTED) ]; then \
echo "test succeeded."; \
else \
echo "test failed, expected:" $(TEST_EXPECTED) "but got:" $(TEST_OUTPUT); \
fi
dir:
@if [ ! -d "$(BUILD_PATH)" ]; then mkdir $(BUILD_PATH); fi
clean:
@if [ -d "$(BUILD_PATH)" ]; then rm -rf $(BUILD_PATH)/lib$(PROJECT_NAME).*; fi

8
build/test.c Normal file
View File

@ -0,0 +1,8 @@
#include <stdio.h>
#include "libcomandy.h"
int main() {
char* msg = request("{}");
puts(msg);
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module comandy
go 1.22.1
require github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905 h1:PHf84+ujLpFGJbfytrwZT6/D7KojmjFm5Itv6te6WUA=
github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905/go.mod h1:BFl0X1+rGJf8bLHl/kO+v05ryHrj/R4kyCrK89NvegA=

130
main.go Normal file
View File

@ -0,0 +1,130 @@
package main
import "C"
import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
"io"
"net"
"net/http"
"reflect"
"strings"
"time"
"github.com/fumiama/terasu"
)
func main() {}
var dialer = net.Dialer{
Timeout: time.Minute,
}
var cli = http.Client{
Transport: &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return terasu.Use(tls.Client(conn, &tls.Config{
ServerName: host,
InsecureSkipVerify: true,
})), nil
},
},
}
type capsule struct {
C int `json:"code,omitempty"`
M string `json:"method,omitempty"`
U string `json:"url,omitempty"`
H map[string]any `json:"headers,omitempty"`
D string `json:"data,omitempty"`
}
func (r *capsule) printerr(err error) string {
buf := strings.Builder{}
r.C = http.StatusInternalServerError
r.D = base64.StdEncoding.EncodeToString(stringToBytes(err.Error()))
_ = json.NewEncoder(&buf).Encode(r)
return buf.String()
}
func (r *capsule) printstrerr(err string) string {
buf := strings.Builder{}
r.C = http.StatusInternalServerError
r.D = base64.StdEncoding.EncodeToString(stringToBytes(err))
_ = json.NewEncoder(&buf).Encode(r)
return buf.String()
}
//export request
func request(para *C.char) *C.char {
r := capsule{}
err := json.Unmarshal(stringToBytes(C.GoString(para)), &r)
if err != nil {
return C.CString(r.printerr(err))
}
if r.U == "" || !strings.HasPrefix(r.U, "http") {
return C.CString(r.printstrerr("invalid url '" + r.U + "'"))
}
if r.M != "GET" && r.M != "POST" && r.M != "DELETE" {
return C.CString(r.printstrerr("invalid method '" + r.U + "'"))
}
req, err := http.NewRequest(r.M, r.U, strings.NewReader(r.D))
if err != nil {
return C.CString(r.printerr(err))
}
for k, vs := range r.H {
lk := strings.ToLower(k)
if strings.HasPrefix(lk, "x-") {
continue
}
switch x := vs.(type) {
case string:
req.Header.Add(k, x)
case []string:
for _, v := range x {
req.Header.Add(k, v)
}
default:
return C.CString(r.printstrerr("unsupported H type " + reflect.ValueOf(x).Type().Name()))
}
}
resp, err := cli.Do(req)
if err != nil {
return C.CString(r.printerr(err))
}
defer resp.Body.Close()
sb := strings.Builder{}
enc := base64.NewEncoder(base64.StdEncoding, &sb)
_, err = io.CopyN(enc, resp.Body, resp.ContentLength)
_ = enc.Close()
if err != nil {
return C.CString(r.printerr(err))
}
r.C = resp.StatusCode
r.H = make(map[string]any, len(resp.Header)*2)
for k, vs := range resp.Header {
if len(vs) == 1 {
r.H[k] = vs[0]
continue
}
r.H[k] = vs
}
r.D = sb.String()
outbuf := strings.Builder{}
err = json.NewEncoder(&outbuf).Encode(&r)
if err != nil {
return C.CString(r.printerr(err))
}
return C.CString(outbuf.String())
}

32
utils.go Normal file
View File

@ -0,0 +1,32 @@
package main
import "unsafe"
// slice is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
//
// Unlike reflect.SliceHeader, its Data field is sufficient to guarantee the
// data it references will not be garbage collected.
type slice struct {
data unsafe.Pointer
len int
cap int
}
/*
// bytesToString 没有内存开销的转换
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
*/
// stringToBytes 没有内存开销的转换
func stringToBytes(s string) (b []byte) {
bh := (*slice)(unsafe.Pointer(&b))
sh := (*slice)(unsafe.Pointer(&s))
bh.data = sh.data
bh.len = sh.len
bh.cap = sh.len
return b
}