mirror of
https://github.com/eolinker/apinto
synced 2025-09-26 21:01:19 +08:00
Fix the issue of local model data synchronization errors
This commit is contained in:
@@ -8,6 +8,7 @@ var (
|
|||||||
AIModelTotalTokenLabel = "ai_model_total_token"
|
AIModelTotalTokenLabel = "ai_model_total_token"
|
||||||
AIModelModeLabel = "ai_model_mode"
|
AIModelModeLabel = "ai_model_mode"
|
||||||
AIModelLabel = "ai_model"
|
AIModelLabel = "ai_model"
|
||||||
|
AIKeyLabel = "ai_key"
|
||||||
AIProviderLabel = "ai_provider"
|
AIProviderLabel = "ai_provider"
|
||||||
AIProviderStatusesLabel = "ai_provider_statuses"
|
AIProviderStatusesLabel = "ai_provider_statuses"
|
||||||
AIModelStatusLabel = "ai_model_status"
|
AIModelStatusLabel = "ai_model_status"
|
||||||
@@ -69,6 +70,14 @@ func GetAIModel(ctx eocontext.EoContext) string {
|
|||||||
return valueString(ctx, AIModelLabel)
|
return valueString(ctx, AIModelLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetAIKey(ctx eocontext.EoContext, key string) {
|
||||||
|
ctx.WithValue(AIKeyLabel, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAIKey(ctx eocontext.EoContext) string {
|
||||||
|
return valueString(ctx, AIKeyLabel)
|
||||||
|
}
|
||||||
|
|
||||||
func SetAIProvider(ctx eocontext.EoContext, provider string) {
|
func SetAIProvider(ctx eocontext.EoContext, provider string) {
|
||||||
ctx.WithValue(AIProviderLabel, provider)
|
ctx.WithValue(AIProviderLabel, provider)
|
||||||
}
|
}
|
||||||
|
@@ -86,6 +86,7 @@ func (m *KeyPoolManager) Set(id string, resource IKeyResource) {
|
|||||||
sort.Slice(keys, func(i, j int) bool {
|
sort.Slice(keys, func(i, j int) bool {
|
||||||
return keys[i].Priority() < keys[j].Priority()
|
return keys[i].Priority() < keys[j].Priority()
|
||||||
})
|
})
|
||||||
|
|
||||||
m.keySorts.Set(id, keys)
|
m.keySorts.Set(id, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,12 +36,12 @@ func TransformData(inputJSON string, mappingRule MappingRule) (map[string]interf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("类型转换失败 %s -> %s: %v", key, v.Value, err)
|
return nil, fmt.Errorf("类型转换失败 %s -> %s: %v", key, v.Value, err)
|
||||||
}
|
}
|
||||||
//if value == "response_format" {
|
if value == "response_format" {
|
||||||
// resultMap[v.Value] = map[string]interface{}{
|
resultMap[v.Value] = map[string]interface{}{
|
||||||
// "type": convertedValue,
|
"type": convertedValue,
|
||||||
// }
|
}
|
||||||
// continue
|
continue
|
||||||
//}
|
}
|
||||||
resultMap[v.Value] = convertedValue
|
resultMap[v.Value] = convertedValue
|
||||||
} else {
|
} else {
|
||||||
resultMap[key] = value
|
resultMap[key] = value
|
||||||
|
@@ -6,7 +6,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Request 定义客户端统一输入请求格式
|
// Request 定义客户端统一输入请求格式
|
||||||
type Request openai.ChatCompletionRequest
|
type Request struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Messages []openai.ChatCompletionMessage `json:"messages"`
|
||||||
|
// MaxTokens The maximum number of tokens that can be generated in the chat completion.
|
||||||
|
// This value can be used to control costs for text generated via API.
|
||||||
|
// This value is now deprecated in favor of max_completion_tokens, and is not compatible with o1 series models.
|
||||||
|
// refs: https://platform.openai.com/docs/api-reference/chat/create#chat-create-max_tokens
|
||||||
|
MaxTokens int `json:"max_tokens,omitempty"`
|
||||||
|
// MaxCompletionTokens An upper bound for the number of tokens that can be generated for a completion,
|
||||||
|
// including visible output tokens and reasoning tokens https://platform.openai.com/docs/guides/reasoning
|
||||||
|
MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
|
||||||
|
Temperature float32 `json:"temperature,omitempty"`
|
||||||
|
TopP float32 `json:"top_p,omitempty"`
|
||||||
|
N int `json:"n,omitempty"`
|
||||||
|
Stream bool `json:"stream,omitempty"`
|
||||||
|
Stop []string `json:"stop,omitempty"`
|
||||||
|
PresencePenalty float32 `json:"presence_penalty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Response 定义客户端统一输出响应格式
|
// Response 定义客户端统一输出响应格式
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package ai_convert
|
package ai_convert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -49,6 +47,9 @@ func NewOpenAIConvert(apikey string, baseUrl string, timeout time.Duration, chec
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strings.TrimSuffix(u.Path, "/") == "" {
|
||||||
|
u.Path = "/v1"
|
||||||
|
}
|
||||||
c.path = fmt.Sprintf("%s%s", strings.TrimSuffix(u.Path, "/"), OpenAIChatCompletePath)
|
c.path = fmt.Sprintf("%s%s", strings.TrimSuffix(u.Path, "/"), OpenAIChatCompletePath)
|
||||||
} else {
|
} else {
|
||||||
c.path = fmt.Sprintf("/v1%s", OpenAIChatCompletePath)
|
c.path = fmt.Sprintf("/v1%s", OpenAIChatCompletePath)
|
||||||
@@ -66,7 +67,7 @@ func (o *OpenAIConvert) RequestConvert(ctx eoscContext.EoContext, extender map[s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var promptToken int
|
var promptToken int
|
||||||
chatRequest := eosc.NewBase[openai.ChatCompletionRequest](extender)
|
chatRequest := eosc.NewBase[Request](extender)
|
||||||
err = json.Unmarshal(body, chatRequest)
|
err = json.Unmarshal(body, chatRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unmarshal body error: %v, body: %s", err, string(body))
|
return fmt.Errorf("unmarshal body error: %v, body: %s", err, string(body))
|
||||||
@@ -89,6 +90,7 @@ func (o *OpenAIConvert) RequestConvert(ctx eoscContext.EoContext, extender map[s
|
|||||||
if o.balanceHandler != nil {
|
if o.balanceHandler != nil {
|
||||||
ctx.SetBalance(o.balanceHandler)
|
ctx.SetBalance(o.balanceHandler)
|
||||||
}
|
}
|
||||||
|
httpContext.AppendBodyFinishFunc(o.bodyFinish)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -133,36 +135,63 @@ func (o *OpenAIConvert) ResponseConvert(ctx eoscContext.EoContext) error {
|
|||||||
return ResponseConvert(ctx, o.checkErr, o.errorCallback)
|
return ResponseConvert(ctx, o.checkErr, o.errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *OpenAIConvert) bodyFinish(ctx http_service.IHttpContext) {
|
||||||
|
body := ctx.Response().GetBody()
|
||||||
|
defer func() {
|
||||||
|
SetAIProviderStatuses(ctx, AIProviderStatus{
|
||||||
|
Provider: GetAIProvider(ctx),
|
||||||
|
Model: GetAIModel(ctx),
|
||||||
|
Key: GetAIKey(ctx),
|
||||||
|
Status: GetAIStatus(ctx),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
if o.checkErr != nil && !o.checkErr(ctx, body) {
|
||||||
|
o.errorCallback(ctx, body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
encoding := ctx.Response().Headers().Get("content-encoding")
|
||||||
|
if encoding == "gzip" {
|
||||||
|
tmp, err := encoder.ToUTF8(encoding, body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("convert to utf-8 error: %v, body: %s", err, string(body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var resp openai.ChatCompletionResponse
|
||||||
|
err = json.Unmarshal(tmp, &resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unmarshal body error: %v, body: %s", err, string(tmp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SetAIModelInputToken(ctx, resp.Usage.PromptTokens)
|
||||||
|
SetAIModelOutputToken(ctx, resp.Usage.CompletionTokens)
|
||||||
|
SetAIModelTotalToken(ctx, resp.Usage.TotalTokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (o *OpenAIConvert) streamHandler(ctx http_service.IHttpContext, p []byte) ([]byte, error) {
|
func (o *OpenAIConvert) streamHandler(ctx http_service.IHttpContext, p []byte) ([]byte, error) {
|
||||||
|
encoding := ctx.Response().Headers().Get("content-encoding")
|
||||||
|
if encoding == "gzip" {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
// 对响应数据进行划分
|
// 对响应数据进行划分
|
||||||
inputToken := GetAIModelInputToken(ctx)
|
inputToken := GetAIModelInputToken(ctx)
|
||||||
outputToken := 0
|
outputToken := 0
|
||||||
totalToken := inputToken
|
totalToken := inputToken
|
||||||
scanner := bufio.NewScanner(bytes.NewReader(p))
|
|
||||||
// Check the content encoding and convert to UTF-8 if necessary.
|
line := string(p)
|
||||||
encoding := ctx.Response().Headers().Get("content-encoding")
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if encoding != "utf-8" && encoding != "" {
|
if encoding != "utf-8" && encoding != "" {
|
||||||
tmp, err := encoder.ToUTF8(encoding, []byte(line))
|
tmp, err := encoder.ToUTF8(encoding, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("convert to utf-8 error: %v, line: %s", err, line)
|
log.Errorf("convert to utf-8 error: %v, line: %s", err, line)
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
if ctx.Response().StatusCode() != 200 || (o.checkErr != nil && !o.checkErr(ctx, tmp)) {
|
|
||||||
if o.errorCallback != nil {
|
|
||||||
o.errorCallback(ctx, tmp)
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
line = string(tmp)
|
line = string(tmp)
|
||||||
}
|
|
||||||
line = strings.TrimPrefix(line, "data:")
|
line = strings.TrimPrefix(line, "data:")
|
||||||
if line == "" || strings.Trim(line, " ") == "[DONE]" {
|
if line == "" || strings.Trim(line, " ") == "[DONE]" {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
var resp openai.ChatCompletionResponse
|
var resp openai.ChatCompletionResponse
|
||||||
err := json.Unmarshal([]byte(line), &resp)
|
err = json.Unmarshal([]byte(line), &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
@@ -171,10 +200,6 @@ func (o *OpenAIConvert) streamHandler(ctx http_service.IHttpContext, p []byte) (
|
|||||||
totalToken += outputToken
|
totalToken += outputToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
log.Errorf("scan error: %v", err)
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
SetAIModelInputToken(ctx, inputToken)
|
SetAIModelInputToken(ctx, inputToken)
|
||||||
SetAIModelOutputToken(ctx, outputToken)
|
SetAIModelOutputToken(ctx, outputToken)
|
||||||
|
@@ -142,6 +142,7 @@ func (c *Convert) RequestConvert(ctx eocontext.EoContext, extender map[string]in
|
|||||||
}
|
}
|
||||||
httpContext.Proxy().Body().SetRaw("application/json", body)
|
httpContext.Proxy().Body().SetRaw("application/json", body)
|
||||||
httpContext.Response().AppendStreamFunc(c.streamHandler)
|
httpContext.Response().AppendStreamFunc(c.streamHandler)
|
||||||
|
ctx.SetLabel("response-content-type", "text/event-stream")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
_ "github.com/eolinker/apinto/drivers/ai-provider/hugging-face"
|
_ "github.com/eolinker/apinto/drivers/ai-provider/hugging-face"
|
||||||
_ "github.com/eolinker/apinto/drivers/ai-provider/hunyuan"
|
_ "github.com/eolinker/apinto/drivers/ai-provider/hunyuan"
|
||||||
_ "github.com/eolinker/apinto/drivers/ai-provider/lm-studio"
|
_ "github.com/eolinker/apinto/drivers/ai-provider/lm-studio"
|
||||||
|
_ "github.com/eolinker/apinto/drivers/ai-provider/local-model"
|
||||||
_ "github.com/eolinker/apinto/drivers/ai-provider/minimax"
|
_ "github.com/eolinker/apinto/drivers/ai-provider/minimax"
|
||||||
_ "github.com/eolinker/apinto/drivers/ai-provider/mistralai"
|
_ "github.com/eolinker/apinto/drivers/ai-provider/mistralai"
|
||||||
_ "github.com/eolinker/apinto/drivers/ai-provider/moonshot"
|
_ "github.com/eolinker/apinto/drivers/ai-provider/moonshot"
|
||||||
|
71
drivers/ai-provider/local-model/local.go
Normal file
71
drivers/ai-provider/local-model/local.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package local_model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
ai_convert "github.com/eolinker/apinto/ai-convert"
|
||||||
|
http_service "github.com/eolinker/eosc/eocontext/http-context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ai_convert.RegisterConverterCreateFunc("LocalModel", Create)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
BaseUrl string `json:"base"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConfig validates the provided configuration.
|
||||||
|
// It ensures the required fields are set and checks the validity of the Base URL if provided.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - v: An interface{} expected to be a pointer to a Config struct.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - *Config: The validated configuration cast to *Config.
|
||||||
|
// - error: An error if the validation fails, or nil if it succeeds.
|
||||||
|
func checkConfig(conf *Config) error {
|
||||||
|
if conf.BaseUrl == "" {
|
||||||
|
return fmt.Errorf("base url is required")
|
||||||
|
}
|
||||||
|
u, err := url.Parse(conf.BaseUrl)
|
||||||
|
if err != nil {
|
||||||
|
// Return an error if the Base URL cannot be parsed.
|
||||||
|
return fmt.Errorf("base url is invalid")
|
||||||
|
}
|
||||||
|
// Ensure the parsed URL contains both a scheme and a host.
|
||||||
|
if u.Scheme == "" || u.Host == "" {
|
||||||
|
return fmt.Errorf("base url is invalid")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(cfg string) (ai_convert.IConverter, error) {
|
||||||
|
var conf Config
|
||||||
|
err := json.Unmarshal([]byte(cfg), &conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = checkConfig(&conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ai_convert.NewOpenAIConvert("", conf.BaseUrl, 0, nil, errorCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorCallback(ctx http_service.IHttpContext, body []byte) {
|
||||||
|
|
||||||
|
switch ctx.Response().StatusCode() {
|
||||||
|
case 400:
|
||||||
|
// Handle the bad request error.
|
||||||
|
ai_convert.SetAIStatusInvalidRequest(ctx)
|
||||||
|
case 401:
|
||||||
|
// Handle the key error.
|
||||||
|
ai_convert.SetAIStatusInvalid(ctx)
|
||||||
|
default:
|
||||||
|
ai_convert.SetAIStatusInvalidRequest(ctx)
|
||||||
|
}
|
||||||
|
}
|
@@ -40,10 +40,10 @@ var (
|
|||||||
Value: "top_k",
|
Value: "top_k",
|
||||||
Type: "int",
|
Type: "int",
|
||||||
},
|
},
|
||||||
//{
|
{
|
||||||
// Value: "response_format",
|
Value: "response_format",
|
||||||
// Type: "string",
|
Type: "string",
|
||||||
//},
|
},
|
||||||
{
|
{
|
||||||
Value: "stream",
|
Value: "stream",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
|
@@ -70,7 +70,7 @@ func (o *Output) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Output) reset(conf *Config) error {
|
func (o *Output) reset(conf *Config) error {
|
||||||
if reflect.DeepEqual(conf, o.outputChan) {
|
if reflect.DeepEqual(conf, o.conf) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
//创建formatter
|
//创建formatter
|
||||||
@@ -136,7 +136,7 @@ func (o *Output) Output(entry eosc.IEntry) error {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return eosc.ErrorWorkerNotRunning
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Output) genRequest(data []byte) (*http.Request, error) {
|
func (o *Output) genRequest(data []byte) (*http.Request, error) {
|
||||||
|
@@ -76,7 +76,10 @@ func (e *executor) doConverter(ctx http_context.IHttpContext, next eocontext.ICh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ctx.Response().IsBodyStream() {
|
if ctx.Response().IsBodyStream() {
|
||||||
ctx.Response().SetHeader("Content-Type", "text/event-stream")
|
contentType := ctx.GetLabel("response-content-type")
|
||||||
|
if ctx.GetLabel("response-content-type") != "" {
|
||||||
|
ctx.Response().SetHeader("Content-Type", contentType)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := resource.ResponseConvert(ctx); err != nil {
|
if err := resource.ResponseConvert(ctx); err != nil {
|
||||||
@@ -104,6 +107,7 @@ func (e *executor) tryProvider(ctx http_context.IHttpContext, originProxy http_c
|
|||||||
}
|
}
|
||||||
ctx.SetProxy(originProxy)
|
ctx.SetProxy(originProxy)
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
|
ai_convert.SetAIKey(ctx, resource.ID())
|
||||||
err := e.doConverter(ctx, next, resource, provider, extender)
|
err := e.doConverter(ctx, next, resource, provider, extender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("try provider error: %v", err)
|
log.Errorf("try provider error: %v", err)
|
||||||
@@ -138,8 +142,21 @@ func (e *executor) DoHttpFilter(ctx http_context.IHttpContext, next eocontext.IC
|
|||||||
if ai_convert.GetAIModel(ctx) == "" {
|
if ai_convert.GetAIModel(ctx) == "" {
|
||||||
ai_convert.SetAIModel(ctx, e.model)
|
ai_convert.SetAIModel(ctx, e.model)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
// If the request is successful, set the AI provider and model in the response headers
|
||||||
|
ctx.Response().SetHeader("X-AI-Provider", ai_convert.GetAIProvider(ctx))
|
||||||
|
ctx.Response().SetHeader("X-AI-Model", ai_convert.GetAIModel(ctx))
|
||||||
|
}()
|
||||||
if err := e.processKeyPool(ctx, provider, cloneProxy, next); err != nil {
|
if err := e.processKeyPool(ctx, provider, cloneProxy, next); err != nil {
|
||||||
|
balances := ai_convert.Balances()
|
||||||
|
if len(balances) == 0 {
|
||||||
|
body := ctx.Response().GetBody()
|
||||||
|
if len(body) == 0 {
|
||||||
|
ctx.Response().SetBody([]byte(err.Error()))
|
||||||
|
ctx.Response().SetStatus(400, "Bad Request")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = e.doBalance(ctx, cloneProxy, next) // Fallback to balance logic
|
err = e.doBalance(ctx, cloneProxy, next) // Fallback to balance logic
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Response().SetBody([]byte(err.Error()))
|
ctx.Response().SetBody([]byte(err.Error()))
|
||||||
@@ -147,9 +164,6 @@ func (e *executor) DoHttpFilter(ctx http_context.IHttpContext, next eocontext.IC
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the request is successful, set the AI provider and model in the response headers
|
|
||||||
ctx.Response().SetHeader("X-AI-Provider", ai_convert.GetAIProvider(ctx))
|
|
||||||
ctx.Response().SetHeader("X-AI-Model", ai_convert.GetAIModel(ctx))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +190,7 @@ func (e *executor) processKeyPool(ctx http_context.IHttpContext, provider string
|
|||||||
if !r.Health() {
|
if !r.Health() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
ai_convert.SetAIKey(ctx, r.ID())
|
||||||
if err = r.RequestConvert(ctx, extender); err != nil {
|
if err = r.RequestConvert(ctx, extender); err != nil {
|
||||||
ai_convert.SetAIProviderStatuses(ctx, ai_convert.AIProviderStatus{
|
ai_convert.SetAIProviderStatuses(ctx, ai_convert.AIProviderStatus{
|
||||||
Provider: e.provider,
|
Provider: e.provider,
|
||||||
@@ -201,7 +215,10 @@ func (e *executor) processKeyPool(ctx http_context.IHttpContext, provider string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ctx.Response().IsBodyStream() {
|
if ctx.Response().IsBodyStream() {
|
||||||
ctx.Response().SetHeader("Content-Type", "text/event-stream")
|
contentType := ctx.GetLabel("response-content-type")
|
||||||
|
if ctx.GetLabel("response-content-type") != "" {
|
||||||
|
ctx.Response().SetHeader("Content-Type", contentType)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err = r.ResponseConvert(ctx); err != nil {
|
if err = r.ResponseConvert(ctx); err != nil {
|
||||||
|
@@ -15,6 +15,7 @@ func (f *Finisher) Finish(org eocontext.EoContext) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ctx.SetLabel("current_running", "false")
|
||||||
ctx.FastFinish()
|
ctx.FastFinish()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -2,6 +2,7 @@ package http_complete
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -34,7 +35,10 @@ func (h *HttpComplete) Complete(org eocontext.EoContext) error {
|
|||||||
}
|
}
|
||||||
//设置响应开始时间
|
//设置响应开始时间
|
||||||
proxyTime := time.Now()
|
proxyTime := time.Now()
|
||||||
|
balance := ctx.GetBalance()
|
||||||
|
if balance == nil {
|
||||||
|
return fmt.Errorf("balance not found")
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
//设置原始响应状态码
|
//设置原始响应状态码
|
||||||
ctx.Response().SetProxyStatus(ctx.Response().StatusCode(), "")
|
ctx.Response().SetProxyStatus(ctx.Response().StatusCode(), "")
|
||||||
@@ -42,8 +46,6 @@ func (h *HttpComplete) Complete(org eocontext.EoContext) error {
|
|||||||
ctx.SetLabel("handler", "proxy")
|
ctx.SetLabel("handler", "proxy")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
balance := ctx.GetBalance()
|
|
||||||
|
|
||||||
scheme := balance.Scheme()
|
scheme := balance.Scheme()
|
||||||
|
|
||||||
switch strings.ToLower(scheme) {
|
switch strings.ToLower(scheme) {
|
||||||
|
@@ -98,6 +98,7 @@ func (m *Manager) FastHandler(port int, ctx *fasthttp.RequestCtx) {
|
|||||||
(*globalFilters).Chain(httpContext, completeCaller)
|
(*globalFilters).Chain(httpContext, completeCaller)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
httpContext.SetLabel("current_running", "true")
|
||||||
log.Debug("match has:", port)
|
log.Debug("match has:", port)
|
||||||
r.Serve(httpContext)
|
r.Serve(httpContext)
|
||||||
}
|
}
|
||||||
|
@@ -1,35 +1,40 @@
|
|||||||
package encoder
|
package encoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"github.com/valyala/bytebufferpool"
|
||||||
"compress/gzip"
|
"github.com/valyala/fasthttp"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
//encoderManger.Set("gzip", &Gzip{})
|
encoderManger.Set("gzip", &Gzip{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Gzip struct {
|
type Gzip struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gzip) ToUTF8(data []byte) ([]byte, error) {
|
func (g *Gzip) ToUTF8(data []byte) ([]byte, error) {
|
||||||
|
var bb bytebufferpool.ByteBuffer
|
||||||
|
_, err := fasthttp.WriteGunzip(&bb, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bb.B, nil
|
||||||
// 创建一个gzip reader
|
// 创建一个gzip reader
|
||||||
reader, err := gzip.NewReader(bytes.NewReader(data))
|
//reader, err := gzip.NewReader(bytes.NewReader(data))
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
//}
|
||||||
defer reader.Close()
|
//defer reader.Close()
|
||||||
|
//
|
||||||
// 读取解压后的数据
|
//// 读取解压后的数据
|
||||||
var buf bytes.Buffer
|
//var buf bytes.Buffer
|
||||||
_, err = io.Copy(&buf, reader)
|
//_, err = io.Copy(&buf, reader)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 返回解压后的数据
|
//// 返回解压后的数据
|
||||||
// 注意:这里假设解压后的数据已经是UTF-8编码
|
//// 注意:这里假设解压后的数据已经是UTF-8编码
|
||||||
// 如果需要处理其他编码转UTF-8,需要额外的转换步骤
|
//// 如果需要处理其他编码转UTF-8,需要额外的转换步骤
|
||||||
return buf.Bytes(), nil
|
//return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
26
go.mod
26
go.mod
@@ -10,6 +10,7 @@ require (
|
|||||||
github.com/brianvoe/gofakeit/v6 v6.20.1
|
github.com/brianvoe/gofakeit/v6 v6.20.1
|
||||||
github.com/clbanning/mxj v1.8.4
|
github.com/clbanning/mxj v1.8.4
|
||||||
github.com/coocood/freecache v1.2.2
|
github.com/coocood/freecache v1.2.2
|
||||||
|
//github.com/dgrr/http2 v0.3.5
|
||||||
github.com/dubbogo/gost v1.13.1
|
github.com/dubbogo/gost v1.13.1
|
||||||
github.com/eolinker/eosc v0.20.3
|
github.com/eolinker/eosc v0.20.3
|
||||||
github.com/fasthttp/websocket v1.5.0
|
github.com/fasthttp/websocket v1.5.0
|
||||||
@@ -20,7 +21,6 @@ require (
|
|||||||
github.com/hashicorp/consul/api v1.9.1
|
github.com/hashicorp/consul/api v1.9.1
|
||||||
github.com/influxdata/influxdb-client-go/v2 v2.12.1
|
github.com/influxdata/influxdb-client-go/v2 v2.12.1
|
||||||
github.com/jhump/protoreflect v1.16.0
|
github.com/jhump/protoreflect v1.16.0
|
||||||
github.com/joho/godotenv v1.3.0
|
|
||||||
github.com/lestrrat-go/jwx v1.2.28
|
github.com/lestrrat-go/jwx v1.2.28
|
||||||
github.com/nacos-group/nacos-sdk-go/v2 v2.2.3
|
github.com/nacos-group/nacos-sdk-go/v2 v2.2.3
|
||||||
github.com/nsqio/go-nsq v1.1.0
|
github.com/nsqio/go-nsq v1.1.0
|
||||||
@@ -35,9 +35,9 @@ require (
|
|||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/traefik/yaegi v0.16.1
|
github.com/traefik/yaegi v0.16.1
|
||||||
github.com/urfave/cli/v2 v2.23.4
|
github.com/urfave/cli/v2 v2.23.4
|
||||||
github.com/valyala/fasthttp v1.47.0
|
github.com/valyala/fasthttp v1.59.0
|
||||||
golang.org/x/crypto v0.21.0
|
golang.org/x/crypto v0.33.0
|
||||||
golang.org/x/net v0.22.0
|
golang.org/x/net v0.35.0
|
||||||
golang.org/x/oauth2 v0.14.0
|
golang.org/x/oauth2 v0.14.0
|
||||||
google.golang.org/api v0.149.0
|
google.golang.org/api v0.149.0
|
||||||
google.golang.org/grpc v1.61.0
|
google.golang.org/grpc v1.61.0
|
||||||
@@ -105,7 +105,7 @@ require (
|
|||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.20.0 // indirect
|
go.opentelemetry.io/otel/metric v1.20.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||||
@@ -116,7 +116,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
dubbo.apache.org/dubbo-go/v3 v3.0.2-0.20220519062747-f6405fa79d5c
|
dubbo.apache.org/dubbo-go/v3 v3.0.2-0.20220519062747-f6405fa79d5c
|
||||||
github.com/andybalholm/brotli v1.0.5
|
github.com/andybalholm/brotli v1.1.1
|
||||||
github.com/apache/dubbo-go-hessian2 v1.11.6
|
github.com/apache/dubbo-go-hessian2 v1.11.6
|
||||||
github.com/armon/go-metrics v0.3.9 // indirect
|
github.com/armon/go-metrics v0.3.9 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
@@ -131,7 +131,7 @@ require (
|
|||||||
github.com/eapache/go-resiliency v1.2.0 // indirect
|
github.com/eapache/go-resiliency v1.2.0 // indirect
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
|
||||||
github.com/eapache/queue v1.1.0 // indirect
|
github.com/eapache/queue v1.1.0 // indirect
|
||||||
github.com/fatih/color v1.9.0 // indirect
|
github.com/fatih/color v1.13.0 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
@@ -155,13 +155,13 @@ require (
|
|||||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||||
github.com/klauspost/compress v1.16.3 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||||
@@ -173,7 +173,7 @@ require (
|
|||||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect
|
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
go.etcd.io/bbolt v1.3.9 // indirect
|
go.etcd.io/bbolt v1.3.9 // indirect
|
||||||
@@ -190,8 +190,8 @@ require (
|
|||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
go.uber.org/zap v1.23.0
|
go.uber.org/zap v1.23.0
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.22.0
|
||||||
golang.org/x/time v0.1.0 // indirect
|
golang.org/x/time v0.1.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||||
|
@@ -12,7 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/eolinker/eosc/eocontext"
|
"github.com/eolinker/eosc/eocontext"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,6 +147,7 @@ func (c *Client) getHostClient(addr string, rewriteHost string) (*fasthttp.HostC
|
|||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
//http2.ConfigureClient(hc, http2.ClientOpts{})
|
||||||
m[key] = hc
|
m[key] = hc
|
||||||
if len(m) == 1 {
|
if len(m) == 1 {
|
||||||
go c.startCleaner(m)
|
go c.startCleaner(m)
|
||||||
|
@@ -49,8 +49,11 @@ type HttpContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *HttpContext) BodyFinish() {
|
func (ctx *HttpContext) BodyFinish() {
|
||||||
for _, finishFunc := range ctx.bodyFinishes {
|
bodyFinishes := ctx.bodyFinishes
|
||||||
finishFunc(ctx)
|
size := len(bodyFinishes)
|
||||||
|
// ##倒序执行
|
||||||
|
for i := size - 1; i >= 0; i-- {
|
||||||
|
bodyFinishes[i](ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +222,12 @@ func (ctx *HttpContext) SendTo(scheme string, node eoscContext.INode, timeout ti
|
|||||||
// 流式传输
|
// 流式传输
|
||||||
ctx.response.Response.SetStatusCode(response.StatusCode())
|
ctx.response.Response.SetStatusCode(response.StatusCode())
|
||||||
ctx.SetLabel("stream_running", "true")
|
ctx.SetLabel("stream_running", "true")
|
||||||
|
|
||||||
ctx.response.Response.SetBodyStreamWriter(func(w *bufio.Writer) {
|
ctx.response.Response.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||||
defer func() {
|
defer func() {
|
||||||
ctx.SetLabel("stream_running", "false")
|
ctx.SetLabel("stream_running", "false")
|
||||||
ctx.FastFinish()
|
ctx.FastFinish()
|
||||||
|
fasthttp.ReleaseResponse(response)
|
||||||
}()
|
}()
|
||||||
reader := response.BodyStream()
|
reader := response.BodyStream()
|
||||||
buffer := make([]byte, 4096) // 4KB 缓冲区
|
buffer := make([]byte, 4096) // 4KB 缓冲区
|
||||||
@@ -262,11 +267,11 @@ func (ctx *HttpContext) SendTo(scheme string, node eoscContext.INode, timeout ti
|
|||||||
ctx.proxyRequests = append(ctx.proxyRequests, agent)
|
ctx.proxyRequests = append(ctx.proxyRequests, agent)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
response.CopyTo(ctx.response.Response)
|
response.CopyTo(ctx.response.Response)
|
||||||
agent.responseBody.Write(ctx.response.Response.Body())
|
agent.responseBody.Write(ctx.response.Response.Body())
|
||||||
agent.setResponseLength(ctx.response.Response.Header.ContentLength())
|
agent.setResponseLength(ctx.response.Response.Header.ContentLength())
|
||||||
ctx.proxyRequests = append(ctx.proxyRequests, agent)
|
ctx.proxyRequests = append(ctx.proxyRequests, agent)
|
||||||
|
fasthttp.ReleaseResponse(response)
|
||||||
return ctx.response.responseError
|
return ctx.response.responseError
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -383,15 +388,14 @@ func (ctx *HttpContext) RequestId() string {
|
|||||||
|
|
||||||
// FastFinish finish
|
// FastFinish finish
|
||||||
func (ctx *HttpContext) FastFinish() {
|
func (ctx *HttpContext) FastFinish() {
|
||||||
streamRunning := ctx.GetLabel("stream_running")
|
if ctx.GetLabel("stream_running") == "true" || ctx.GetLabel("current_running") == "true" {
|
||||||
if streamRunning == "true" {
|
|
||||||
// 暂时不释放
|
// 暂时不释放
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.response.responseError != nil {
|
if ctx.response.responseError != nil {
|
||||||
ctx.fastHttpRequestCtx.SetStatusCode(504)
|
ctx.fastHttpRequestCtx.SetStatusCode(504)
|
||||||
ctx.fastHttpRequestCtx.SetBodyString(ctx.response.responseError.Error())
|
ctx.fastHttpRequestCtx.SetBodyString(ctx.response.responseError.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.port = 0
|
ctx.port = 0
|
||||||
|
@@ -77,7 +77,7 @@ func (r *Response) Finish() error {
|
|||||||
r.Response = nil
|
r.Response = nil
|
||||||
r.responseError = nil
|
r.responseError = nil
|
||||||
r.proxyStatusCode = 0
|
r.proxyStatusCode = 0
|
||||||
r.streamBody = nil
|
r.streamBody.Reset()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (r *Response) reset(resp *fasthttp.Response) {
|
func (r *Response) reset(resp *fasthttp.Response) {
|
||||||
@@ -85,7 +85,10 @@ func (r *Response) reset(resp *fasthttp.Response) {
|
|||||||
r.ResponseHeader.reset(&resp.Header)
|
r.ResponseHeader.reset(&resp.Header)
|
||||||
r.responseError = nil
|
r.responseError = nil
|
||||||
r.proxyStatusCode = 0
|
r.proxyStatusCode = 0
|
||||||
|
if r.streamBody == nil {
|
||||||
r.streamBody = &bytes.Buffer{}
|
r.streamBody = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
r.streamBody.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Response) BodyLen() int {
|
func (r *Response) BodyLen() int {
|
||||||
@@ -93,15 +96,15 @@ func (r *Response) BodyLen() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Response) GetBody() []byte {
|
func (r *Response) GetBody() []byte {
|
||||||
|
if r.IsBodyStream() {
|
||||||
|
return r.streamBody.Bytes()
|
||||||
|
}
|
||||||
if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") {
|
if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") {
|
||||||
body, _ := r.BodyGunzip()
|
body, _ := r.BodyGunzip()
|
||||||
r.DelHeader("Content-Encoding")
|
r.DelHeader("Content-Encoding")
|
||||||
r.SetHeader("Content-Length", strconv.Itoa(len(body)))
|
r.SetHeader("Content-Length", strconv.Itoa(len(body)))
|
||||||
r.Response.SetBody(body)
|
r.Response.SetBody(body)
|
||||||
}
|
}
|
||||||
if r.IsBodyStream() {
|
|
||||||
return r.streamBody.Bytes()
|
|
||||||
}
|
|
||||||
return r.Response.Body()
|
return r.Response.Body()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +118,7 @@ func (r *Response) SetBody(bytes []byte) {
|
|||||||
// 不处理
|
// 不处理
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") {
|
if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") {
|
||||||
r.DelHeader("Content-Encoding")
|
r.DelHeader("Content-Encoding")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user