comandy/http.go

186 lines
4.0 KiB
Go
Raw Normal View History

2024-04-15 17:10:06 +09:00
package main
import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
2024-04-15 23:58:32 +09:00
"errors"
"fmt"
"io"
2024-04-15 17:10:06 +09:00
"net"
"net/http"
2024-04-15 23:58:32 +09:00
"reflect"
2024-04-15 17:10:06 +09:00
"strings"
2024-04-15 23:58:32 +09:00
"sync"
2024-04-15 17:10:06 +09:00
"time"
2024-04-15 23:58:32 +09:00
"github.com/FloatTech/ttl"
2024-04-15 17:10:06 +09:00
"github.com/fumiama/terasu"
2024-04-15 23:58:32 +09:00
"golang.org/x/net/http2"
2024-04-15 17:10:06 +09:00
)
2024-04-17 00:31:31 +09:00
var httpdialer = net.Dialer{
2024-04-15 17:10:06 +09:00
Timeout: time.Minute,
}
2024-04-15 23:58:32 +09:00
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) {
2024-04-17 00:31:31 +09:00
if httpdialer.Timeout != 0 {
2024-04-15 23:58:32 +09:00
var cancel context.CancelFunc
2024-04-17 00:31:31 +09:00
ctx, cancel = context.WithTimeout(ctx, httpdialer.Timeout)
2024-04-15 23:58:32 +09:00
defer cancel()
2024-04-15 17:10:06 +09:00
}
2024-04-15 23:58:32 +09:00
2024-04-17 00:31:31 +09:00
if !httpdialer.Deadline.IsZero() {
2024-04-15 23:58:32 +09:00
var cancel context.CancelFunc
2024-04-17 00:31:31 +09:00
ctx, cancel = context.WithDeadline(ctx, httpdialer.Deadline)
2024-04-15 23:58:32 +09:00
defer cancel()
}
host, port, err := net.SplitHostPort(addr)
2024-04-15 17:10:06 +09:00
if err != nil {
return nil, err
}
2024-04-15 23:58:32 +09:00
addrs := lookupTable.Get(host)
if len(addrs) == 0 {
addrs, err = resolver.LookupHost(ctx, host)
if err != nil {
return nil, err
}
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
}
2024-04-17 00:31:31 +09:00
conn, err := httpdialer.DialContext(ctx, network, a)
2024-04-15 23:58:32 +09:00
if err != nil {
continue
}
2024-04-16 15:27:06 +09:00
tlsConn = tls.Client(conn, cfg)
err = terasu.Use(tlsConn).HandshakeContext(ctx)
2024-04-15 23:58:32 +09:00
if err == nil {
break
}
_ = tlsConn.Close()
}
return tlsConn, nil
2024-04-15 17:10:06 +09:00
},
},
2024-04-15 23:58:32 +09:00
})
2024-04-15 17:10:06 +09:00
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()
}
2024-04-15 23:58:32 +09:00
2024-04-16 15:27:06 +09:00
func (cli *comandyClient) request(para string) (ret string) {
2024-04-15 23:58:32 +09:00
r := capsule{}
2024-04-16 15:27:06 +09:00
defer func() {
err := recover()
if err != nil {
ret = r.printstrerr(fmt.Sprint())
}
}()
2024-04-15 23:58:32 +09:00
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())
}
}
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()
}