coai/utils/net.go

301 lines
7.5 KiB
Go

package utils
import (
"bytes"
"chat/globals"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"runtime/debug"
"strings"
"github.com/goccy/go-json"
"golang.org/x/net/proxy"
)
func newClient(c []globals.ProxyConfig) *http.Client {
client := &http.Client{
Timeout: globals.HttpMaxTimeout,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
if len(c) == 0 {
return client
}
config := c[0]
if config.ProxyType == globals.NoneProxyType {
return client
}
if config.ProxyType == globals.HttpProxyType || config.ProxyType == globals.HttpsProxyType {
proxyUrl, err := url.Parse(config.Proxy)
if len(config.Username) > 0 || len(config.Password) > 0 {
proxyUrl.User = url.UserPassword(config.Username, config.Password)
}
if err != nil {
globals.Warn(fmt.Sprintf("failed to parse proxy url: %s", err))
return client
}
client.Transport = &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
} else if config.ProxyType == globals.Socks5ProxyType {
var auth *proxy.Auth
if len(config.Username) > 0 || len(config.Password) > 0 {
auth = &proxy.Auth{
User: config.Username,
Password: config.Password,
}
}
dialer, err := proxy.SOCKS5("tcp", config.Proxy, auth, proxy.Direct)
if err != nil {
globals.Warn(fmt.Sprintf("failed to create socks5 proxy: %s", err))
return client
}
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
}
client.Transport = &http.Transport{
DialContext: dialContext,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
globals.Debug(fmt.Sprintf("[proxy] configured proxy: %s", config.Proxy))
return client
}
func fillHeaders(req *http.Request, headers map[string]string) {
for key, value := range headers {
req.Header.Set(key, value)
}
}
func Http(uri string, method string, ptr interface{}, headers map[string]string, body io.Reader, config []globals.ProxyConfig) (err error) {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] %s %s\nheaders: \n%s\nbody: \n%s", method, uri, Marshal(headers), Marshal(body)))
}
req, err := http.NewRequest(method, uri, body)
if err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] failed to create request: %s", err))
}
return err
}
fillHeaders(req, headers)
client := newClient(config)
resp, err := client.Do(req)
if err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] failed to send request: %s", err))
}
return err
}
defer resp.Body.Close()
if err = json.NewDecoder(resp.Body).Decode(ptr); err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] failed to decode response: %s\nresponse: %s", err, resp.Body))
}
return err
}
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] response: %s", Marshal(ptr)))
}
return nil
}
func HttpRaw(uri string, method string, headers map[string]string, body io.Reader, config []globals.ProxyConfig) (data []byte, err error) {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] %s %s\nheaders: \n%s\nbody: \n%s", method, uri, Marshal(headers), Marshal(body)))
}
req, err := http.NewRequest(method, uri, body)
if err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] failed to create request: %s", err))
}
return nil, err
}
fillHeaders(req, headers)
client := newClient(config)
resp, err := client.Do(req)
if err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] failed to send request: %s", err))
}
return nil, err
}
defer resp.Body.Close()
if data, err = io.ReadAll(resp.Body); err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] failed to read response: %s", err))
}
return nil, err
}
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http] response: %s", string(data)))
}
return data, nil
}
func Get(uri string, headers map[string]string, config ...globals.ProxyConfig) (data interface{}, err error) {
err = Http(uri, http.MethodGet, &data, headers, nil, config)
return data, err
}
func GetRaw(uri string, headers map[string]string, config ...globals.ProxyConfig) (data string, err error) {
buffer, err := HttpRaw(uri, http.MethodGet, headers, nil, config)
if err != nil {
return "", err
}
return string(buffer), nil
}
func Post(uri string, headers map[string]string, body interface{}, config ...globals.ProxyConfig) (data interface{}, err error) {
err = Http(uri, http.MethodPost, &data, headers, ConvertBody(body), config)
return data, err
}
func ToString(data interface{}) string {
switch v := data.(type) {
case string:
return v
case int, int8, int16, int32, int64:
return fmt.Sprintf("%d", v)
case uint, uint8, uint16, uint32, uint64:
return fmt.Sprintf("%d", v)
case float32, float64:
return fmt.Sprintf("%f", v)
case bool:
return fmt.Sprintf("%t", v)
default:
data := Marshal(data)
if len(data) > 0 {
return data
}
return fmt.Sprintf("%v", data)
}
}
func PostRaw(uri string, headers map[string]string, body interface{}, config ...globals.ProxyConfig) (data string, err error) {
buffer, err := HttpRaw(uri, http.MethodPost, headers, ConvertBody(body), config)
if err != nil {
return "", err
}
return string(buffer), nil
}
func ConvertBody(body interface{}) (form io.Reader) {
if buffer, err := json.Marshal(body); err == nil {
form = bytes.NewBuffer(buffer)
}
return form
}
func EventSource(method string, uri string, headers map[string]string, body interface{}, callback func(string) error, config ...globals.ProxyConfig) error {
// panic recovery
defer func() {
if err := recover(); err != nil {
stack := debug.Stack()
globals.Warn(fmt.Sprintf("event source panic: %s (uri: %s, method: %s)\n%s", err, uri, method, stack))
}
}()
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http-stream] %s %s\nheaders: \n%s\nbody: \n%s", method, uri, Marshal(headers), Marshal(body)))
}
client := newClient(config)
req, err := http.NewRequest(method, uri, ConvertBody(body))
if err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http-stream] failed to create request: %s", err))
}
return err
}
fillHeaders(req, headers)
res, err := client.Do(req)
if err != nil {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http-stream] failed to send request: %s", err))
}
return err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http-stream] request failed with status: %s\nresponse: %s", res.Status, res.Body))
}
if content, err := io.ReadAll(res.Body); err == nil {
if form, err := Unmarshal[map[string]interface{}](content); err == nil {
data := MarshalWithIndent(form, 2)
return fmt.Errorf("request failed with status: %s\n```json\n%s\n```", res.Status, data)
}
}
return fmt.Errorf("request failed with status: %s", res.Status)
}
for {
buf := make([]byte, 20480)
n, err := res.Body.Read(buf)
if err == io.EOF {
return nil
} else if err != nil {
return err
}
data := string(buf[:n])
for _, item := range strings.Split(data, "\n") {
if globals.DebugMode {
globals.Debug(fmt.Sprintf("[http-stream] response: %s", item))
}
segment := strings.TrimSpace(item)
if len(segment) > 0 {
if err := callback(segment); err != nil {
return err
}
}
}
}
}