fix: content length & dns

This commit is contained in:
源文雨 2024-04-15 23:58:32 +09:00
parent fdee77d778
commit 90d3d676f3
9 changed files with 220 additions and 89 deletions

View File

@ -12,7 +12,7 @@ TARGET_SDK := android23
TARGET_ARCH := aarch64 # optional: armv7a i686 x86_64
CGO_ENABLED := 1
GO_SRC := $(shell find . -name '*.go')
GO_SRC := $(shell find . -name '*.go' | grep -v '_test.go$$')
NDK_TOOLCHAIN := ~/Library/Android/sdk/ndk/$(NDK_VERSION)/toolchains/llvm/prebuilt/$(BUILD_MACHINE)-$(BUILD_ARCH)
CC := $(NDK_TOOLCHAIN)/bin/$(TARGET_ARCH)-linux-$(TARGET_SDK)-clang
TEST_OUTPUT = '$(shell cd $(BUILD_PATH) && ./test | head -c 12)'

19
dns.go
View File

@ -48,14 +48,28 @@ func (ds *dnsservers) add(m map[string][]string) {
func (ds *dnsservers) dial(ctx context.Context) (tlsConn *tls.Conn, err error) {
ds.RLock()
defer ds.RUnlock()
if dialer.Timeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, dialer.Timeout)
defer cancel()
}
if !dialer.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, dialer.Deadline)
defer cancel()
}
var conn net.Conn
for host, addrs := range ds.m {
for _, addr := range addrs {
if !addr.E {
continue
}
conn, err = net.Dial("tcp", addr.A)
conn, err = dialer.DialContext(ctx, "tcp", addr.A)
if err != nil {
addr.E = false // no need to acquire write lock
continue
}
tlsConn = terasu.Use(tls.Client(conn, &tls.Config{ServerName: host}))
@ -63,6 +77,7 @@ func (ds *dnsservers) dial(ctx context.Context) (tlsConn *tls.Conn, err error) {
if err == nil {
return
}
_ = tlsConn.Close()
addr.E = false // no need to acquire write lock
}
}
@ -129,7 +144,7 @@ var dotv4servers = dnsservers{
var resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
if canUseIPv6.Get() {
return dotv6servers.dial(ctx)
}

View File

@ -16,7 +16,6 @@ func TestResolver(t *testing.T) {
t.Fatal(err)
}
t.Log(addrs)
t.Fail()
}
func TestDNS(t *testing.T) {

6
go.mod
View File

@ -3,6 +3,10 @@ module comandy
go 1.22.1
require (
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905
github.com/fumiama/terasu v0.0.0-20240415131749-e65650a52c3c
golang.org/x/net v0.24.0
)
require golang.org/x/text v0.14.0 // indirect

18
go.sum
View File

@ -1,4 +1,18 @@
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 h1:g4pTnDJUW4VbJ9NvoRfUvdjDrHz/6QhfN/LoIIpICbo=
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
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=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fumiama/terasu v0.0.0-20240415131749-e65650a52c3c h1:RxkHkeanPDrZrEEcUcosgBULmL8UDkSasvwP+jpdIZQ=
github.com/fumiama/terasu v0.0.0-20240415131749-e65650a52c3c/go.mod h1:BFl0X1+rGJf8bLHl/kO+v05ryHrj/R4kyCrK89NvegA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

141
http.go
View File

@ -5,36 +5,81 @@ import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"reflect"
"strings"
"sync"
"time"
"github.com/FloatTech/ttl"
"github.com/fumiama/terasu"
"golang.org/x/net/http2"
)
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)
var lookupTable = ttl.NewCache[string, []string](time.Hour)
type comandyClient http.Client
var cli = comandyClient(http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
if dialer.Timeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, dialer.Timeout)
defer cancel()
}
if !dialer.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, dialer.Deadline)
defer cancel()
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(addr)
addrs := lookupTable.Get(host)
if len(addrs) == 0 {
addrs, err = resolver.LookupHost(ctx, host)
if err != nil {
return nil, err
}
return terasu.Use(tls.Client(conn, &tls.Config{
ServerName: host,
InsecureSkipVerify: true,
})), nil
},
},
lookupTable.Set(host, addrs)
}
if len(addr) == 0 {
return nil, errors.New("empty host addr")
}
var tlsConn *tls.Conn
for _, a := range addrs {
if strings.Contains(a, ":") {
a = "[" + a + "]:" + port
} else {
a += ":" + port
}
conn, err := dialer.DialContext(ctx, network, a)
if err != nil {
continue
}
tlsConn = terasu.Use(tls.Client(conn, cfg))
err = tlsConn.HandshakeContext(ctx)
if err == nil {
break
}
_ = tlsConn.Close()
}
return tlsConn, nil
},
},
})
type capsule struct {
C int `json:"code,omitempty"`
@ -59,3 +104,77 @@ func (r *capsule) printstrerr(err string) string {
_ = json.NewEncoder(&buf).Encode(r)
return buf.String()
}
func (cli *comandyClient) request(para string) string {
r := capsule{}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
_ = canUseIPv6.Get()
}()
err := json.Unmarshal(stringToBytes(para), &r)
if err != nil {
return r.printerr(err)
}
if r.U == "" || !strings.HasPrefix(r.U, "https://") {
return r.printstrerr("invalid url '" + r.U + "'")
}
if r.M != "GET" && r.M != "POST" && r.M != "DELETE" {
return r.printstrerr("invalid method '" + r.U + "'")
}
var body io.Reader
if len(r.D) > 0 {
body = strings.NewReader(r.D)
}
req, err := http.NewRequest(r.M, r.U, body)
if err != nil {
return 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 r.printstrerr("unsupported H type " + reflect.ValueOf(x).Type().Name())
}
}
fmt.Println(r.U)
wg.Wait()
resp, err := (*http.Client)(cli).Do(req)
if err != nil {
return r.printerr(err)
}
defer resp.Body.Close()
sb := strings.Builder{}
enc := base64.NewEncoder(base64.StdEncoding, &sb)
_, err = io.Copy(enc, resp.Body)
_ = enc.Close()
if err != nil {
return 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 r.printerr(err)
}
return outbuf.String()
}

49
http_test.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"encoding/json"
"io"
"net/http"
"testing"
)
func TestClientGet(t *testing.T) {
_ = canUseIPv6.Get()
req, err := http.NewRequest("GET", "https://api.mangacopy.com/api/v3/h5/homeIndex?platform=3", nil)
if err != nil {
t.Fatal(err)
}
resp, err := (*http.Client)(&cli).Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
t.Log("[T] response code", resp.StatusCode)
for k, vs := range resp.Header {
for _, v := range vs {
t.Log("[T] response header", k+":", v)
}
}
data, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
t.Log(bytesToString(data))
t.Fail()
}
func TestRequest(t *testing.T) {
r := cli.request(`{"code":0,"headers":{"authorization":"Token ","host":"api.mangacopy.com","source":"copyApp","webp":"1","region":"1","version":"2.1.7","platform":"3","user-agent":"COPY/2.1.7"},"method":"GET","url":"https://api.mangacopy.com/api/v3/h5/homeIndex?platform\u003d3"}`)
t.Log(r)
c := capsule{}
err := json.Unmarshal(stringToBytes(r), &c)
if err != nil {
t.Fatal(err)
}
if c.C != http.StatusOK {
t.Fatal("status code", c.C)
}
if len(c.D) == 0 {
t.Fatal("empty data")
}
}

69
main.go
View File

@ -3,12 +3,7 @@ package main
import "C"
import (
"encoding/base64"
"encoding/json"
"io"
"net/http"
"reflect"
"strings"
)
func main() {}
@ -45,67 +40,5 @@ func add_dns(para *C.char, is_ipv6 C.int) *C.char {
//
//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, "https://") {
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 + "'"))
}
var body io.Reader
if len(r.D) > 0 {
body = strings.NewReader(r.D)
}
req, err := http.NewRequest(r.M, r.U, body)
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())
return C.CString(cli.request(C.GoString(para)))
}

View File

@ -14,12 +14,10 @@ type slice struct {
cap int
}
/*
// bytesToString 没有内存开销的转换
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
*/
// stringToBytes 没有内存开销的转换
func stringToBytes(s string) (b []byte) {