init
This commit is contained in:
		
							parent
							
								
									8550e86395
								
							
						
					
					
						commit
						9cf8968a82
					
				
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -19,3 +19,11 @@
 | 
			
		||||
 | 
			
		||||
# Go workspace file
 | 
			
		||||
go.work
 | 
			
		||||
 | 
			
		||||
# Cgo files
 | 
			
		||||
*.a
 | 
			
		||||
*.h
 | 
			
		||||
test
 | 
			
		||||
 | 
			
		||||
# MacOS system files
 | 
			
		||||
.DS_Store
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Makefile
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										8
									
								
								build/test.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
#include "libcomandy.h"
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
    char* msg = request("{}");
 | 
			
		||||
    puts(msg);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								go.mod
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										130
									
								
								main.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										32
									
								
								utils.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user