mirror of
https://github.com/alibaba/higress.git
synced 2026-05-26 21:57:30 +08:00
refactor: migrate MCP SDK to main repo (#3516)
This commit is contained in:
209
plugins/wasm-go/pkg/mcp/utils/json_rpc.go
Normal file
209
plugins/wasm-go/pkg/mcp/utils/json_rpc.go
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/higress-group/wasm-go/pkg/iface"
|
||||
"github.com/higress-group/wasm-go/pkg/log"
|
||||
pb "github.com/higress-group/wasm-go/pkg/protos"
|
||||
"github.com/higress-group/wasm-go/pkg/wrapper"
|
||||
)
|
||||
|
||||
const (
|
||||
CtxJsonRpcID = "jsonRpcID"
|
||||
CtxNeedPause = "needPause" // Context key to signal if the handler needs to pause
|
||||
JError = "error"
|
||||
JCode = "code"
|
||||
JMessage = "message"
|
||||
JResult = "result"
|
||||
|
||||
ErrParseError = -32700
|
||||
ErrInvalidRequest = -32600
|
||||
ErrMethodNotFound = -32601
|
||||
ErrInvalidParams = -32602
|
||||
ErrInternalError = -32603
|
||||
)
|
||||
|
||||
// JsonRpcID represents a JSON-RPC ID which can be either a string or a number
|
||||
type JsonRpcID struct {
|
||||
StringValue string
|
||||
IntValue int64
|
||||
IsString bool
|
||||
}
|
||||
|
||||
// NewJsonRpcIDFromGjson creates a JsonRpcID from a gjson.Result
|
||||
func NewJsonRpcIDFromGjson(result gjson.Result) JsonRpcID {
|
||||
if result.Type == gjson.String {
|
||||
return JsonRpcID{
|
||||
StringValue: result.String(),
|
||||
IsString: true,
|
||||
}
|
||||
}
|
||||
return JsonRpcID{
|
||||
IntValue: result.Int(),
|
||||
IsString: false,
|
||||
}
|
||||
}
|
||||
|
||||
type JsonRpcRequestHandler func(context wrapper.HttpContext, id JsonRpcID, method string, params gjson.Result, rawBody []byte) types.Action
|
||||
|
||||
type JsonRpcResponseHandler func(context wrapper.HttpContext, id JsonRpcID, result gjson.Result, error gjson.Result, rawBody []byte) types.Action
|
||||
|
||||
type JsonRpcMethodHandler func(context wrapper.HttpContext, id JsonRpcID, params gjson.Result) error
|
||||
|
||||
type MethodHandlers map[string]JsonRpcMethodHandler
|
||||
|
||||
func makeHttpResponse(ctx wrapper.HttpContext, code uint32, debugInfo string, headers [][2]string, body []byte) {
|
||||
phase := ctx.GetExecutionPhase()
|
||||
if phase < iface.EncodeHeader {
|
||||
proxywasm.SendHttpResponseWithDetail(code, debugInfo, headers, body, -1)
|
||||
return
|
||||
}
|
||||
if debugInfo != "" {
|
||||
log.Infof("response detail info:%s", debugInfo)
|
||||
}
|
||||
proxywasm.RemoveHttpResponseHeader("content-length")
|
||||
proxywasm.ReplaceHttpResponseHeader(":status", strconv.Itoa(int(code)))
|
||||
for _, kv := range headers {
|
||||
proxywasm.ReplaceHttpResponseHeader(kv[0], kv[1])
|
||||
}
|
||||
if phase == iface.EncodeData {
|
||||
proxywasm.ReplaceHttpResponseBody(body)
|
||||
return
|
||||
}
|
||||
// EncodeHeader phase
|
||||
args := &pb.InjectEncodedDataToFilterChainArguments{
|
||||
Body: string(body),
|
||||
Endstream: true,
|
||||
}
|
||||
argsStr, _ := proto.Marshal(args)
|
||||
_, err := proxywasm.CallForeignFunction("inject_encoded_data_to_filter_chain_on_header", argsStr)
|
||||
if err != nil {
|
||||
log.Warnf("call inject_encoded_data_to_filter_chain_on_header failed, err:%v, fallback to send directly", err)
|
||||
proxywasm.SendHttpResponseWithDetail(code, debugInfo, headers, body, -1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func sendJsonRpcResponse(ctx wrapper.HttpContext, id JsonRpcID, extras map[string]any, debugInfo string) {
|
||||
body := []byte(`{"jsonrpc": "2.0"}`)
|
||||
if id.IsString {
|
||||
body, _ = sjson.SetBytes(body, "id", id.StringValue)
|
||||
} else {
|
||||
body, _ = sjson.SetBytes(body, "id", id.IntValue)
|
||||
}
|
||||
for key, value := range extras {
|
||||
body, _ = sjson.SetBytes(body, key, value)
|
||||
}
|
||||
makeHttpResponse(ctx, 200, debugInfo, [][2]string{{"Content-Type", "application/json; charset=utf-8"}}, body)
|
||||
}
|
||||
|
||||
func OnJsonRpcResponseSuccess(ctx wrapper.HttpContext, result map[string]any, debugInfo ...string) {
|
||||
var (
|
||||
id JsonRpcID
|
||||
ok bool
|
||||
)
|
||||
idRaw := ctx.GetContext(CtxJsonRpcID)
|
||||
if id, ok = idRaw.(JsonRpcID); !ok {
|
||||
makeHttpResponse(ctx, 500, "not_found_json_rpc_id", nil, []byte("not found json rpc id"))
|
||||
return
|
||||
}
|
||||
responseDebugInfo := "json_rpc_success"
|
||||
if len(debugInfo) > 0 {
|
||||
responseDebugInfo = debugInfo[0]
|
||||
}
|
||||
sendJsonRpcResponse(ctx, id, map[string]any{JResult: result}, responseDebugInfo)
|
||||
}
|
||||
|
||||
func OnJsonRpcResponseError(ctx wrapper.HttpContext, err error, errorCode int, debugInfo ...string) {
|
||||
var (
|
||||
id JsonRpcID
|
||||
ok bool
|
||||
)
|
||||
idRaw := ctx.GetContext(CtxJsonRpcID)
|
||||
if id, ok = idRaw.(JsonRpcID); !ok {
|
||||
makeHttpResponse(ctx, 500, "not_found_json_rpc_id", nil, []byte("not found json rpc id"))
|
||||
return
|
||||
}
|
||||
responseDebugInfo := fmt.Sprintf("json_rpc_error(%s)", err)
|
||||
if len(debugInfo) > 0 {
|
||||
responseDebugInfo = debugInfo[0]
|
||||
}
|
||||
sendJsonRpcResponse(ctx, id, map[string]any{JError: map[string]any{
|
||||
JMessage: err.Error(),
|
||||
JCode: errorCode,
|
||||
}}, responseDebugInfo)
|
||||
}
|
||||
|
||||
func HandleJsonRpcMethod(ctx wrapper.HttpContext, body []byte, handles MethodHandlers) types.Action {
|
||||
idResult := gjson.GetBytes(body, "id")
|
||||
id := NewJsonRpcIDFromGjson(idResult)
|
||||
ctx.SetContext(CtxJsonRpcID, id)
|
||||
method := gjson.GetBytes(body, "method").String()
|
||||
params := gjson.GetBytes(body, "params")
|
||||
if method != "" {
|
||||
if handle, ok := handles[method]; ok {
|
||||
log.Debugf("json rpc call method[%s] with params[%s]", method, params.Raw)
|
||||
|
||||
// Clear pause flag before calling handler
|
||||
ctx.SetContext(CtxNeedPause, false)
|
||||
|
||||
err := handle(ctx, id, params)
|
||||
if err != nil {
|
||||
OnJsonRpcResponseError(ctx, err, ErrInvalidRequest)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// Check if the handler set the pause flag
|
||||
if needPause := ctx.GetContext(CtxNeedPause); needPause != nil && needPause.(bool) {
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
OnJsonRpcResponseError(ctx, fmt.Errorf("method not found:%s", method), ErrMethodNotFound)
|
||||
} else {
|
||||
proxywasm.SendHttpResponseWithDetail(202, "json_rpc_ack", nil, nil, -1)
|
||||
}
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func HandleJsonRpcRequest(ctx wrapper.HttpContext, body []byte, handle JsonRpcRequestHandler) types.Action {
|
||||
idResult := gjson.GetBytes(body, "id")
|
||||
id := NewJsonRpcIDFromGjson(idResult)
|
||||
ctx.SetContext(CtxJsonRpcID, id)
|
||||
method := gjson.GetBytes(body, "method").String()
|
||||
params := gjson.GetBytes(body, "params")
|
||||
log.Debugf("json rpc call method[%s] with params[%s]", method, params.Raw)
|
||||
return handle(ctx, id, method, params, body)
|
||||
}
|
||||
|
||||
func HandleJsonRpcResponse(ctx wrapper.HttpContext, body []byte, handle JsonRpcResponseHandler) types.Action {
|
||||
idResult := gjson.GetBytes(body, "id")
|
||||
id := NewJsonRpcIDFromGjson(idResult)
|
||||
error := gjson.GetBytes(body, "error")
|
||||
result := gjson.GetBytes(body, "result")
|
||||
log.Debugf("json rpc response error[%s] result[%s]", error.Raw, result.Raw)
|
||||
return handle(ctx, id, result, error, body)
|
||||
}
|
||||
Reference in New Issue
Block a user