├── .gitignore ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── cmd.sh ├── deploy ├── configmap.yaml ├── deployment.yaml ├── echo_deployment.yaml ├── echo_service.yaml ├── service.yaml ├── webhook-create-signed-cert.sh ├── webhook-patch-ca-bundle.sh ├── webhook_deployment.yaml ├── webhook_mutating.yaml ├── webhook_rbac.yaml └── webhook_service.yaml ├── example ├── docker │ ├── Dockerfile │ ├── build.sh │ └── server ├── env_sink │ ├── example.sh │ ├── hello.proto │ ├── rust-grpc-proxy │ └── server ├── hello │ ├── config.toml │ ├── hello.proto │ └── helloworld ├── metadata │ ├── Dockerfile │ ├── example.sh │ ├── hello.proto │ ├── rust-grpc-proxy │ └── server └── restful │ ├── example.sh │ ├── hello.proto │ ├── rust-grpc-proxy │ └── server ├── src ├── app │ ├── dyn_map_impl.rs │ ├── md_filters_impl.rs │ ├── mod.rs │ ├── proxy_sink.rs │ ├── query_analysis_impl.rs │ └── server.rs ├── cmd │ ├── exit.rs │ ├── mod.rs │ ├── run.rs │ ├── show.rs │ └── test.rs ├── config │ ├── config.rs │ ├── config.toml │ └── mod.rs ├── infra │ ├── dynamic │ │ ├── anno.rs │ │ ├── client.rs │ │ ├── compressed_dictionary_tree.rs │ │ ├── mod.rs │ │ ├── simple_index.rs │ │ └── transition.rs │ ├── mod.rs │ ├── profiler │ │ ├── mod.rs │ │ ├── reflect.rs │ │ ├── reflect_file_desc.rs │ │ └── services_desc_assembler.rs │ └── server │ │ ├── filter_middle_log.rs │ │ ├── filter_middle_req_id.rs │ │ ├── filter_middle_time.rs │ │ ├── http_server.rs │ │ ├── mod.rs │ │ └── shutdown_control.rs ├── main.rs └── util │ ├── mod.rs │ └── to_vec.rs └── webhook ├── Dockerfile ├── certs ├── cert.pem └── key.pem ├── go.mod ├── go.sum ├── main.go └── sidecar_rust_grpc_proxy.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | ./mg.sh 13 | 14 | ./.idea 15 | 16 | ./webhook/.idea -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-grpc-proxy" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["wangdong <1443965173@qq.com>"] 6 | license = "MIT" 7 | description = "The grpc dynamic proxy is based on rust" 8 | 9 | [dependencies] 10 | wd_run = "0.2.2" 11 | wd_log = "0.2.0" 12 | wd_sonyflake = "0.0.1" 13 | tokio = {version = "1.23",features = ["full"]} 14 | tokio-stream = "0.1" 15 | hyper = {version = "0.14.23",features = ["full"]} 16 | async-trait = "0.1.61" 17 | anyhow = "1.0.68" 18 | protobuf = "3.2.0" 19 | protobuf-json-mapping = "3" 20 | tonic = "0.9.2" 21 | prost = "0.11.3" 22 | #prost-types = "0.11.3" 23 | serde = "1.0" 24 | serde_json = "1.0.85" -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | EXPOSE 6789 3 | WORKDIR /root/ 4 | COPY target/x86_64-unknown-linux-musl/release/rust-grpc-proxy . 5 | 6 | CMD ["./rust-grpc-proxy", "run"] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 woshihaoren4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grpc-proxy 2 | > The grpc dynamic proxy is based on rust. 3 | > Provides non-invasive protocol conversion services. 4 | 5 | 6 | ## functional support 7 | 8 | - [x] http to grpc 9 | - [x] grpc annotations restful 10 | - [x] custom metadata 11 | - [x] sidecar scheme 12 | - [ ] dynamic reflect 13 | - [ ] development(for event) 14 | 15 | ## quick start 16 | ```shell 17 | cd example/restful 18 | 19 | ./example.sh 20 | ``` 21 | 22 | ## docs 23 | https://blog.csdn.net/qq_25490573/category_12237327.html 24 | 25 | 26 | ## docker 27 | 28 | [https://hub.docker.com/repository/docker/wdshihaoren/rust-grpc-proxy](https://hub.docker.com/repository/docker/wdshihaoren/rust-grpc-proxy) 29 | 30 | ## author 31 | if you need support or have questions, please contact`1443965173@qq.com` 32 | 33 | ## other 34 | -------------------------------------------------------------------------------- /cmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | case $1 in 4 | run) 5 | cargo run -- run 6 | ;; 7 | run_env) 8 | RUST_GRPC_PROXY_ADDR="127.0.0.1:1234" cargo run -- run 9 | ;; 10 | show) 11 | cargo run -- show 12 | ;; 13 | restful) 14 | cargo run -- run -c example/restful/config.toml 15 | ;; 16 | md) 17 | cargo run -- run -c example/metadata/config.toml 18 | ;; 19 | docker) 20 | 21 | if [ ! -e ".cargo/config.toml" ] ; then 22 | mkdir .cargo;touch .cargo/config.toml 23 | fi 24 | 25 | cat>".cargo/config.toml" <> ${tmpdir}/csr.conf 63 | [req] 64 | req_extensions = v3_req 65 | distinguished_name = req_distinguished_name 66 | [req_distinguished_name] 67 | [ v3_req ] 68 | basicConstraints = CA:FALSE 69 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 70 | extendedKeyUsage = serverAuth 71 | subjectAltName = @alt_names 72 | [alt_names] 73 | DNS.1 = ${service} 74 | DNS.2 = ${service}.${namespace} 75 | DNS.3 = ${service}.${namespace}.svc 76 | EOF 77 | 78 | openssl genrsa -out ${tmpdir}/server-key.pem 2048 79 | openssl req -new -key ${tmpdir}/server-key.pem -subj "/CN=system:node:${service}.${namespace}.svc/O=system:nodes" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf 80 | 81 | # clean-up any previously created CSR for our service. Ignore errors if not present. 82 | kubectl delete csr ${csrName} 2>/dev/null || true 83 | 84 | # create server cert/key CSR and send to k8s API 85 | cat <&2 122 | exit 1 123 | fi 124 | echo ${serverCert} | openssl base64 -d -A -out ${tmpdir}/server-cert.pem 125 | 126 | 127 | # create the secret with CA cert and server cert/key 128 | kubectl create secret generic ${secret} \ 129 | --from-file=key.pem=${tmpdir}/server-key.pem \ 130 | --from-file=cert.pem=${tmpdir}/server-cert.pem \ 131 | --dry-run -o yaml | 132 | kubectl -n ${namespace} apply -f - 133 | -------------------------------------------------------------------------------- /deploy/webhook-patch-ca-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT=$(cd $(dirname $0)/../../; pwd) 4 | 5 | set -o errexit 6 | set -o nounset 7 | set -o pipefail 8 | 9 | 10 | #export CA_BUNDLE=$(kubectl config view --raw --flatten -o json | jq -r '.clusters[] | select(.name == "'$(kubectl config current-context)'") | .cluster."certificate-authority-data"') 11 | export CA_BUNDLE=$(kubectl config view --raw --flatten -o json | jq -r '.clusters[1] | .cluster."certificate-authority-data"') 12 | 13 | if command -v envsubst >/dev/null 2>&1; then 14 | envsubst 15 | else 16 | sed -e "s|\${CA_BUNDLE}|${CA_BUNDLE}|g" 17 | fi 18 | -------------------------------------------------------------------------------- /deploy/webhook_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-proxy-webhook-deployment 5 | labels: 6 | app: grpc-proxy-webhook 7 | namespace: qa 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: grpc-proxy-webhook 13 | template: 14 | metadata: 15 | labels: 16 | app: grpc-proxy-webhook 17 | spec: 18 | serviceAccount: grpc-proxy-webhook-sa 19 | containers: 20 | - name: grpc-proxy-webhook 21 | image: registry.cn-hangzhou.aliyuncs.com/wshr/wd:sidecar-v4 22 | imagePullPolicy: Always 23 | args: 24 | - -tlsCertFile=/etc/webhook/certs/cert.pem 25 | - -tlsKeyFile=/etc/webhook/certs/key.pem 26 | volumeMounts: 27 | - name: webhook-certs 28 | mountPath: /etc/webhook/certs 29 | readOnly: true 30 | volumes: 31 | - name: webhook-certs 32 | secret: 33 | secretName: grpc-proxy-webhook-certs 34 | -------------------------------------------------------------------------------- /deploy/webhook_mutating.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: grpc-proxy-webhook-cfg 5 | labels: 6 | app: grpc-proxy-webhook 7 | webhooks: 8 | - name: mutating.grpc-proxy-webhook.com 9 | admissionReviewVersions: [ "v1", "v1beta1" ] 10 | sideEffects: None 11 | clientConfig: 12 | service: 13 | name: grpc-proxy-webhook-svc 14 | namespace: qa 15 | path: "/sidecar/rust-grpc-proxy" 16 | caBundle: ${CA_BUNDLE} 17 | rules: 18 | - operations: [ "CREATE","UPDATE" ] 19 | apiGroups: [""] 20 | apiVersions: ["v1"] 21 | resources: ["pods"] 22 | # namespaceSelector: 23 | # matchLabels: 24 | # grpc-proxy-webhook: enabled 25 | 26 | -------------------------------------------------------------------------------- /deploy/webhook_rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: grpc-proxy-webhook-sa 5 | labels: 6 | app: grpc-proxy-webhook 7 | namespace: qa 8 | 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | name: grpc-proxy-webhook-cr 14 | labels: 15 | app: grpc-proxy-webhook 16 | rules: 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - pods 21 | - events 22 | verbs: 23 | - "*" 24 | - apiGroups: 25 | - apps 26 | resources: 27 | - deployments 28 | - daemonsets 29 | - replicasets 30 | - statefulsets 31 | verbs: 32 | - "*" 33 | - apiGroups: 34 | - autoscaling 35 | resources: 36 | - '*' 37 | verbs: 38 | - '*' 39 | 40 | --- 41 | kind: ClusterRoleBinding 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | metadata: 44 | name: grpc-proxy-webhook-crb 45 | labels: 46 | app: grpc-proxy-webhook 47 | subjects: 48 | - kind: ServiceAccount 49 | name: grpc-proxy-webhook-sa 50 | namespace: qa 51 | roleRef: 52 | apiGroup: rbac.authorization.k8s.io 53 | kind: ClusterRole 54 | name: grpc-proxy-webhook-cr 55 | -------------------------------------------------------------------------------- /deploy/webhook_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-proxy-webhook-svc 5 | labels: 6 | app: grpc-proxy-webhook 7 | namespace: qa 8 | spec: 9 | ports: 10 | - port: 443 11 | targetPort: 443 12 | selector: 13 | app: grpc-proxy-webhook 14 | -------------------------------------------------------------------------------- /example/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | EXPOSE 1234 3 | WORKDIR /root/ 4 | COPY server . 5 | 6 | CMD ["./server", "server -n test-server -a :1234"] -------------------------------------------------------------------------------- /example/docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TAG="registry.cn-hangzhou.aliyuncs.com/wshr/wd:latest" 4 | docker build -f ./Dockerfile -t "$TAG" . 5 | docker push "$TAG" -------------------------------------------------------------------------------- /example/docker/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/docker/server -------------------------------------------------------------------------------- /example/env_sink/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVICE_ECHO="SERVICE_ECHO" 4 | GRPC_PROXY="GRPC_PROXY" 5 | CONFIG_PATH="config.toml" 6 | 7 | function test_one() { 8 | result=$(curl -s -l --location --request GET 'http://127.0.0.1:6789/api/v1/echo/hello/get?query=666' | jq -r '."response"') 9 | assert_eq "$result" 'GET [SERVICE_ECHO]---> request=hello query=666' "test_one" 10 | } 11 | function test_three() { 12 | result=$(curl -s -l --location --request POST 'http://127.0.0.1:6789/api/v1/echo/post' \ 13 | --header 'Content-Type: application/json' \ 14 | --data-raw '{ 15 | "request": "hello", 16 | "query": 123 17 | }' | jq -r '.response') 18 | assert_eq "$result" 'POST [SERVICE_ECHO]---> request=hello query=123' "test_three" 19 | } 20 | 21 | function assert_eq() { 22 | if [ "$1" == "$2" ] 23 | then 24 | echo "用例[$3] success" 25 | else 26 | echo -e "===> 用例[$3] assert failed <=== " 27 | echo -e " expectation -->$2 " 28 | echo -e " actual -->$1 " 29 | fi 30 | } 31 | 32 | 33 | function reset_config_file() { 34 | cat>$CONFIG_PATH < $CONFIG_PATH" && cat $CONFIG_PATH 66 | exit 67 | ;; 68 | server) 69 | start_server && echo 'src server run success' 70 | exit 71 | esac 72 | 73 | 74 | 75 | start_server 76 | 77 | 78 | if [ ! -e $CONFIG_PATH ] 79 | then 80 | reset_config_file && echo "配置文件:$CONFIG_PATH 初始化成功" 81 | fi 82 | 83 | if [ $(screen -ls | grep -c $GRPC_PROXY) -le 0 ] 84 | then 85 | echo -n "等待测试服务启动" 86 | for (( i=0;i<10;i=i+1 )) 87 | do 88 | echo -n "。" 89 | sleep 1 90 | done 91 | echo "" 92 | 93 | screen -dmS $GRPC_PROXY /bin/bash -c "./rust-grpc-proxy run -c $CONFIG_PATH" && echo "代理服务[$GRPC_PROXY]已启动" 94 | 95 | echo -n "等待代理服务启动" 96 | for (( i=0;i<10;i=i+1 )) 97 | do 98 | echo -n "。" 99 | sleep 1 100 | done 101 | echo "" 102 | else 103 | echo "$GRPC_PROXY 服务已经启动" 104 | fi 105 | 106 | if [ ! -x "$(command -v jq)" ];then 107 | echo 'jq not found , please install' 108 | echo ' MAC : brew install jq' 109 | echo ' Ubuntu : sudo apt-get install jq' 110 | exit 1 111 | fi 112 | 113 | test_one 114 | 115 | test_three -------------------------------------------------------------------------------- /example/env_sink/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proto; 4 | option go_package = "./proto"; 5 | //import "google/protobuf/struct.proto"; 6 | 7 | import "google/api/annotations.proto"; 8 | 9 | 10 | 11 | // Echo Service 12 | service EchoService { 13 | rpc EchoGet(EchoGetRequest) returns (EchoGetResponse){ 14 | option (google.api.http) = { 15 | get: "/api/v1/echo/{request}/get" 16 | }; 17 | }; 18 | rpc EchoPost(EchoGetRequest) returns (EchoGetResponse){ 19 | option (google.api.http) = { 20 | post: "/api/v1/echo/post" 21 | body: "*" 22 | }; 23 | }; 24 | } 25 | 26 | // Echo Service 27 | service GreetService { 28 | rpc GreetGet(GreetGetRequest) returns (GreetGetResponse){ 29 | option (google.api.http) = { 30 | get: "/api/v1/greet/{request}" 31 | }; 32 | }; 33 | } 34 | 35 | message EchoGetRequest { 36 | string request = 1; 37 | int32 query = 2; 38 | } 39 | 40 | message EchoGetResponse { 41 | string response = 1; 42 | } 43 | 44 | message GreetGetRequest { 45 | string request = 1; 46 | string content = 2; 47 | } 48 | 49 | message GreetGetResponse { 50 | string response = 1; 51 | } -------------------------------------------------------------------------------- /example/env_sink/rust-grpc-proxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/env_sink/rust-grpc-proxy -------------------------------------------------------------------------------- /example/env_sink/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/env_sink/server -------------------------------------------------------------------------------- /example/hello/config.toml: -------------------------------------------------------------------------------- 1 | [[proxy_sink]] 2 | name = "hello" 3 | addr = "127.0.0.1:8888" -------------------------------------------------------------------------------- /example/hello/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proto; 4 | option go_package = "./proto"; 5 | //import "google/protobuf/struct.proto"; 6 | 7 | import "google/api/annotations.proto"; 8 | 9 | 10 | 11 | // HelloWorld Service 12 | service HelloWorldService { 13 | rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse){ 14 | option (google.api.http) = { 15 | post: "/api/v2/hello" 16 | body: "*" 17 | }; 18 | }; 19 | } 20 | 21 | // Request message 22 | message HelloWorldRequest { 23 | string request = 1; 24 | } 25 | 26 | // Response message 27 | message HelloWorldResponse { 28 | string response = 1; 29 | } -------------------------------------------------------------------------------- /example/hello/helloworld: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/hello/helloworld -------------------------------------------------------------------------------- /example/metadata/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | EXPOSE 6789 3 | WORKDIR /root/ 4 | COPY target/x86_64-unknown-linux-musl/release/rust-grpc-proxy . 5 | 6 | CMD ["./rust-grpc-proxy", "run"] -------------------------------------------------------------------------------- /example/metadata/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVICE_ECHO="SERVICE_ECHO" 4 | GRPC_PROXY="GRPC_PROXY" 5 | CONFIG_PATH="config.toml" 6 | 7 | function test_one() { 8 | result=$(curl -s -l --location --request GET 'http://127.0.0.1:6789/api/v1/echo/hello/get?query=666' | jq -r '."response"') 9 | assert_eq "$result" 'GET [SERVICE_ECHO]---> request=hello query=666' "test_one" 10 | } 11 | function test_three() { 12 | result=$(curl -s -l --location --request POST 'http://127.0.0.1:6789/api/v1/echo/post' \ 13 | --header 'Content-Type: application/json' \ 14 | --data-raw '{ 15 | "request": "hello", 16 | "query": 123 17 | }' | jq -r '.response') 18 | assert_eq "$result" 'POST [SERVICE_ECHO]---> request=hello query=123' "test_three" 19 | } 20 | 21 | function assert_eq() { 22 | if [ "$1" == "$2" ] 23 | then 24 | echo "用例[$3] success" 25 | else 26 | echo -e "===> 用例[$3] assert failed <=== " 27 | echo -e " expectation -->$2 " 28 | echo -e " actual -->$1 " 29 | fi 30 | } 31 | 32 | 33 | function reset_config_file() { 34 | cat>$CONFIG_PATH < $CONFIG_PATH" && cat $CONFIG_PATH 71 | exit 72 | ;; 73 | server) 74 | start_server && echo 'src server run success' 75 | exit 76 | esac 77 | 78 | 79 | 80 | start_server 81 | 82 | 83 | if [ ! -e $CONFIG_PATH ] 84 | then 85 | reset_config_file && echo "配置文件:$CONFIG_PATH 初始化成功" 86 | fi 87 | 88 | if [ $(screen -ls | grep -c $GRPC_PROXY) -le 0 ] 89 | then 90 | echo -n "等待测试服务启动" 91 | for (( i=0;i<10;i=i+1 )) 92 | do 93 | echo -n "。" 94 | sleep 1 95 | done 96 | echo "" 97 | 98 | screen -dmS $GRPC_PROXY /bin/bash -c "./rust-grpc-proxy run -c $CONFIG_PATH" && echo "代理服务[$GRPC_PROXY]已启动" 99 | 100 | echo -n "等待代理服务启动" 101 | for (( i=0;i<10;i=i+1 )) 102 | do 103 | echo -n "。" 104 | sleep 1 105 | done 106 | echo "" 107 | else 108 | echo "$GRPC_PROXY 服务已经启动" 109 | fi 110 | 111 | if [ ! -x "$(command -v jq)" ];then 112 | echo 'jq not found , please install' 113 | echo ' MAC : brew install jq' 114 | echo ' Ubuntu : sudo apt-get install jq' 115 | exit 1 116 | fi 117 | 118 | test_one 119 | 120 | test_three -------------------------------------------------------------------------------- /example/metadata/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proto; 4 | option go_package = "./proto"; 5 | //import "google/protobuf/struct.proto"; 6 | 7 | import "google/api/annotations.proto"; 8 | 9 | 10 | 11 | // Echo Service 12 | service EchoService { 13 | rpc EchoGet(EchoGetRequest) returns (EchoGetResponse){ 14 | option (google.api.http) = { 15 | get: "/api/v1/echo/{request}/get" 16 | }; 17 | }; 18 | rpc EchoPost(EchoGetRequest) returns (EchoGetResponse){ 19 | option (google.api.http) = { 20 | post: "/api/v1/echo/post" 21 | body: "*" 22 | }; 23 | }; 24 | } 25 | 26 | // Echo Service 27 | service GreetService { 28 | rpc GreetGet(GreetGetRequest) returns (GreetGetResponse){ 29 | option (google.api.http) = { 30 | get: "/api/v1/greet/{request}" 31 | }; 32 | }; 33 | } 34 | 35 | message EchoGetRequest { 36 | string request = 1; 37 | int32 query = 2; 38 | } 39 | 40 | message EchoGetResponse { 41 | string response = 1; 42 | } 43 | 44 | message GreetGetRequest { 45 | string request = 1; 46 | string content = 2; 47 | } 48 | 49 | message GreetGetResponse { 50 | string response = 1; 51 | } -------------------------------------------------------------------------------- /example/metadata/rust-grpc-proxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/metadata/rust-grpc-proxy -------------------------------------------------------------------------------- /example/metadata/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/metadata/server -------------------------------------------------------------------------------- /example/restful/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVICE_ECHO="SERVICE_ECHO" 4 | SERVICE_GREET="SERVICE_GREET" 5 | GRPC_PROXY="GRPC_PROXY" 6 | CONFIG_PATH="config.toml" 7 | 8 | function test_one() { 9 | result=$(curl -s -l --location --request GET 'http://127.0.0.1:6789/api/v1/echo/hello/get?query=666' | jq -r '."response"') 10 | assert_eq "$result" 'GET [SERVICE_ECHO]---> request=hello query=666' "test_one" 11 | } 12 | function test_two() { 13 | result=$(curl -s -l --location --request GET 'http://127.0.0.1:6789/api/v1/greet/hello?content=world' | jq -r '.response') 14 | assert_eq "$result" 'Get [SERVICE_GREET]---> request=hello query=world' "test_two" 15 | } 16 | function test_three() { 17 | result=$(curl -s -l --location --request POST 'http://127.0.0.1:6789/api/v1/echo/post' \ 18 | --header 'Content-Type: application/json' \ 19 | --data-raw '{ 20 | "request": "hello", 21 | "query": 123 22 | }' | jq -r '.response') 23 | assert_eq "$result" 'POST [SERVICE_ECHO]---> request=hello query=123' "test_three" 24 | } 25 | 26 | function assert_eq() { 27 | if [ "$1" == "$2" ] 28 | then 29 | echo "用例[$3] success" 30 | else 31 | echo -e "===> 用例[$3] assert failed <=== " 32 | echo -e " expectation -->$2 " 33 | echo -e " actual -->$1 " 34 | fi 35 | } 36 | 37 | 38 | function reset_config_file() { 39 | cat>$CONFIG_PATH < $CONFIG_PATH" && cat $CONFIG_PATH 64 | exit 65 | ;; 66 | esac 67 | 68 | if [ $(screen -ls | grep -c $SERVICE_ECHO) -le 0 ] 69 | then 70 | screen -dmS $SERVICE_ECHO /bin/bash -c "./server server -n $SERVICE_ECHO -a :1234" && echo "测试服务[$SERVICE_ECHO]已启动" 71 | else 72 | echo "$SERVICE_ECHO 服务已经启动" 73 | fi 74 | 75 | if [ $(screen -ls | grep -c $SERVICE_GREET) -le 0 ] 76 | then 77 | screen -dmS $SERVICE_GREET /bin/bash -c "./server server -n $SERVICE_GREET -a :1235" && echo "测试服务[$SERVICE_GREET]已启动" 78 | else 79 | echo "$SERVICE_GREET 服务已经启动" 80 | fi 81 | 82 | 83 | 84 | if [ ! -e $CONFIG_PATH ] 85 | then 86 | reset_config_file && echo "配置文件:$CONFIG_PATH 初始化成功" 87 | fi 88 | 89 | if [ $(screen -ls | grep -c $GRPC_PROXY) -le 0 ] 90 | then 91 | echo -n "等待测试服务启动" 92 | for (( i=0;i<10;i=i+1 )) 93 | do 94 | echo -n "。" 95 | sleep 1 96 | done 97 | echo "" 98 | 99 | screen -dmS $GRPC_PROXY /bin/bash -c "./rust-grpc-proxy run -c $CONFIG_PATH" && echo "代理服务[$GRPC_PROXY]已启动" 100 | 101 | echo -n "等待代理服务启动" 102 | for (( i=0;i<10;i=i+1 )) 103 | do 104 | echo -n "。" 105 | sleep 1 106 | done 107 | echo "" 108 | else 109 | echo "$GRPC_PROXY 服务已经启动" 110 | fi 111 | 112 | if [ ! -x "$(command -v jq)" ];then 113 | echo 'jq not found , please install' 114 | echo ' MAC : brew install jq' 115 | echo ' Ubuntu : sudo apt-get install jq' 116 | exit 1 117 | fi 118 | 119 | test_one 120 | 121 | test_two 122 | 123 | test_three -------------------------------------------------------------------------------- /example/restful/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proto; 4 | option go_package = "./proto"; 5 | //import "google/protobuf/struct.proto"; 6 | 7 | import "google/api/annotations.proto"; 8 | 9 | 10 | 11 | // Echo Service 12 | service EchoService { 13 | rpc EchoGet(EchoGetRequest) returns (EchoGetResponse){ 14 | option (google.api.http) = { 15 | get: "/api/v1/echo/{request}/get" 16 | }; 17 | }; 18 | rpc EchoPost(EchoGetRequest) returns (EchoGetResponse){ 19 | option (google.api.http) = { 20 | post: "/api/v1/echo/post" 21 | body: "*" 22 | }; 23 | }; 24 | } 25 | 26 | // Echo Service 27 | service GreetService { 28 | rpc GreetGet(GreetGetRequest) returns (GreetGetResponse){ 29 | option (google.api.http) = { 30 | get: "/api/v1/greet/{request}" 31 | }; 32 | }; 33 | } 34 | 35 | message EchoGetRequest { 36 | string request = 1; 37 | int32 query = 2; 38 | } 39 | 40 | message EchoGetResponse { 41 | string response = 1; 42 | } 43 | 44 | message GreetGetRequest { 45 | string request = 1; 46 | string content = 2; 47 | } 48 | 49 | message GreetGetResponse { 50 | string response = 1; 51 | } -------------------------------------------------------------------------------- /example/restful/rust-grpc-proxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/restful/rust-grpc-proxy -------------------------------------------------------------------------------- /example/restful/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/example/restful/server -------------------------------------------------------------------------------- /src/app/dyn_map_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::app::DynMap; 2 | use crate::infra::dynamic::DynClient; 3 | use std::ops::DerefMut; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | use std::sync::{Arc, RwLock}; 6 | 7 | #[derive(Default)] 8 | pub struct MapList { 9 | list: Vec<(String, String, Arc)>, //name,path,dyn client 10 | } 11 | pub struct DynMapDefault { 12 | map: Vec>, 13 | index: AtomicUsize, 14 | } 15 | 16 | impl MapList { 17 | #[allow(dead_code)] 18 | fn new(list: Vec<(String, String, Arc)>) -> Self { 19 | Self { list } 20 | } 21 | fn insert( 22 | list: &mut Vec<(String, String, Arc)>, 23 | name: String, 24 | path: String, 25 | dc: DynClient, 26 | ) { 27 | //短的在前,长的在后 28 | for (i, (n, _, _)) in list.iter().enumerate() { 29 | if n.eq(&name) { 30 | list[i] = (name, path, Arc::new(dc)); 31 | return; 32 | } 33 | } 34 | let mut i = list.len() as isize - 1; 35 | while i >= 0 { 36 | if list[i as usize].0.len() < path.len() { 37 | list.insert((i as usize) + 1, (name, path, Arc::new(dc))); 38 | return; 39 | } 40 | i -= 1; 41 | } 42 | list.insert(0, (name, path, Arc::new(dc))); 43 | } 44 | } 45 | 46 | impl DynMapDefault { 47 | fn reset(&self, old_index: usize, list: Vec<(String, String, Arc)>) -> usize { 48 | let new_index = if old_index == 1 { 0 } else { 1 }; 49 | let rw = self.map.get(new_index).unwrap(); 50 | let mut rw = rw.write().unwrap(); 51 | let rw_map = rw.deref_mut(); 52 | rw_map.list = list; 53 | return new_index; 54 | } 55 | } 56 | 57 | impl Default for DynMapDefault { 58 | fn default() -> Self { 59 | let map = vec![ 60 | RwLock::new(MapList::default()), 61 | RwLock::new(MapList::default()), 62 | ]; 63 | let index = AtomicUsize::new(0); 64 | Self { map, index } 65 | } 66 | } 67 | 68 | impl DynMap for DynMapDefault { 69 | fn get(&self, path: String) -> Option> { 70 | let index = self.index.load(Ordering::Relaxed); 71 | let rw = self.map.get(index).unwrap(); 72 | let binding = rw.read(); 73 | let map_r = binding.as_ref().unwrap(); 74 | for (_, p, client) in map_r.list.iter().rev() { 75 | if path.starts_with(p) { 76 | return Some(client.clone()); 77 | } 78 | } 79 | return None; 80 | } 81 | 82 | //fixme 需要加一个写操作的互斥锁,否则极小概率导致死锁 83 | fn set(&self, name: String, path: String, dc: DynClient) { 84 | let index = self.index.load(Ordering::Relaxed); 85 | let rw = self.map.get(index).unwrap(); 86 | let binding = rw.read(); 87 | let map_r = binding.as_ref().unwrap(); 88 | let mut map = map_r.list.clone(); 89 | drop(rw); 90 | MapList::insert(&mut map, name, path, dc); 91 | let new_index = self.reset(index, map); 92 | self.index.store(new_index, Ordering::Relaxed); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/app/md_filters_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::app::MetadataAnalysis; 2 | use crate::config::MetadataFilter; 3 | use hyper::header::HeaderValue; 4 | use hyper::HeaderMap; 5 | use std::collections::HashMap; 6 | 7 | const HTTP_CONTENT_TYPE:&'static str = "http-content-type"; 8 | const CONTENT_TYPE:&'static str = "content-type"; 9 | 10 | pub struct MetadataAnalysisDefaultImpl { 11 | prefix: Vec, 12 | has: HashMap, 13 | show_response_server: bool, 14 | response_default_content_type : String, 15 | } 16 | 17 | impl From<&MetadataFilter> for MetadataAnalysisDefaultImpl { 18 | fn from(mf: &MetadataFilter) -> Self { 19 | let mut has = HashMap::new(); 20 | let mut iter = mf.r#match.iter(); 21 | while let Some(key) = iter.next() { 22 | has.insert(key.clone(), true); 23 | } 24 | has.insert(HTTP_CONTENT_TYPE.to_string(),true); 25 | let prefix = mf.prefix.clone(); 26 | let show_response_server = mf.response_show_server; 27 | let response_default_content_type = mf.response_default_content_type.clone(); 28 | Self { 29 | prefix, 30 | has, 31 | show_response_server, 32 | response_default_content_type, 33 | } 34 | } 35 | } 36 | 37 | impl MetadataAnalysisDefaultImpl { 38 | fn allow(&self, key: &str) -> bool { 39 | if self.has.get(key).is_some() { 40 | return true; 41 | } 42 | for i in self.prefix.iter() { 43 | if key.starts_with(i.as_str()) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | } 49 | } 50 | 51 | impl MetadataAnalysis for MetadataAnalysisDefaultImpl { 52 | fn request(&self, headers: &HeaderMap) -> HashMap { 53 | let mut header = HashMap::new(); 54 | for (k, v) in headers.iter() { 55 | if !self.allow(k.as_str()) { 56 | continue; 57 | } 58 | match v.to_str() { 59 | Ok(val) => { 60 | header.insert(k.to_string(), val.into()); 61 | } 62 | Err(e) => { 63 | wd_log::log_warn_ln!("MetadataAnalysisDefaultImpl.request error:{}", e); 64 | } 65 | }; 66 | } 67 | return header; 68 | } 69 | 70 | fn response(&self, header: HashMap) -> HashMap { 71 | let mut mp = HashMap::new(); 72 | let mut header = header.into_iter(); 73 | while let Some((key, value)) = header.next() { 74 | if self.allow(key.as_str()) { 75 | mp.insert(key, value); 76 | } 77 | } 78 | if self.show_response_server { 79 | mp.insert("proxy_server".into(), "rust-grpc-proxy".into()); 80 | } 81 | if let Some(ty) = mp.remove(HTTP_CONTENT_TYPE) { 82 | mp.insert(CONTENT_TYPE.to_string(),ty); 83 | }else{ 84 | mp.insert(CONTENT_TYPE.to_string(),self.response_default_content_type.clone()); 85 | } 86 | return mp; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/app/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::dynamic::DynClient; 2 | use hyper::http::HeaderValue; 3 | use hyper::HeaderMap; 4 | use std::collections::HashMap; 5 | use std::sync::Arc; 6 | 7 | mod dyn_map_impl; 8 | mod md_filters_impl; 9 | mod proxy_sink; 10 | mod query_analysis_impl; 11 | mod server; 12 | 13 | use crate::app::dyn_map_impl::DynMapDefault; 14 | use crate::app::md_filters_impl::MetadataAnalysisDefaultImpl; 15 | use crate::app::query_analysis_impl::QueryAnalysisDefaultImpl; 16 | use crate::config::Config; 17 | use crate::infra::server::{ 18 | HyperHttpServerBuilder, LogMiddle, RequestIdMiddle, ShutDown, TimeMiddle, 19 | }; 20 | pub use proxy_sink::*; 21 | pub use server::AppEntity; 22 | 23 | pub trait DynMap: Send + Sync { 24 | fn get(&self, path: String) -> Option>; 25 | fn set(&self, name: String, path: String, dc: DynClient); 26 | } 27 | pub trait QueryAnalysis: Send + Sync { 28 | fn analysis(&self, query: &str) -> Option>; 29 | } 30 | pub trait MetadataAnalysis: Send + Sync { 31 | fn request(&self, header: &HeaderMap) -> HashMap; 32 | fn response(&self, header: HashMap) -> HashMap; 33 | } 34 | 35 | pub async fn start(sd: ShutDown, cfg: Config) { 36 | let map = Arc::new(DynMapDefault::default()); 37 | let app = AppEntity::new( 38 | map.clone(), 39 | Arc::new(QueryAnalysisDefaultImpl), 40 | Arc::new(MetadataAnalysisDefaultImpl::from(&cfg.metadata_filters)), 41 | ); 42 | init_proxy_sink(map.clone(), cfg.proxy_sink).await; 43 | 44 | init_env_sink(map, cfg.env_sink, cfg.server.name).await; 45 | //todo 开启新的服务动态监听grpc sink变化 gateway 模式 46 | 47 | let _ = HyperHttpServerBuilder::new() 48 | .set_addr( 49 | cfg.server 50 | .addr 51 | .parse() 52 | .expect("parse config server.addr error"), 53 | ) 54 | .handle(app) 55 | .append_filter(TimeMiddle) 56 | .append_filter(RequestIdMiddle::new()) 57 | .append_filter(LogMiddle) 58 | // .run().await.expect("http服务报错"); 59 | .set_shutdown_singe(sd) 60 | .async_run(); 61 | } 62 | 63 | pub async fn show(cfg: Config) { 64 | if cfg.proxy_sink.is_empty() { 65 | wd_log::log_warn_ln!("config[proxy_sink] is nil"); 66 | return; 67 | } 68 | for i in cfg.proxy_sink.iter() { 69 | wd_log::log_info_ln!( 70 | "---------> start reflect grpc server[{}] <---------", 71 | i.name 72 | ); 73 | let client = wd_log::res_panic!(init_dyn_client(i.name.clone(),i.addr.clone()).await;"init_proxy_sink: init {} failed,addr=({})",i.name,i.addr); 74 | let list = client.method_list(); 75 | for i in list.iter() { 76 | wd_log::log_info_ln!("{} {} {}", i.0, i.1, i.2); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/app/proxy_sink.rs: -------------------------------------------------------------------------------- 1 | use crate::app::DynMap; 2 | use crate::config::{EnvSink, ProxySink}; 3 | use crate::infra::dynamic::{DynClient, JsonProtoTransitionDefaultImpl, SimpleIndex}; 4 | use crate::infra::profiler::FileDescProfiler; 5 | use std::sync::Arc; 6 | 7 | pub async fn init_proxy_sink(dm: Arc, ps: Vec) { 8 | for i in ps.into_iter() { 9 | let client = wd_log::res_panic!(init_dyn_client(i.name.clone(),i.addr.clone()).await;"init_proxy_sink: init {} failed,addr=({})",i.name,i.addr); 10 | dm.set(i.name, i.prefix, client); 11 | } 12 | } 13 | 14 | pub async fn init_env_sink(dm: Arc, es: EnvSink, name: String) { 15 | if !es.disable { 16 | return; 17 | } 18 | let mut i = es.interval_sec; 19 | let addr = match std::env::var(es.addr_env_key.as_str()) { 20 | Ok(o) => o, 21 | Err(err) => { 22 | wd_log::log_error_ln!("load env[{}] error:{}",es.addr_env_key,err); 23 | return; 24 | } 25 | }; 26 | if addr.is_empty(){ 27 | wd_log::log_error_ln!("load env[{}] is nil",es.addr_env_key); 28 | return; 29 | } 30 | wd_log::log_debug_ln!("load env addr: {} start init dyn client", addr); 31 | while i < es.wait_time_max_sec { 32 | tokio::time::sleep(std::time::Duration::from_secs(es.interval_sec)).await; 33 | let result = init_dyn_client(name.clone(), addr.clone()).await; 34 | match result { 35 | Ok(o) => { 36 | for i in o.method_list().iter() { 37 | wd_log::log_debug_ln!("env sink ->{} {} {}", i.0, i.1, i.2); 38 | } 39 | dm.set(name, es.prefix, o); 40 | return; 41 | } 42 | Err(e) => { 43 | wd_log::log_debug_ln!("init_env_sink failed={}", e); 44 | } 45 | } 46 | i += es.interval_sec; 47 | } 48 | wd_log::log_error_ln!("init dyn client failed,please src server is normal"); 49 | } 50 | 51 | pub async fn init_dyn_client(name: String, sink_addr: String) -> anyhow::Result { 52 | let index = FileDescProfiler::new() 53 | .map(sink_addr.clone(), SimpleIndex::parse) 54 | .await?; 55 | // .expect("parse grpc index from reflect failed"); 56 | let client = DynClient::new(JsonProtoTransitionDefaultImpl, index) 57 | .set_host_port(sink_addr) 58 | .set_name(name); 59 | return Ok(client); 60 | } 61 | -------------------------------------------------------------------------------- /src/app/query_analysis_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::app::QueryAnalysis; 2 | use std::collections::HashMap; 3 | 4 | pub struct QueryAnalysisDefaultImpl; 5 | 6 | impl QueryAnalysis for QueryAnalysisDefaultImpl { 7 | fn analysis(&self, query: &str) -> Option> { 8 | let args = query 9 | .split('&') 10 | .map(|x| x.to_string()) 11 | .collect::>(); 12 | let mut map = HashMap::new(); 13 | for v in args.into_iter() { 14 | let ss = v.split('=').collect::>(); 15 | if ss.len() == 2 { 16 | map.insert(ss[0].into(), ss[1].into()); 17 | } 18 | } 19 | if map.is_empty() { 20 | None 21 | } else { 22 | Some(map) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/server.rs: -------------------------------------------------------------------------------- 1 | use crate::app::{DynMap, MetadataAnalysis, QueryAnalysis}; 2 | use crate::infra::server::HttpHandle; 3 | use hyper::{Body, Request, Response}; 4 | use std::collections::HashMap; 5 | use std::sync::Arc; 6 | use wd_run::Context; 7 | 8 | pub struct AppEntity { 9 | map: Arc, 10 | query: Arc, 11 | md_filters: Arc, 12 | } 13 | 14 | impl AppEntity { 15 | pub fn new( 16 | map: Arc, 17 | query: Arc, 18 | md_filters: Arc, 19 | ) -> Self { 20 | Self { 21 | map, 22 | query, 23 | md_filters, 24 | } 25 | } 26 | pub fn response( 27 | status: u16, 28 | body: B, 29 | resp_headers: Option>, 30 | ) -> anyhow::Result> 31 | where 32 | Body: From, 33 | { 34 | let body = Body::from(body); 35 | let mut resp = Response::builder().status(status); 36 | if let Some(mp) = resp_headers { 37 | for (k, v) in mp.into_iter() { 38 | resp = resp.header(k, v); 39 | } 40 | } 41 | let resp = resp.body(body)?; 42 | Ok(resp) 43 | } 44 | 45 | pub fn error(t: T) -> anyhow::Result> { 46 | let body = Body::from(t.to_string()); 47 | let resp = Response::builder().status(500).body(body)?; 48 | Ok(resp) 49 | } 50 | } 51 | 52 | #[async_trait::async_trait] 53 | impl HttpHandle for AppEntity { 54 | async fn handle(&self, _ctx: Context, req: Request) -> anyhow::Result> { 55 | let method = req.method().clone(); 56 | let path = req.uri().path().to_string(); 57 | let query = req.uri().query().unwrap_or(""); 58 | let query = self.query.analysis(query); 59 | let metadata = self.md_filters.request(req.headers()); 60 | 61 | let body = req.into_body(); 62 | let body = match hyper::body::to_bytes(body).await { 63 | Ok(o) => o.to_vec(), 64 | Err(e) => return AppEntity::error(e.to_string()), 65 | }; 66 | 67 | let client = match self.map.get(path.clone()) { 68 | None => return AppEntity::response(404, "not found", None), 69 | Some(c) => c, 70 | }; 71 | 72 | let (resp_header, resp_body) = 73 | match client.invoke(method, path, metadata, body, query).await { 74 | Ok(o) => o, 75 | Err(e) => return AppEntity::error(e.to_string()), 76 | }; 77 | let resp_header = self.md_filters.response(resp_header); 78 | AppEntity::response(200, resp_body, Some(resp_header)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/cmd/exit.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::server::ShutDown; 2 | use std::future::Future; 3 | use std::pin::Pin; 4 | use wd_run::Context; 5 | 6 | pub struct ExitApplication { 7 | server_sd: ShutDown, 8 | } 9 | 10 | impl ExitApplication { 11 | pub fn new(server_sd: ShutDown) -> Self { 12 | Self { server_sd } 13 | } 14 | } 15 | 16 | impl wd_run::EventHandle for ExitApplication { 17 | fn handle(&self, ctx: Context) -> Pin + Send>> { 18 | let sd = self.server_sd.clone(); 19 | Box::pin(async move { 20 | wd_log::log_debug_ln!("exit signal is received, the application begins to exit"); 21 | sd.close().await; 22 | wd_log::log_debug_ln!("application exit succeeded"); 23 | return ctx; 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | mod exit; 2 | mod run; 3 | mod show; 4 | mod test; 5 | 6 | pub async fn start() { 7 | let (run_cmd, sd) = run::RunApplication::new(); 8 | let exit_handle = exit::ExitApplication::new(sd); 9 | let test_cmd = test::TestExampleBuilder::new().init_examples().build(); 10 | let show_args = show::Show::args(); 11 | wd_run::ArgsManager::new() 12 | .register_cmd(run::RunApplication::args(), run_cmd) 13 | .register_cmd(test_cmd.args(), test_cmd) 14 | .register_cmd(show_args, show::Show) 15 | .register_exit(exit_handle) 16 | .run() 17 | .await; 18 | } 19 | -------------------------------------------------------------------------------- /src/cmd/run.rs: -------------------------------------------------------------------------------- 1 | use crate::app; 2 | use crate::config::{Config, Log}; 3 | use crate::infra::server::ShutDown; 4 | use std::future::Future; 5 | use std::pin::Pin; 6 | use wd_log::Level; 7 | use wd_run::{CmdInfo, Context}; 8 | 9 | #[derive(Default)] 10 | pub struct RunApplication { 11 | sd: ShutDown, 12 | } 13 | 14 | impl RunApplication { 15 | pub fn new() -> (Self, ShutDown) { 16 | let app = Self { 17 | sd: ShutDown::default(), 18 | }; 19 | let sd = app.sd.clone(); 20 | (app, sd) 21 | } 22 | pub fn args() -> CmdInfo { 23 | CmdInfo::new("run", "run application").add( 24 | "c", 25 | "./src/config/config.toml", 26 | "config file path", 27 | ) 28 | } 29 | pub async fn load_config(ctx: &Context) -> Config { 30 | let path = ctx 31 | .copy::<_, String>("c") 32 | .await 33 | .expect("load config failed"); 34 | let cfg = match Config::from_file_by_path(&path) { 35 | Ok(o) => o, 36 | Err(e) => { 37 | wd_log::log_info_ln!("from file:[{}] load config error={}", path,e); 38 | Config::default() 39 | } 40 | }; 41 | wd_log::log_debug_ln!("config file load success:{}", cfg.to_string()); 42 | return cfg; 43 | } 44 | pub fn init_log(app: String, log: Log) { 45 | wd_log::set_level(Level::from(log.level)); 46 | // unsafe { 47 | let name: &'static str = Box::leak(app.into()); 48 | wd_log::set_prefix(name); 49 | // } 50 | wd_log::show_time(log.show_time); 51 | wd_log::show_file_line(log.show_file_line); 52 | if !log.out_file_path.is_empty() { 53 | wd_log::output_to_file(log.out_file_path).expect("init_log output_to_file error") 54 | } 55 | wd_log::log_debug_ln!("log config init success"); 56 | } 57 | 58 | pub async fn launch(cfg: Config, sd: ShutDown) { 59 | wd_log::log_debug_ln!("start run application"); 60 | app::start(sd, cfg).await; 61 | wd_log::log_debug_ln!("run application success"); 62 | } 63 | // pub async fn launch(_cfg:Config,sd:ShutDown) { 64 | // let _ = HyperHttpServerBuilder::new() 65 | // .handle(|_c, r: Request| async move { 66 | // let method = r.method(); 67 | // let path = r.uri(); 68 | // wd_log::log_debug_ln!("method:[{}] path:[{}]", method, path); 69 | // tokio::time::sleep(Duration::from_millis(100)).await; 70 | // Ok(Response::new(Body::from("success"))) 71 | // }) 72 | // .append_filter(TimeMiddle) 73 | // .append_filter(RequestIdMiddle::new()) 74 | // .append_filter(LogMiddle) 75 | // // .run().await.expect("http服务报错"); 76 | // .set_shutdown_singe(sd) 77 | // .async_run(); 78 | // } 79 | } 80 | 81 | impl wd_run::EventHandle for RunApplication { 82 | fn handle(&self, ctx: Context) -> Pin + Send>> { 83 | let sd = self.sd.clone(); 84 | Box::pin(async move { 85 | let cfg = RunApplication::load_config(&ctx).await; 86 | RunApplication::init_log(cfg.server.name.clone(), cfg.log.clone()); 87 | RunApplication::launch(cfg, sd.clone()).await; //启动客户端 88 | sd.wait_close().await; 89 | return ctx; 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/cmd/show.rs: -------------------------------------------------------------------------------- 1 | use crate::app; 2 | use crate::cmd::run::RunApplication; 3 | use std::future::Future; 4 | use std::pin::Pin; 5 | use wd_run::{CmdInfo, Context}; 6 | 7 | #[derive(Default)] 8 | pub struct Show; 9 | 10 | impl Show { 11 | pub fn args() -> CmdInfo { 12 | CmdInfo::new("show", "show config file proxy sink method list").add( 13 | "c", 14 | "./src/config/config.toml", 15 | "config file path", 16 | ) 17 | } 18 | } 19 | 20 | impl wd_run::EventHandle for Show { 21 | fn handle(&self, ctx: Context) -> Pin + Send>> { 22 | Box::pin(async move { 23 | let cfg = RunApplication::load_config(&ctx).await; 24 | RunApplication::init_log(cfg.server.name.clone(), cfg.log.clone()); 25 | app::show(cfg).await; 26 | return ctx; 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cmd/test.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::dynamic::{DynClient, JsonProtoTransitionDefaultImpl, SimpleIndex}; 2 | use crate::infra::profiler::FileDescProfiler; 3 | use hyper::Method; 4 | use std::collections::HashMap; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use std::sync::Arc; 8 | use wd_run::{CmdInfo, Context}; 9 | 10 | #[derive(Default)] 11 | pub struct TestExampleBuilder { 12 | examples: HashMap>, 13 | } 14 | impl TestExampleBuilder { 15 | pub fn new() -> Self { 16 | Self { 17 | examples: HashMap::new(), 18 | } 19 | } 20 | 21 | pub fn init_examples(mut self) -> Self { 22 | self.examples 23 | .insert("dyn".into(), Box::new(TestExampleDynClient)); 24 | self 25 | } 26 | pub fn build(self) -> TestExample { 27 | TestExample { 28 | examples: Arc::new(self.examples), 29 | } 30 | } 31 | } 32 | 33 | #[derive(Default)] 34 | pub struct TestExample { 35 | examples: Arc>>, 36 | } 37 | impl TestExample { 38 | pub fn args(&self) -> CmdInfo { 39 | let mut ms: String = "all".into(); 40 | for (i, _) in self.examples.iter() { 41 | ms.push_str(","); 42 | ms.push_str(i) 43 | } 44 | CmdInfo::new("test", "test example").add("m", "all", format!("test models: {}", ms)) 45 | } 46 | } 47 | 48 | impl wd_run::EventHandle for TestExample { 49 | fn handle(&self, ctx: Context) -> Pin + Send>> { 50 | let examples = self.examples.clone(); 51 | Box::pin(async move { 52 | let order = ctx.copy::<_, String>("m").await.unwrap(); 53 | let ods = order 54 | .split(",") 55 | .map(|x| x.to_string()) 56 | .collect::>(); 57 | wd_log::log_info_ln!("start test"); 58 | if order.eq("all") { 59 | //测试全部 60 | for (m, func) in examples.iter() { 61 | wd_log::log_info_ln!("------> module:[{}] test start", m); 62 | func.handle(ctx.clone()).await; 63 | wd_log::log_info_ln!("------> module:[{}] test success", m); 64 | } 65 | } else { 66 | for (m, func) in examples.iter() { 67 | if ods.contains(m) { 68 | wd_log::log_info_ln!("------> module:[{}] test start", m); 69 | func.handle(ctx.clone()).await; 70 | wd_log::log_info_ln!("------> module:[{}] test success", m); 71 | } 72 | } 73 | } 74 | wd_log::log_info_ln!("test over"); 75 | return ctx; 76 | }) 77 | } 78 | } 79 | 80 | struct TestExampleDynClient; 81 | 82 | impl wd_run::EventHandle for TestExampleDynClient { 83 | fn handle(&self, ctx: Context) -> Pin + Send>> { 84 | Box::pin(async move { 85 | let index = FileDescProfiler::new() 86 | .map("127.0.0.1:666".into(), SimpleIndex::parse) 87 | .await 88 | .expect("parse grpc index from reflect failed"); 89 | wd_log::log_info_ln!("FileDescProfiler reflect success"); 90 | let client = DynClient::new(JsonProtoTransitionDefaultImpl, index) 91 | .set_host_port("127.0.0.1:666"); 92 | let request_body = Vec::from(r#"{"request":"hello"}"#); 93 | let (_, resp) = client 94 | .invoke( 95 | Method::POST, 96 | "/api/v2/hello".into(), 97 | HashMap::new(), 98 | request_body, 99 | None, 100 | ) 101 | .await 102 | .expect("invoke grpc request failed"); 103 | let resp_body = String::from_utf8_lossy(resp.as_slice()).to_string(); 104 | wd_log::log_info_ln!("response body:{}", resp_body); 105 | assert_eq!( 106 | resp_body, r#"{"response": "hello world"}"#, 107 | "grpc response body is error" 108 | ); 109 | return ctx; 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/config/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::path::Path; 3 | 4 | macro_rules! field_generate { 5 | ($cfg:tt; $($name:tt,$ty:ty,$default:expr,$default_desc:tt);*) => { 6 | #[derive(Debug,Serialize,Deserialize,Clone)] 7 | pub struct $cfg{ 8 | $( 9 | #[serde(default=$default_desc)] 10 | pub $name : $ty, 11 | )* 12 | 13 | } 14 | impl $cfg{ 15 | $( 16 | fn $name()->$ty{ 17 | $default 18 | } 19 | )* 20 | } 21 | impl Default for $cfg{ 22 | fn default() -> Self { 23 | Self{ 24 | $( 25 | $name : $default, 26 | )* 27 | } 28 | } 29 | } 30 | }; 31 | } 32 | 33 | field_generate!(Server; 34 | name,String,String::from("rust-grpc-proxy"),"Server::name"; 35 | addr,String,String::from("0.0.0.0:6789"),"Server::addr" 36 | // control_pre,String,String::from("/grpc/proxy"),"Server::control_pre" 37 | ); 38 | field_generate!(Log; 39 | level,String,String::from("debug"),"Log::level"; 40 | show_time,bool,true,"Log::show_time"; 41 | show_file_line,bool,false,"Log::show_file_line"; 42 | out_file_path,String,String::new(),"Log::out_file_path" 43 | ); 44 | 45 | field_generate!(DynamicSink; 46 | enable,bool,false,"DynamicSink::enable"; 47 | addr,String,String::from("0.0.0.0:6790"),"DynamicSink::addr" 48 | ); 49 | 50 | field_generate!(ProxySink; 51 | name,String,String::from("default"),"ProxySink::name"; 52 | addr,String,String::from(""),"ProxySink::addr"; 53 | prefix,String,String::from("/"),"ProxySink::prefix" 54 | ); 55 | 56 | field_generate!(MetadataFilter; 57 | prefix,Vec,vec![String::from("md-")],"MetadataFilter::prefix"; 58 | r#match,Vec,vec![],"MetadataFilter::r#match"; 59 | response_show_server,bool,true,"MetadataFilter::response_show_server"; 60 | response_default_content_type,String,"application/json".into(),"MetadataFilter::response_default_content_type" //如果grpc response中未指明 `http-content-type`,则使用此字段作为返回内容类型 61 | ); 62 | 63 | field_generate!(EnvSink; 64 | disable,bool,true,"EnvSink::disable"; 65 | addr_env_key,String,String::from("RUST_GRPC_PROXY_ADDR"),"EnvSink::addr_env_key"; 66 | wait_time_max_sec,u64,300,"EnvSink::wait_time_max_sec"; 67 | interval_sec,u64,10,"EnvSink::interval_sec"; 68 | prefix,String,String::from("/"),"EnvSink::prefix" 69 | ); 70 | 71 | #[derive(Debug, Default, Serialize, Deserialize, Clone)] 72 | pub struct Config { 73 | #[serde(default = "Server::default")] 74 | pub server: Server, 75 | #[serde(default = "Log::default")] 76 | pub log: Log, 77 | #[serde(default = "Vec::new")] 78 | pub proxy_sink: Vec, 79 | #[serde(default = "DynamicSink::default")] 80 | pub dynamic_sink: DynamicSink, 81 | #[serde(default = "MetadataFilter::default")] 82 | pub metadata_filters: MetadataFilter, 83 | #[serde(default = "EnvSink::default")] 84 | pub env_sink: EnvSink, 85 | } 86 | 87 | impl Config { 88 | pub fn from_file_by_path(path: impl AsRef) -> anyhow::Result { 89 | match wd_run::load_config(path) { 90 | Err(e) => return Err(anyhow::anyhow!(e)), 91 | Ok(o) => Ok(o), 92 | } 93 | } 94 | } 95 | 96 | impl ToString for Config { 97 | fn to_string(&self) -> String { 98 | match serde_json::to_string(self) { 99 | Ok(o) => o, 100 | Err(e) => e.to_string(), 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/config/config.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/grpc-proxy/a2da18609fb27d61102bdc9e9e85f490dfb6ad53/src/config/config.toml -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | 3 | pub use config::*; 4 | -------------------------------------------------------------------------------- /src/infra/dynamic/anno.rs: -------------------------------------------------------------------------------- 1 | /// Defines the HTTP configuration for an API service. It contains a list of 2 | /// \[HttpRule][google.api.HttpRule\], each specifying the mapping of an RPC method 3 | /// to one or more HTTP REST API methods. 4 | #[allow(clippy::derive_partial_eq_without_eq)] 5 | #[derive(Clone, PartialEq, ::prost::Message)] 6 | pub struct Http { 7 | /// A list of HTTP configuration rules that apply to individual API methods. 8 | /// 9 | /// **NOTE:** All service configuration rules follow "last one wins" order. 10 | #[prost(message, repeated, tag = "1")] 11 | pub rules: ::prost::alloc::vec::Vec, 12 | /// When set to true, URL path parmeters will be fully URI-decoded except in 13 | /// cases of single segment matches in reserved expansion, where "%2F" will be 14 | /// left encoded. 15 | /// 16 | /// The default behavior is to not decode RFC 6570 reserved characters in multi 17 | /// segment matches. 18 | #[prost(bool, tag = "2")] 19 | pub fully_decode_reserved_expansion: bool, 20 | } 21 | /// `HttpRule` defines the mapping of an RPC method to one or more HTTP 22 | /// REST API methods. The mapping specifies how different portions of the RPC 23 | /// request message are mapped to URL path, URL query parameters, and 24 | /// HTTP request body. The mapping is typically specified as an 25 | /// `google.api.http` annotation on the RPC method, 26 | /// see "google/api/annotations.proto" for details. 27 | /// 28 | /// The mapping consists of a field specifying the path template and 29 | /// method kind. The path template can refer to fields in the request 30 | /// message, as in the example below which describes a REST GET 31 | /// operation on a resource collection of messages: 32 | /// 33 | /// 34 | /// service Messaging { 35 | /// rpc GetMessage(GetMessageRequest) returns (Message) { 36 | /// option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}"; 37 | /// } 38 | /// } 39 | /// message GetMessageRequest { 40 | /// message SubMessage { 41 | /// string subfield = 1; 42 | /// } 43 | /// string message_id = 1; // mapped to the URL 44 | /// SubMessage sub = 2; // `sub.subfield` is url-mapped 45 | /// } 46 | /// message Message { 47 | /// string text = 1; // content of the resource 48 | /// } 49 | /// 50 | /// The same http annotation can alternatively be expressed inside the 51 | /// `GRPC API Configuration` YAML file. 52 | /// 53 | /// http: 54 | /// rules: 55 | /// - selector: .Messaging.GetMessage 56 | /// get: /v1/messages/{message_id}/{sub.subfield} 57 | /// 58 | /// This definition enables an automatic, bidrectional mapping of HTTP 59 | /// JSON to RPC. Example: 60 | /// 61 | /// HTTP | RPC 62 | /// -----|----- 63 | /// `GET /v1/messages/123456/foo` | `GetMessage(message_id: "123456" sub: SubMessage(subfield: "foo"))` 64 | /// 65 | /// In general, not only fields but also field paths can be referenced 66 | /// from a path pattern. Fields mapped to the path pattern cannot be 67 | /// repeated and must have a primitive (non-message) type. 68 | /// 69 | /// Any fields in the request message which are not bound by the path 70 | /// pattern automatically become (optional) HTTP query 71 | /// parameters. Assume the following definition of the request message: 72 | /// 73 | /// 74 | /// service Messaging { 75 | /// rpc GetMessage(GetMessageRequest) returns (Message) { 76 | /// option (google.api.http).get = "/v1/messages/{message_id}"; 77 | /// } 78 | /// } 79 | /// message GetMessageRequest { 80 | /// message SubMessage { 81 | /// string subfield = 1; 82 | /// } 83 | /// string message_id = 1; // mapped to the URL 84 | /// int64 revision = 2; // becomes a parameter 85 | /// SubMessage sub = 3; // `sub.subfield` becomes a parameter 86 | /// } 87 | /// 88 | /// 89 | /// This enables a HTTP JSON to RPC mapping as below: 90 | /// 91 | /// HTTP | RPC 92 | /// -----|----- 93 | /// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo"))` 94 | /// 95 | /// Note that fields which are mapped to HTTP parameters must have a 96 | /// primitive type or a repeated primitive type. Message types are not 97 | /// allowed. In the case of a repeated type, the parameter can be 98 | /// repeated in the URL, as in `...?param=A¶m=B`. 99 | /// 100 | /// For HTTP method kinds which allow a request body, the `body` field 101 | /// specifies the mapping. Consider a REST update method on the 102 | /// message resource collection: 103 | /// 104 | /// 105 | /// service Messaging { 106 | /// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { 107 | /// option (google.api.http) = { 108 | /// put: "/v1/messages/{message_id}" 109 | /// body: "message" 110 | /// }; 111 | /// } 112 | /// } 113 | /// message UpdateMessageRequest { 114 | /// string message_id = 1; // mapped to the URL 115 | /// Message message = 2; // mapped to the body 116 | /// } 117 | /// 118 | /// 119 | /// The following HTTP JSON to RPC mapping is enabled, where the 120 | /// representation of the JSON in the request body is determined by 121 | /// protos JSON encoding: 122 | /// 123 | /// HTTP | RPC 124 | /// -----|----- 125 | /// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" message { text: "Hi!" })` 126 | /// 127 | /// The special name `*` can be used in the body mapping to define that 128 | /// every field not bound by the path template should be mapped to the 129 | /// request body. This enables the following alternative definition of 130 | /// the update method: 131 | /// 132 | /// service Messaging { 133 | /// rpc UpdateMessage(Message) returns (Message) { 134 | /// option (google.api.http) = { 135 | /// put: "/v1/messages/{message_id}" 136 | /// body: "*" 137 | /// }; 138 | /// } 139 | /// } 140 | /// message Message { 141 | /// string message_id = 1; 142 | /// string text = 2; 143 | /// } 144 | /// 145 | /// 146 | /// The following HTTP JSON to RPC mapping is enabled: 147 | /// 148 | /// HTTP | RPC 149 | /// -----|----- 150 | /// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" text: "Hi!")` 151 | /// 152 | /// Note that when using `*` in the body mapping, it is not possible to 153 | /// have HTTP parameters, as all fields not bound by the path end in 154 | /// the body. This makes this option more rarely used in practice of 155 | /// defining REST APIs. The common usage of `*` is in custom methods 156 | /// which don't use the URL at all for transferring data. 157 | /// 158 | /// It is possible to define multiple HTTP methods for one RPC by using 159 | /// the `additional_bindings` option. Example: 160 | /// 161 | /// service Messaging { 162 | /// rpc GetMessage(GetMessageRequest) returns (Message) { 163 | /// option (google.api.http) = { 164 | /// get: "/v1/messages/{message_id}" 165 | /// additional_bindings { 166 | /// get: "/v1/users/{user_id}/messages/{message_id}" 167 | /// } 168 | /// }; 169 | /// } 170 | /// } 171 | /// message GetMessageRequest { 172 | /// string message_id = 1; 173 | /// string user_id = 2; 174 | /// } 175 | /// 176 | /// 177 | /// This enables the following two alternative HTTP JSON to RPC 178 | /// mappings: 179 | /// 180 | /// HTTP | RPC 181 | /// -----|----- 182 | /// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` 183 | /// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: "123456")` 184 | /// 185 | /// # Rules for HTTP mapping 186 | /// 187 | /// The rules for mapping HTTP path, query parameters, and body fields 188 | /// to the request message are as follows: 189 | /// 190 | /// 1. The `body` field specifies either `*` or a field path, or is 191 | /// omitted. If omitted, it indicates there is no HTTP request body. 192 | /// 2. Leaf fields (recursive expansion of nested messages in the 193 | /// request) can be classified into three types: 194 | /// (a) Matched in the URL template. 195 | /// (b) Covered by body (if body is `*`, everything except (a) fields; 196 | /// else everything under the body field) 197 | /// (c) All other fields. 198 | /// 3. URL query parameters found in the HTTP request are mapped to (c) fields. 199 | /// 4. Any body sent with an HTTP request can contain only (b) fields. 200 | /// 201 | /// The syntax of the path template is as follows: 202 | /// 203 | /// Template = "/" Segments [ Verb ] ; 204 | /// Segments = Segment { "/" Segment } ; 205 | /// Segment = "*" | "**" | LITERAL | Variable ; 206 | /// Variable = "{" FieldPath [ "=" Segments ] "}" ; 207 | /// FieldPath = IDENT { "." IDENT } ; 208 | /// Verb = ":" LITERAL ; 209 | /// 210 | /// The syntax `*` matches a single path segment. The syntax `**` matches zero 211 | /// or more path segments, which must be the last part of the path except the 212 | /// `Verb`. The syntax `LITERAL` matches literal text in the path. 213 | /// 214 | /// The syntax `Variable` matches part of the URL path as specified by its 215 | /// template. A variable template must not contain other variables. If a variable 216 | /// matches a single path segment, its template may be omitted, e.g. `{var}` 217 | /// is equivalent to `{var=*}`. 218 | /// 219 | /// If a variable contains exactly one path segment, such as `"{var}"` or 220 | /// `"{var=*}"`, when such a variable is expanded into a URL path, all characters 221 | /// except `\[-_.~0-9a-zA-Z\]` are percent-encoded. Such variables show up in the 222 | /// Discovery Document as `{var}`. 223 | /// 224 | /// If a variable contains one or more path segments, such as `"{var=foo/*}"` 225 | /// or `"{var=**}"`, when such a variable is expanded into a URL path, all 226 | /// characters except `\[-_.~/0-9a-zA-Z\]` are percent-encoded. Such variables 227 | /// show up in the Discovery Document as `{+var}`. 228 | /// 229 | /// NOTE: While the single segment variable matches the semantics of 230 | /// [RFC 6570]() Section 3.2.2 231 | /// Simple String Expansion, the multi segment variable **does not** match 232 | /// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion 233 | /// does not expand special characters like `?` and `#`, which would lead 234 | /// to invalid URLs. 235 | /// 236 | /// NOTE: the field paths in variables and in the `body` must not refer to 237 | /// repeated fields or map fields. 238 | #[allow(clippy::derive_partial_eq_without_eq)] 239 | #[derive(Clone, PartialEq, ::prost::Message)] 240 | pub struct HttpRule { 241 | /// Selects methods to which this rule applies. 242 | /// 243 | /// Refer to \[selector][google.api.DocumentationRule.selector\] for syntax details. 244 | #[prost(string, tag = "1")] 245 | pub selector: ::prost::alloc::string::String, 246 | /// The name of the request field whose value is mapped to the HTTP body, or 247 | /// `*` for mapping all fields not captured by the path pattern to the HTTP 248 | /// body. NOTE: the referred field must not be a repeated field and must be 249 | /// present at the top-level of request message type. 250 | #[prost(string, tag = "7")] 251 | pub body: ::prost::alloc::string::String, 252 | /// Optional. The name of the response field whose value is mapped to the HTTP 253 | /// body of response. Other response fields are ignored. When 254 | /// not set, the response message will be used as HTTP body of response. 255 | #[prost(string, tag = "12")] 256 | pub response_body: ::prost::alloc::string::String, 257 | /// Additional HTTP bindings for the selector. Nested bindings must 258 | /// not contain an `additional_bindings` field themselves (that is, 259 | /// the nesting may only be one level deep). 260 | #[prost(message, repeated, tag = "11")] 261 | pub additional_bindings: ::prost::alloc::vec::Vec, 262 | /// Determines the URL pattern is matched by this rules. This pattern can be 263 | /// used with any of the {get|put|post|delete|patch} methods. A custom method 264 | /// can be defined using the 'custom' field. 265 | #[prost(oneof = "http_rule::Pattern", tags = "2, 3, 4, 5, 6, 8")] 266 | pub pattern: ::core::option::Option, 267 | } 268 | /// Nested message and enum types in `HttpRule`. 269 | pub mod http_rule { 270 | /// Determines the URL pattern is matched by this rules. This pattern can be 271 | /// used with any of the {get|put|post|delete|patch} methods. A custom method 272 | /// can be defined using the 'custom' field. 273 | #[allow(clippy::derive_partial_eq_without_eq)] 274 | #[derive(Clone, PartialEq, ::prost::Oneof)] 275 | pub enum Pattern { 276 | /// Used for listing and getting information about resources. 277 | #[prost(string, tag = "2")] 278 | Get(::prost::alloc::string::String), 279 | /// Used for updating a resource. 280 | #[prost(string, tag = "3")] 281 | Put(::prost::alloc::string::String), 282 | /// Used for creating a resource. 283 | #[prost(string, tag = "4")] 284 | Post(::prost::alloc::string::String), 285 | /// Used for deleting a resource. 286 | #[prost(string, tag = "5")] 287 | Delete(::prost::alloc::string::String), 288 | /// Used for updating a resource. 289 | #[prost(string, tag = "6")] 290 | Patch(::prost::alloc::string::String), 291 | /// The custom pattern is used for specifying an HTTP method that is not 292 | /// included in the `pattern` field, such as HEAD, or "*" to leave the 293 | /// HTTP method unspecified for this rule. The wild-card rule is useful 294 | /// for services that provide content to Web (HTML) clients. 295 | #[prost(message, tag = "8")] 296 | Custom(super::CustomHttpPattern), 297 | } 298 | } 299 | /// A custom pattern is used for defining custom HTTP verb. 300 | #[allow(clippy::derive_partial_eq_without_eq)] 301 | #[derive(Clone, PartialEq, ::prost::Message)] 302 | pub struct CustomHttpPattern { 303 | /// The name of this custom HTTP verb. 304 | #[prost(string, tag = "1")] 305 | pub kind: ::prost::alloc::string::String, 306 | /// The path matched by this custom verb. 307 | #[prost(string, tag = "2")] 308 | pub path: ::prost::alloc::string::String, 309 | } 310 | -------------------------------------------------------------------------------- /src/infra/dynamic/client.rs: -------------------------------------------------------------------------------- 1 | use super::{JsonProtoTransition, PathIndex}; 2 | use hyper::body::Bytes; 3 | use hyper::client::{connect::HttpConnector, Client}; 4 | use hyper::{Body, Method}; 5 | use protobuf::reflect::MessageDescriptor; 6 | use std::collections::HashMap; 7 | 8 | pub struct DynClient { 9 | name: String, 10 | host_port: String, 11 | protocol: String, 12 | client: Client, 13 | format: Box, 14 | // restful: Box, 15 | index: Box, 16 | } 17 | 18 | impl DynClient { 19 | #[allow(dead_code)] 20 | pub fn new(format: J, index: P) -> Self 21 | where 22 | J: JsonProtoTransition + Send + Sync + 'static, 23 | P: PathIndex + Send + Sync + 'static, 24 | { 25 | let name = "dyn-grpc-client".into(); 26 | let client = hyper::Client::builder().http2_only(true).build_http(); 27 | let format = Box::new(format); 28 | let index = Box::new(index); 29 | let host_port = String::from("127.0.0.1:443"); 30 | let protocol = J::protocol(); 31 | Self { 32 | name, 33 | host_port, 34 | protocol, 35 | client, 36 | format, 37 | index, 38 | } 39 | } 40 | #[allow(dead_code)] 41 | pub fn set_host_port>(mut self, host_post: T) -> Self { 42 | self.host_port = host_post.into(); 43 | self 44 | } 45 | #[allow(dead_code)] 46 | pub fn name(&self) -> String { 47 | self.name.clone() 48 | } 49 | #[allow(dead_code)] 50 | pub fn set_name(mut self, name: T) -> Self { 51 | self.name = name.to_string(); 52 | self 53 | } 54 | #[allow(dead_code)] 55 | pub fn method_list(&self) -> Vec<(Method, String, String)> { 56 | self.index.list() 57 | } 58 | #[allow(dead_code)] 59 | fn error>>(s: T) -> anyhow::Result<(HashMap, Vec)> { 60 | return Ok((HashMap::new(), s.into())); 61 | } 62 | } 63 | 64 | // #[async_trait::async_trait] 65 | impl DynClient { 66 | #[allow(dead_code)] 67 | pub async fn invoke( 68 | &self, 69 | method: Method, 70 | path: String, 71 | metadata: HashMap, 72 | body: Vec, 73 | extend: Option>, 74 | ) -> anyhow::Result<(HashMap, Vec)> { 75 | let (grpc_path, desc, restful) = if let Some(o) = self.index.search(method, path) { 76 | o 77 | } else { 78 | return DynClient::error("not found"); 79 | }; 80 | let extend = if let Some(mut mp) = extend { 81 | if let Some(rf) = restful { 82 | for (k, v) in rf { 83 | mp.insert(k, v); 84 | } 85 | } 86 | Some(mp) 87 | } else { 88 | restful 89 | }; 90 | let body = self.json_request_to_grpc(body, desc.input_type(), extend)?; 91 | let (status, md, resp_body) = self.do_grpc_request(grpc_path, metadata, body).await?; 92 | if status != 200 { 93 | let resp_result = String::from_utf8_lossy(resp_body.to_vec().as_slice()).to_string(); 94 | return DynClient::error(format!("status:{} error:{}", status, resp_result)); 95 | } 96 | let resp_json_body = self.grpc_response_to_json(resp_body, desc.output_type())?; 97 | return Ok((md, resp_json_body)); 98 | } 99 | 100 | pub fn json_request_to_grpc( 101 | &self, 102 | // path: String, 103 | body: Vec, 104 | desc: MessageDescriptor, 105 | extend: Option>, 106 | ) -> anyhow::Result> { 107 | // let ps = self.restful.path(path); 108 | let body = self.format.json_to_proto(body, desc, extend)?; 109 | Ok(body) 110 | // let mut buf = vec![0]; 111 | // let mut len = (body.len() as u32).to_be_bytes().to_vec(); 112 | // buf.append(&mut len); 113 | // buf.append(&mut body); 114 | // return Ok(buf) 115 | } 116 | pub fn grpc_response_to_json( 117 | &self, 118 | body: Bytes, 119 | desc: MessageDescriptor, 120 | ) -> anyhow::Result> { 121 | if body.is_empty() { 122 | return Err(anyhow::anyhow!("response is nil")); 123 | } 124 | // body.advance(1); 125 | // let len = body.get_u32(); 126 | // let buf = body.split_to(len as usize); 127 | let buf = body.to_vec(); 128 | self.format.proto_to_json(buf, desc) 129 | } 130 | pub async fn do_grpc_request( 131 | &self, 132 | grpc_path: String, 133 | metadata: HashMap, 134 | body: Vec, 135 | ) -> anyhow::Result<(u16, HashMap, Bytes)> { 136 | let url = format!("http://{}/{}", self.host_port, grpc_path); 137 | // wd_log::log_debug_ln!("do_grpc_request url:{}",url); 138 | let mut req = hyper::Request::builder() 139 | .version(hyper::Version::HTTP_2) 140 | .method(Method::POST) 141 | .header( 142 | "content-type", 143 | format!("application/grpc+{}", &self.protocol), 144 | ) 145 | .uri(url.as_str()); 146 | for (k, v) in metadata.into_iter() { 147 | req = req.header(k, v); 148 | } 149 | let req = req.body(Body::from(body))?; 150 | let resp = self.client.request(req).await?; 151 | let status = resp.status().as_u16(); 152 | let mut metadata = HashMap::new(); 153 | for (k, v) in resp.headers() { 154 | let value = match v.to_str() { 155 | Ok(o) => o.to_string(), 156 | Err(e) => { 157 | wd_log::log_error_ln!( 158 | "Url:[{}] do_grpc_request metadata parse failed:{}", 159 | url, 160 | e 161 | ); 162 | continue; 163 | } 164 | }; 165 | metadata.insert(k.to_string(), value); 166 | } 167 | let body = hyper::body::to_bytes(resp.into_body()).await?; 168 | return Ok((status, metadata, body)); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/infra/dynamic/compressed_dictionary_tree.rs: -------------------------------------------------------------------------------- 1 | //todo 压缩字典树来实现索引功能 类似gin框架的索引结构 2 | -------------------------------------------------------------------------------- /src/infra/dynamic/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod anno; 2 | mod client; 3 | mod compressed_dictionary_tree; 4 | mod simple_index; 5 | mod transition; 6 | 7 | pub use client::DynClient; 8 | pub use simple_index::SimpleIndex; 9 | pub use transition::JsonProtoTransitionDefaultImpl; 10 | 11 | use hyper::Method; 12 | use protobuf::reflect::{MessageDescriptor, MethodDescriptor}; 13 | use std::collections::HashMap; 14 | use std::sync::Arc; 15 | 16 | pub trait JsonProtoTransition { 17 | fn protocol() -> String 18 | where 19 | Self: Sized, 20 | { 21 | "proto".into() 22 | } //proto json ... 23 | fn json_to_proto( 24 | &self, 25 | data: Vec, 26 | pt: MessageDescriptor, 27 | opt: Option>, 28 | ) -> anyhow::Result>; 29 | fn proto_to_json(&self, data: Vec, pt: MessageDescriptor) -> anyhow::Result>; 30 | } 31 | 32 | pub trait PathIndex { 33 | fn search( 34 | &self, 35 | method: Method, 36 | path: String, 37 | ) -> Option<( 38 | String, 39 | Arc, 40 | Option>, 41 | )>; //返回grpc路径 package.services/method 42 | fn list(&self) -> Vec<(Method, String, String)> { 43 | vec![] 44 | } 45 | } 46 | 47 | pub trait RestfulTransition { 48 | //grpc option中 restful语法的支持 49 | #[allow(unused_variables)] 50 | fn path(&self, path: String) -> Option> { 51 | return None; 52 | } 53 | } 54 | 55 | pub struct RestfulTransitionDefaultImpl; 56 | impl RestfulTransition for RestfulTransitionDefaultImpl {} 57 | -------------------------------------------------------------------------------- /src/infra/dynamic/simple_index.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::dynamic::anno::http_rule::Pattern; 2 | use crate::infra::dynamic::anno::HttpRule; 3 | use crate::infra::dynamic::PathIndex; 4 | use hyper::Method; 5 | use protobuf::reflect::{MethodDescriptor, ServiceDescriptor}; 6 | use protobuf::UnknownValueRef; 7 | use std::collections::HashMap; 8 | use std::sync::Arc; 9 | 10 | struct Node { 11 | method: Method, 12 | http_path: String, 13 | http_path_list: Vec, 14 | http_path_match: Vec, 15 | grpc_path: String, 16 | desc: Arc, 17 | } 18 | 19 | impl Node { 20 | // pub fn path_match( 21 | // &self, 22 | // method: &Method, 23 | // path: &String, 24 | // ) -> Option<(String, Arc)> { 25 | // if self.http_path.eq(path) && self.method.eq(method) { 26 | // Some((self.grpc_path.clone(), self.desc.clone())) 27 | // } else { 28 | // None 29 | // } 30 | // } 31 | 32 | pub fn path_match_restful( 33 | &self, 34 | method: &Method, 35 | path: &Vec<&str>, 36 | ) -> Option<( 37 | String, 38 | Arc, 39 | Option>, 40 | )> { 41 | if method != self.method { 42 | return None; 43 | } 44 | let mut opt: Option> = None; 45 | for (i, v) in self.http_path_match.iter().enumerate() { 46 | let p = if let Some(p) = path.get(i) { 47 | *p 48 | } else { 49 | return None; 50 | }; 51 | if v.as_str() == "*" { 52 | let key = self.http_path_list[i].clone(); 53 | let value = p.to_string(); 54 | if let Some(ref mut mp) = &mut opt { 55 | mp.insert(key, value); 56 | } else { 57 | let mp = HashMap::from([(key, value)]); 58 | opt = Some(mp); 59 | } 60 | } else if v != p { 61 | return None; 62 | } 63 | } 64 | return Some((self.grpc_path.clone(), self.desc.clone(), opt)); 65 | } 66 | 67 | //SimpleIndex 只是一个简单实现的路由模块,所有此处魔数硬编码 详见如下文档 68 | //https://github.com/googleapis/googleapis/blob/master/google/api/annotations.proto 69 | #[allow(dead_code)] 70 | fn from_method_descriptor( 71 | service_name: String, 72 | desc: MethodDescriptor, 73 | ) -> anyhow::Result> { 74 | let method_name = desc.proto().name.clone().unwrap_or(String::from("none")); 75 | let option = if let Some(s) = desc.proto().options.as_ref() { 76 | s 77 | } else { 78 | return Ok(None); 79 | }; 80 | let value = match option.special_fields.unknown_fields().get(72295728) { 81 | // UnknownValueRef::LengthDelimited(b) => b, 82 | // _=>return None, 83 | None => return Ok(None), 84 | Some(s) => s, 85 | }; 86 | let value = match value { 87 | UnknownValueRef::LengthDelimited(s) => s, 88 | _ => return Ok(None), 89 | }; 90 | let hp: HttpRule = prost::Message::decode(value)?; 91 | Ok(Node::from_http_rule(service_name, method_name, hp, desc)) 92 | } 93 | #[allow(dead_code)] 94 | fn from_http_rule( 95 | service_name: String, 96 | method_name: String, 97 | hp: HttpRule, 98 | desc: MethodDescriptor, 99 | ) -> Option { 100 | let (method, http_path) = match hp.pattern? { 101 | Pattern::Get(p) => (Method::GET, p), 102 | Pattern::Put(p) => (Method::PUT, p), 103 | Pattern::Post(p) => (Method::POST, p), 104 | Pattern::Delete(p) => (Method::DELETE, p), 105 | Pattern::Patch(p) => (Method::PATCH, p), 106 | Pattern::Custom(_) => return None, //SimpleIndex 暂时不支持自定义方法名 107 | }; 108 | let mut http_path_list = vec![]; 109 | let mut http_path_match = vec![]; 110 | let list: Vec<&str> = http_path.split('/').collect(); 111 | for i in list.into_iter() { 112 | if i.starts_with('{') && i.ends_with('}') { 113 | let filters: &[_] = &['{', '}']; 114 | http_path_match.push("*".into()); 115 | http_path_list.push(i.trim_matches(filters).to_string()); 116 | } else { 117 | http_path_match.push(i.to_string()); 118 | http_path_list.push(i.into()); 119 | } 120 | } 121 | let grpc_path = format!("{}/{}", service_name, method_name); 122 | let desc = Arc::new(desc); 123 | wd_log::log_debug_ln!("[grpc] {} ---> http: [{}] {}",grpc_path,method,http_path); 124 | Some(Self { 125 | method, 126 | http_path, 127 | http_path_list, 128 | http_path_match, 129 | grpc_path, 130 | desc, 131 | }) 132 | } 133 | } 134 | 135 | //简单的路径分类工具 136 | //暂时不支持restful 137 | pub struct SimpleIndex { 138 | nodes: Vec, 139 | } 140 | 141 | impl SimpleIndex { 142 | #[allow(dead_code)] 143 | fn new() -> Self { 144 | Self { nodes: vec![] } 145 | } 146 | #[allow(dead_code)] 147 | fn append_node(&mut self, node: Node) { 148 | self.nodes.push(node); 149 | } 150 | } 151 | 152 | impl SimpleIndex { 153 | //解析 154 | #[allow(dead_code)] 155 | pub(crate) fn parse(mp: HashMap) -> anyhow::Result { 156 | let mut index = Self::new(); 157 | for (service, desc) in mp.into_iter() { 158 | for i in desc.methods() { 159 | if let Some(node) = Node::from_method_descriptor(service.clone(), i)? { 160 | index.append_node(node); 161 | } 162 | } 163 | } 164 | Ok(index) 165 | } 166 | } 167 | 168 | impl PathIndex for SimpleIndex { 169 | fn search( 170 | &self, 171 | method: Method, 172 | path: String, 173 | ) -> Option<( 174 | String, 175 | Arc, 176 | Option>, 177 | )> { 178 | let path_list: Vec<&str> = path.split('/').collect(); 179 | for n in self.nodes.iter() { 180 | if let Some(s) = n.path_match_restful(&method, &path_list) { 181 | return Some(s); 182 | } 183 | } 184 | None 185 | } 186 | 187 | fn list(&self) -> Vec<(Method, String, String)> { 188 | let mut list = vec![]; 189 | for i in self.nodes.iter() { 190 | let node = (i.method.clone(), i.http_path.clone(), i.grpc_path.clone()); 191 | list.push(node); 192 | } 193 | list 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/infra/dynamic/transition.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::dynamic::JsonProtoTransition; 2 | use protobuf::descriptor::field_descriptor_proto::Type; 3 | use protobuf::reflect::{MessageDescriptor, ReflectValueBox}; 4 | use std::collections::HashMap; 5 | use protobuf_json_mapping::PrintOptions; 6 | 7 | pub struct JsonProtoTransitionDefaultImpl; 8 | 9 | impl JsonProtoTransition for JsonProtoTransitionDefaultImpl { 10 | fn json_to_proto( 11 | &self, 12 | data: Vec, 13 | pt: MessageDescriptor, 14 | opt: Option>, 15 | ) -> anyhow::Result> { 16 | let s = String::from_utf8(data)?; 17 | let mut message_dyn = if s.is_empty() { 18 | pt.new_instance() 19 | } else { 20 | protobuf_json_mapping::parse_dyn_from_str(&pt, s.as_str())? 21 | }; 22 | 23 | //将opt组装进去 24 | if let Some(mp) = opt { 25 | for (k, v) in mp.into_iter() { 26 | if let Some(field) = pt.field_by_name(k.as_str()) { 27 | let value = match field.proto().type_() { 28 | Type::TYPE_DOUBLE => ReflectValueBox::F64(v.parse().unwrap_or(0f64)), 29 | Type::TYPE_FLOAT => ReflectValueBox::F32(v.parse().unwrap_or(0f32)), 30 | Type::TYPE_INT64 => ReflectValueBox::I64(v.parse().unwrap_or(0i64)), 31 | Type::TYPE_UINT64 => ReflectValueBox::U64(v.parse().unwrap_or(0u64)), 32 | Type::TYPE_INT32 => ReflectValueBox::I32(v.parse().unwrap_or(0i32)), 33 | Type::TYPE_FIXED64 => ReflectValueBox::I64(v.parse().unwrap_or(0i64)), 34 | Type::TYPE_FIXED32 => ReflectValueBox::I32(v.parse().unwrap_or(0i32)), 35 | Type::TYPE_BOOL => ReflectValueBox::Bool(v.parse().unwrap_or(false)), 36 | Type::TYPE_STRING => ReflectValueBox::String(v), 37 | // Type::TYPE_GROUP =>{}, 38 | // Type::TYPE_MESSAGE =>{}, 39 | Type::TYPE_BYTES => ReflectValueBox::Bytes(v.into_bytes()), 40 | Type::TYPE_UINT32 => ReflectValueBox::U32(v.parse().unwrap_or(0u32)), 41 | // Type::TYPE_ENUM =>{}, 42 | // Type::TYPE_SFIXED32 =>{}, 43 | // Type::TYPE_SFIXED64 => {}, 44 | Type::TYPE_SINT32 => ReflectValueBox::I32(v.parse().unwrap_or(0i32)), 45 | Type::TYPE_SINT64 => ReflectValueBox::I64(v.parse().unwrap_or(0i64)), 46 | _ => continue, 47 | }; 48 | // wd_log::log_debug_ln!("set_singular_field {}:{:?}",field.name(),value); 49 | field.set_singular_field(&mut *message_dyn, value); 50 | } 51 | } 52 | } 53 | let mut body = message_dyn.write_to_bytes_dyn()?; 54 | //组装 55 | let mut buf = vec![0]; 56 | let mut len = (body.len() as u32).to_be_bytes().to_vec(); 57 | buf.append(&mut len); 58 | buf.append(&mut body); 59 | return Ok(buf); 60 | } 61 | 62 | fn proto_to_json(&self, data: Vec, pt: MessageDescriptor) -> anyhow::Result> { 63 | if data.len() < 5 { 64 | return Err(anyhow::anyhow!("proto_to_json: data len < 5")); 65 | } 66 | let msg = pt.parse_from_bytes(&data[5..])?; 67 | let s = protobuf_json_mapping::print_to_string_with_options(&*msg,&PrintOptions{proto_field_name:true,..Default::default()})?; 68 | Ok(s.into_bytes()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/infra/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dynamic; 2 | pub mod profiler; 3 | pub mod server; 4 | -------------------------------------------------------------------------------- /src/infra/profiler/mod.rs: -------------------------------------------------------------------------------- 1 | mod reflect; 2 | mod reflect_file_desc; 3 | mod services_desc_assembler; 4 | 5 | pub use reflect::*; 6 | pub use reflect_file_desc::FileDescProfiler; 7 | pub use services_desc_assembler::ServiceDescriptorAssemblerDefaultImpl; 8 | 9 | use protobuf::descriptor::FileDescriptorProto; 10 | use protobuf::reflect::ServiceDescriptor; 11 | use std::collections::HashMap; 12 | 13 | pub trait ServicesFilter { 14 | fn filter(&self, _: &mut Vec); 15 | } 16 | 17 | pub trait ServiceDescriptorAssembler { 18 | fn assemble( 19 | &self, 20 | input: HashMap>, 21 | deps : Vec>, 22 | ) -> anyhow::Result>; 23 | } 24 | 25 | pub struct ServicesFilterDefaultImpl; 26 | impl ServicesFilter for ServicesFilterDefaultImpl { 27 | fn filter(&self, services: &mut Vec) { 28 | let mut i = 0; 29 | loop { 30 | if let Some(s) = services.get(i) { 31 | if s.starts_with("grpc.") { 32 | services.remove(i); 33 | } else { 34 | i += 1 35 | } 36 | } else { 37 | break; 38 | } 39 | } 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod test { 45 | use crate::infra::profiler::FileDescProfiler; 46 | 47 | #[tokio::test(flavor = "multi_thread", worker_threads = 1)] 48 | async fn test_file_desc_profiler() { 49 | let profiler = FileDescProfiler::new(); 50 | let result = profiler.reflect("127.0.0.1:666".into()).await; 51 | if let Err(e) = result { 52 | wd_log::log_error_ln!("profiler.reflect 127.0.0.1:666 error:{}", e); 53 | return; 54 | } 55 | wd_log::log_debug_ln!("---> profiler.reflect success start show"); 56 | for (k, v) in result.unwrap().into_iter() { 57 | wd_log::log_info_ln!("service:{} => desc:{}", k, v.proto().name.as_ref().unwrap()); 58 | for i in v.methods() { 59 | wd_log::log_info_ln!( 60 | " method:{} input:{} output:{}", 61 | i.proto().name.as_ref().unwrap(), 62 | i.input_type().name(), 63 | i.output_type().name() 64 | ); 65 | // wd_log::log_info_ln!(" method:{} option:{}",i.proto().name.as_ref().unwrap(),i.proto().options.to_string()); 66 | } 67 | } 68 | wd_log::log_debug_ln!("---> over"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/infra/profiler/reflect.rs: -------------------------------------------------------------------------------- 1 | /// The message sent by the client when calling ServerReflectionInfo method. 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct ServerReflectionRequest { 5 | #[prost(string, tag = "1")] 6 | pub host: ::prost::alloc::string::String, 7 | /// To use reflection service, the client should set one of the following 8 | /// fields in message_request. The server distinguishes requests by their 9 | /// defined field and then handles them using corresponding methods. 10 | #[prost( 11 | oneof = "server_reflection_request::MessageRequest", 12 | tags = "3, 4, 5, 6, 7" 13 | )] 14 | pub message_request: ::core::option::Option, 15 | } 16 | /// Nested message and enum types in `ServerReflectionRequest`. 17 | pub mod server_reflection_request { 18 | /// To use reflection service, the client should set one of the following 19 | /// fields in message_request. The server distinguishes requests by their 20 | /// defined field and then handles them using corresponding methods. 21 | #[allow(clippy::derive_partial_eq_without_eq)] 22 | #[derive(Clone, PartialEq, ::prost::Oneof)] 23 | pub enum MessageRequest { 24 | /// Find a proto file by the file name. 25 | #[prost(string, tag = "3")] 26 | FileByFilename(::prost::alloc::string::String), 27 | /// Find the proto file that declares the given fully-qualified symbol name. 28 | /// This field should be a fully-qualified symbol name 29 | /// (e.g. .\[.\] or .). 30 | #[prost(string, tag = "4")] 31 | FileContainingSymbol(::prost::alloc::string::String), 32 | /// Find the proto file which defines an extension extending the given 33 | /// message type with the given field number. 34 | #[prost(message, tag = "5")] 35 | FileContainingExtension(super::ExtensionRequest), 36 | /// Finds the tag numbers used by all known extensions of the given message 37 | /// type, and appends them to ExtensionNumberResponse in an undefined order. 38 | /// Its corresponding method is best-effort: it's not guaranteed that the 39 | /// reflection service will implement this method, and it's not guaranteed 40 | /// that this method will provide all extensions. Returns 41 | /// StatusCode::UNIMPLEMENTED if it's not implemented. 42 | /// This field should be a fully-qualified type name. The format is 43 | /// . 44 | #[prost(string, tag = "6")] 45 | AllExtensionNumbersOfType(::prost::alloc::string::String), 46 | /// List the full names of registered services. The content will not be 47 | /// checked. 48 | #[prost(string, tag = "7")] 49 | ListServices(::prost::alloc::string::String), 50 | } 51 | } 52 | /// The type name and extension number sent by the client when requesting 53 | /// file_containing_extension. 54 | #[allow(clippy::derive_partial_eq_without_eq)] 55 | #[derive(Clone, PartialEq, ::prost::Message)] 56 | pub struct ExtensionRequest { 57 | /// Fully-qualified type name. The format should be . 58 | #[prost(string, tag = "1")] 59 | pub containing_type: ::prost::alloc::string::String, 60 | #[prost(int32, tag = "2")] 61 | pub extension_number: i32, 62 | } 63 | /// The message sent by the server to answer ServerReflectionInfo method. 64 | #[allow(clippy::derive_partial_eq_without_eq)] 65 | #[derive(Clone, PartialEq, ::prost::Message)] 66 | pub struct ServerReflectionResponse { 67 | #[prost(string, tag = "1")] 68 | pub valid_host: ::prost::alloc::string::String, 69 | #[prost(message, optional, tag = "2")] 70 | pub original_request: ::core::option::Option, 71 | /// The server sets one of the following fields according to the message_request 72 | /// in the request. 73 | #[prost( 74 | oneof = "server_reflection_response::MessageResponse", 75 | tags = "4, 5, 6, 7" 76 | )] 77 | pub message_response: ::core::option::Option, 78 | } 79 | /// Nested message and enum types in `ServerReflectionResponse`. 80 | pub mod server_reflection_response { 81 | /// The server sets one of the following fields according to the message_request 82 | /// in the request. 83 | #[allow(clippy::derive_partial_eq_without_eq)] 84 | #[derive(Clone, PartialEq, ::prost::Oneof)] 85 | pub enum MessageResponse { 86 | /// This message is used to answer file_by_filename, file_containing_symbol, 87 | /// file_containing_extension requests with transitive dependencies. 88 | /// As the repeated label is not allowed in oneof fields, we use a 89 | /// FileDescriptorResponse message to encapsulate the repeated fields. 90 | /// The reflection service is allowed to avoid sending FileDescriptorProtos 91 | /// that were previously sent in response to earlier requests in the stream. 92 | #[prost(message, tag = "4")] 93 | FileDescriptorResponse(super::FileDescriptorResponse), 94 | /// This message is used to answer all_extension_numbers_of_type requests. 95 | #[prost(message, tag = "5")] 96 | AllExtensionNumbersResponse(super::ExtensionNumberResponse), 97 | /// This message is used to answer list_services requests. 98 | #[prost(message, tag = "6")] 99 | ListServicesResponse(super::ListServiceResponse), 100 | /// This message is used when an error occurs. 101 | #[prost(message, tag = "7")] 102 | ErrorResponse(super::ErrorResponse), 103 | } 104 | } 105 | /// Serialized FileDescriptorProto messages sent by the server answering 106 | /// a file_by_filename, file_containing_symbol, or file_containing_extension 107 | /// request. 108 | #[allow(clippy::derive_partial_eq_without_eq)] 109 | #[derive(Clone, PartialEq, ::prost::Message)] 110 | pub struct FileDescriptorResponse { 111 | /// Serialized FileDescriptorProto messages. We avoid taking a dependency on 112 | /// descriptor.proto, which uses proto2 only features, by making them opaque 113 | /// bytes instead. 114 | #[prost(bytes = "vec", repeated, tag = "1")] 115 | pub file_descriptor_proto: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, 116 | } 117 | /// A list of extension numbers sent by the server answering 118 | /// all_extension_numbers_of_type request. 119 | #[allow(clippy::derive_partial_eq_without_eq)] 120 | #[derive(Clone, PartialEq, ::prost::Message)] 121 | pub struct ExtensionNumberResponse { 122 | /// Full name of the base type, including the package name. The format 123 | /// is . 124 | #[prost(string, tag = "1")] 125 | pub base_type_name: ::prost::alloc::string::String, 126 | #[prost(int32, repeated, tag = "2")] 127 | pub extension_number: ::prost::alloc::vec::Vec, 128 | } 129 | /// A list of ServiceResponse sent by the server answering list_services request. 130 | #[allow(clippy::derive_partial_eq_without_eq)] 131 | #[derive(Clone, PartialEq, ::prost::Message)] 132 | pub struct ListServiceResponse { 133 | /// The information of each service may be expanded in the future, so we use 134 | /// ServiceResponse message to encapsulate it. 135 | #[prost(message, repeated, tag = "1")] 136 | pub service: ::prost::alloc::vec::Vec, 137 | } 138 | /// The information of a single service used by ListServiceResponse to answer 139 | /// list_services request. 140 | #[allow(clippy::derive_partial_eq_without_eq)] 141 | #[derive(Clone, PartialEq, ::prost::Message)] 142 | pub struct ServiceResponse { 143 | /// Full name of a registered service, including its package name. The format 144 | /// is . 145 | #[prost(string, tag = "1")] 146 | pub name: ::prost::alloc::string::String, 147 | } 148 | /// The error code and error message sent by the server when an error occurs. 149 | #[allow(clippy::derive_partial_eq_without_eq)] 150 | #[derive(Clone, PartialEq, ::prost::Message)] 151 | pub struct ErrorResponse { 152 | /// This field uses the error codes defined in grpc::StatusCode. 153 | #[prost(int32, tag = "1")] 154 | pub error_code: i32, 155 | #[prost(string, tag = "2")] 156 | pub error_message: ::prost::alloc::string::String, 157 | } 158 | /// Generated client implementations. 159 | pub mod server_reflection_client { 160 | #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 161 | use tonic::codegen::http::Uri; 162 | use tonic::codegen::*; 163 | #[derive(Debug, Clone)] 164 | pub struct ServerReflectionClient { 165 | inner: tonic::client::Grpc, 166 | } 167 | impl ServerReflectionClient { 168 | /// Attempt to create a new client by connecting to a given endpoint. 169 | pub async fn connect(dst: D) -> Result 170 | where 171 | D: std::convert::TryInto, 172 | D::Error: Into, 173 | { 174 | let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 175 | Ok(Self::new(conn)) 176 | } 177 | } 178 | impl ServerReflectionClient 179 | where 180 | T: tonic::client::GrpcService, 181 | T::Error: Into, 182 | T::ResponseBody: Body + Send + 'static, 183 | ::Error: Into + Send, 184 | { 185 | pub fn new(inner: T) -> Self { 186 | let inner = tonic::client::Grpc::new(inner); 187 | Self { inner } 188 | } 189 | pub fn with_origin(inner: T, origin: Uri) -> Self { 190 | let inner = tonic::client::Grpc::with_origin(inner, origin); 191 | Self { inner } 192 | } 193 | pub fn with_interceptor( 194 | inner: T, 195 | interceptor: F, 196 | ) -> ServerReflectionClient> 197 | where 198 | F: tonic::service::Interceptor, 199 | T::ResponseBody: Default, 200 | T: tonic::codegen::Service< 201 | http::Request, 202 | Response = http::Response< 203 | >::ResponseBody, 204 | >, 205 | >, 206 | >>::Error: 207 | Into + Send + Sync, 208 | { 209 | ServerReflectionClient::new(InterceptedService::new(inner, interceptor)) 210 | } 211 | /// Compress requests with the given encoding. 212 | /// 213 | /// This requires the server to support it otherwise it might respond with an 214 | /// error. 215 | #[must_use] 216 | pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 217 | self.inner = self.inner.send_compressed(encoding); 218 | self 219 | } 220 | /// Enable decompressing responses. 221 | #[must_use] 222 | pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 223 | self.inner = self.inner.accept_compressed(encoding); 224 | self 225 | } 226 | /// The reflection service is structured as a bidirectional stream, ensuring 227 | /// all related requests go to a single server. 228 | pub async fn server_reflection_info( 229 | &mut self, 230 | request: impl tonic::IntoStreamingRequest, 231 | ) -> Result< 232 | tonic::Response>, 233 | tonic::Status, 234 | > { 235 | self.inner.ready().await.map_err(|e| { 236 | tonic::Status::new( 237 | tonic::Code::Unknown, 238 | format!("Service was not ready: {}", e.into()), 239 | ) 240 | })?; 241 | let codec = tonic::codec::ProstCodec::default(); 242 | let path = http::uri::PathAndQuery::from_static( 243 | "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo", 244 | ); 245 | self.inner 246 | .streaming(request.into_streaming_request(), path, codec) 247 | .await 248 | } 249 | } 250 | } 251 | /// Generated server implementations. 252 | pub mod server_reflection_server { 253 | #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 254 | use tonic::codegen::*; 255 | /// Generated trait containing gRPC methods that should be implemented for use with ServerReflectionServer. 256 | #[async_trait] 257 | pub trait ServerReflection: Send + Sync + 'static { 258 | /// Server streaming response type for the ServerReflectionInfo method. 259 | type ServerReflectionInfoStream: futures_core::Stream> 260 | + Send 261 | + 'static; 262 | /// The reflection service is structured as a bidirectional stream, ensuring 263 | /// all related requests go to a single server. 264 | async fn server_reflection_info( 265 | &self, 266 | request: tonic::Request>, 267 | ) -> Result, tonic::Status>; 268 | } 269 | #[derive(Debug)] 270 | pub struct ServerReflectionServer { 271 | inner: _Inner, 272 | accept_compression_encodings: EnabledCompressionEncodings, 273 | send_compression_encodings: EnabledCompressionEncodings, 274 | } 275 | struct _Inner(Arc); 276 | impl ServerReflectionServer { 277 | pub fn new(inner: T) -> Self { 278 | Self::from_arc(Arc::new(inner)) 279 | } 280 | pub fn from_arc(inner: Arc) -> Self { 281 | let inner = _Inner(inner); 282 | Self { 283 | inner, 284 | accept_compression_encodings: Default::default(), 285 | send_compression_encodings: Default::default(), 286 | } 287 | } 288 | pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService 289 | where 290 | F: tonic::service::Interceptor, 291 | { 292 | InterceptedService::new(Self::new(inner), interceptor) 293 | } 294 | /// Enable decompressing requests with the given encoding. 295 | #[must_use] 296 | pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 297 | self.accept_compression_encodings.enable(encoding); 298 | self 299 | } 300 | /// Compress responses with the given encoding, if the client supports it. 301 | #[must_use] 302 | pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 303 | self.send_compression_encodings.enable(encoding); 304 | self 305 | } 306 | } 307 | impl tonic::codegen::Service> for ServerReflectionServer 308 | where 309 | T: ServerReflection, 310 | B: Body + Send + 'static, 311 | B::Error: Into + Send + 'static, 312 | { 313 | type Response = http::Response; 314 | type Error = std::convert::Infallible; 315 | type Future = BoxFuture; 316 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 317 | Poll::Ready(Ok(())) 318 | } 319 | fn call(&mut self, req: http::Request) -> Self::Future { 320 | let inner = self.inner.clone(); 321 | match req.uri().path() { 322 | "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo" => { 323 | #[allow(non_camel_case_types)] 324 | struct ServerReflectionInfoSvc(pub Arc); 325 | impl 326 | tonic::server::StreamingService 327 | for ServerReflectionInfoSvc 328 | { 329 | type Response = super::ServerReflectionResponse; 330 | type ResponseStream = T::ServerReflectionInfoStream; 331 | type Future = 332 | BoxFuture, tonic::Status>; 333 | fn call( 334 | &mut self, 335 | request: tonic::Request< 336 | tonic::Streaming, 337 | >, 338 | ) -> Self::Future { 339 | let inner = self.0.clone(); 340 | let fut = async move { (*inner).server_reflection_info(request).await }; 341 | Box::pin(fut) 342 | } 343 | } 344 | let accept_compression_encodings = self.accept_compression_encodings; 345 | let send_compression_encodings = self.send_compression_encodings; 346 | let inner = self.inner.clone(); 347 | let fut = async move { 348 | let inner = inner.0; 349 | let method = ServerReflectionInfoSvc(inner); 350 | let codec = tonic::codec::ProstCodec::default(); 351 | let mut grpc = tonic::server::Grpc::new(codec).apply_compression_config( 352 | accept_compression_encodings, 353 | send_compression_encodings, 354 | ); 355 | let res = grpc.streaming(method, req).await; 356 | Ok(res) 357 | }; 358 | Box::pin(fut) 359 | } 360 | _ => Box::pin(async move { 361 | Ok(http::Response::builder() 362 | .status(200) 363 | .header("grpc-status", "12") 364 | .header("content-type", "application/grpc") 365 | .body(empty_body()) 366 | .unwrap()) 367 | }), 368 | } 369 | } 370 | } 371 | impl Clone for ServerReflectionServer { 372 | fn clone(&self) -> Self { 373 | let inner = self.inner.clone(); 374 | Self { 375 | inner, 376 | accept_compression_encodings: self.accept_compression_encodings, 377 | send_compression_encodings: self.send_compression_encodings, 378 | } 379 | } 380 | } 381 | impl Clone for _Inner { 382 | fn clone(&self) -> Self { 383 | Self(self.0.clone()) 384 | } 385 | } 386 | impl std::fmt::Debug for _Inner { 387 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 388 | write!(f, "{:?}", self.0) 389 | } 390 | } 391 | impl tonic::server::NamedService for ServerReflectionServer { 392 | const NAME: &'static str = "grpc.reflection.v1alpha.ServerReflection"; 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/infra/profiler/reflect_file_desc.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::profiler::server_reflection_client::ServerReflectionClient; 2 | use crate::infra::profiler::server_reflection_request::MessageRequest; 3 | use crate::infra::profiler::server_reflection_response::MessageResponse; 4 | use crate::infra::profiler::services_desc_assembler::ServiceDescriptorAssemblerDefaultImpl; 5 | use crate::infra::profiler::{ 6 | ServerReflectionRequest, ServiceDescriptorAssembler, ServicesFilter, ServicesFilterDefaultImpl, 7 | }; 8 | use protobuf::descriptor::FileDescriptorProto; 9 | use protobuf::reflect::ServiceDescriptor; 10 | use protobuf::Message; 11 | use std::collections::HashMap; 12 | use tokio_stream::StreamExt; 13 | use tonic::transport::Channel; 14 | 15 | pub struct FileDescProfiler { 16 | services_filter: Vec>, 17 | services_desc_assemble: Box, 18 | } 19 | 20 | impl FileDescProfiler { 21 | #[allow(dead_code)] 22 | pub fn new() -> Self { 23 | let services_filter: Vec> = 24 | vec![Box::new(ServicesFilterDefaultImpl)]; 25 | let services_desc_assemble = Box::new(ServiceDescriptorAssemblerDefaultImpl); 26 | Self { 27 | services_filter, 28 | services_desc_assemble, 29 | } 30 | } 31 | #[allow(dead_code)] 32 | pub fn set_services_filter< 33 | F: FnOnce(&mut Vec>), 34 | >( 35 | mut self, 36 | function: F, 37 | ) -> Self { 38 | function(&mut self.services_filter); 39 | self 40 | } 41 | #[allow(dead_code)] 42 | pub fn set_assemble( 43 | mut self, 44 | assembler: S, 45 | ) -> Self { 46 | self.services_desc_assemble = Box::new(assembler); 47 | self 48 | } 49 | } 50 | 51 | impl FileDescProfiler { 52 | //reflect grpc desc 53 | #[allow(dead_code)] 54 | pub async fn reflect( 55 | &self, 56 | server: String, 57 | ) -> anyhow::Result> { 58 | let mut client = ServerReflectionClient::connect(format!("http://{}", &server)).await?; 59 | // wd_log::log_debug_ln!("FileDescProfiler: connect target:{} success",server); 60 | let mut services = FileDescProfiler::get_service_list(&mut client).await?; 61 | for i in self.services_filter.iter() { 62 | i.filter(&mut services); 63 | } 64 | wd_log::log_debug_ln!("[{}] reflect success --> {:?}", &server, services); 65 | let mut map = FileDescProfiler::get_file_desc_by_services(&mut client, services).await?; 66 | let deps = FileDescProfiler::add_dependency_to_services(&mut client,&mut map).await?; 67 | let services = self.services_desc_assemble.assemble(map,deps)?; 68 | Ok(services) 69 | } 70 | #[allow(dead_code)] 71 | pub async fn map(&self, server: String, f: F) -> anyhow::Result 72 | where 73 | F: Fn(HashMap) -> anyhow::Result + Send, 74 | T: Send, 75 | { 76 | let list = self.reflect(server).await?; 77 | f(list) 78 | } 79 | } 80 | impl FileDescProfiler { 81 | async fn get_service_list( 82 | client: &mut ServerReflectionClient, 83 | ) -> anyhow::Result> { 84 | let req_stream = tokio_stream::once(ServerReflectionRequest { 85 | host: String::default(), 86 | message_request: Some(MessageRequest::ListServices("*".into())), 87 | }); 88 | let mut resp = client 89 | .server_reflection_info(req_stream) 90 | .await 91 | .unwrap() 92 | .into_inner(); 93 | let mut list = vec![]; 94 | while let Some(s) = resp.next().await { 95 | match s { 96 | Ok(o) => { 97 | if let MessageResponse::ListServicesResponse(s) = o.message_response.unwrap() { 98 | for i in s.service.into_iter() { 99 | list.push(i.name) 100 | } 101 | } 102 | break; 103 | } 104 | Err(e) => { 105 | return Err(anyhow::anyhow!( 106 | "get_service_list error: status:[{}] message:{}", 107 | e.code(), 108 | e.message() 109 | )); 110 | } 111 | }; 112 | // if let MessageResponse::ListServicesResponse(s) = s.unwrap().message_response.unwrap() { 113 | // for i in s.service.into_iter() { 114 | // list.push(i.name) 115 | // } 116 | // } 117 | } 118 | return Ok(list); 119 | } 120 | async fn add_dependency_to_services(client: &mut ServerReflectionClient,map:&mut HashMap>) -> anyhow::Result>>{ 121 | let mut list = vec![]; 122 | let mut services = vec![]; 123 | for (_,v) in map.iter(){ 124 | for i in v.iter(){ 125 | services.push(i); 126 | } 127 | } 128 | let mut deps = vec![]; 129 | for i in services.into_iter(){ 130 | for i in i.dependency.clone().into_iter(){ 131 | if !deps.iter().any(|x:&String|x == i.as_str() ) { 132 | deps.push(i); 133 | } 134 | } 135 | } 136 | if deps.is_empty() { 137 | return Ok(list) 138 | } 139 | loop { 140 | let resp = Self::reflect_dependency_by_file(client,deps).await?; 141 | if resp.is_empty() { 142 | break 143 | } 144 | deps = vec![]; 145 | for i in resp.iter(){ 146 | for j in i.dependency.iter(){ 147 | deps.push(j.clone()); 148 | } 149 | } 150 | list.push(resp); 151 | } 152 | 153 | Ok(list) 154 | } 155 | async fn reflect_dependency_by_file(client: &mut ServerReflectionClient,deps:Vec) -> anyhow::Result>{ 156 | let mut list = vec![]; 157 | if deps.is_empty() { 158 | return Ok(list) 159 | } 160 | 161 | let stream = deps 162 | .iter() 163 | .map(|x| ServerReflectionRequest { 164 | host: String::default(), 165 | message_request: Some(MessageRequest::FileByFilename(x.to_string())), 166 | }) 167 | .collect::>(); 168 | let resp = Self::reflect_request(client,stream).await?; 169 | 170 | 171 | for i in resp.into_iter(){ 172 | for i in i.into_iter(){ 173 | list.push(i); 174 | } 175 | } 176 | 177 | Ok(list) 178 | } 179 | async fn get_file_desc_by_services( 180 | client: &mut ServerReflectionClient, 181 | services: Vec, 182 | ) -> anyhow::Result>> { 183 | let mut map = HashMap::new(); 184 | let stream = services 185 | .iter() 186 | .map(|x| ServerReflectionRequest { 187 | host: String::default(), 188 | message_request: Some(MessageRequest::FileContainingSymbol(x.to_string())), 189 | }) 190 | .collect::>(); 191 | let resp = Self::reflect_request(client,stream).await?; 192 | 193 | for (_,list) in resp.into_iter().enumerate(){ 194 | for i in list.iter(){ 195 | for j in i.service.iter(){ 196 | let name = format!("{}.{}",i.package(),j.name()); 197 | if services.contains(&name) { 198 | map.insert(name,list.clone()); 199 | } 200 | } 201 | } 202 | } 203 | 204 | // let req_stream = tokio_stream::iter(stream); 205 | // let mut resp = client 206 | // .server_reflection_info(req_stream) 207 | // .await? 208 | // .into_inner(); 209 | // let mut i = 0; 210 | // while let Some(s) = resp.next().await { 211 | // let resp = match s { 212 | // Ok(o) => o, 213 | // Err(e) => { 214 | // return Err(anyhow::anyhow!( 215 | // "get_file_desc_by_services error,status:{},message:{}", 216 | // e.code(), 217 | // e.message() 218 | // )); 219 | // } 220 | // }; 221 | // let file_desc_resp = resp.message_response.unwrap(); 222 | // match file_desc_resp { 223 | // MessageResponse::FileDescriptorResponse(s) => { 224 | // let mut list = vec![]; 225 | // for buf in s.file_descriptor_proto.into_iter() { 226 | // let fdp = FileDescriptorProto::parse_from_bytes(buf.as_slice())?; 227 | // list.push(fdp); 228 | // } 229 | // map.insert(services[i].clone(), list); 230 | // } 231 | // _ => {} 232 | // } 233 | // i += 1; 234 | // } 235 | Ok(map) 236 | } 237 | async fn reflect_request(client: &mut ServerReflectionClient, stream:Vec) -> anyhow::Result>> { 238 | let mut item = vec![]; 239 | let req_stream = tokio_stream::iter(stream); 240 | let mut resp = client 241 | .server_reflection_info(req_stream) 242 | .await? 243 | .into_inner(); 244 | while let Some(s) = resp.next().await { 245 | let resp = match s { 246 | Ok(o) => o, 247 | Err(e) => { 248 | return Err(anyhow::anyhow!( 249 | "reflect_request error,status:{},message:{}", 250 | e.code(), 251 | e.message() 252 | )); 253 | } 254 | }; 255 | let file_desc_resp = resp.message_response.unwrap(); 256 | match file_desc_resp { 257 | MessageResponse::FileDescriptorResponse(s) => { 258 | let mut list = vec![]; 259 | for buf in s.file_descriptor_proto.into_iter() { 260 | let fdp = FileDescriptorProto::parse_from_bytes(buf.as_slice())?; 261 | list.push(fdp); 262 | } 263 | item.push(list) 264 | } 265 | _ => {} 266 | } 267 | } 268 | Ok(item) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/infra/profiler/services_desc_assembler.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::profiler::ServiceDescriptorAssembler; 2 | use protobuf::descriptor::FileDescriptorProto; 3 | use protobuf::reflect::ServiceDescriptor; 4 | use std::collections::HashMap; 5 | 6 | pub struct ServiceDescriptorAssemblerDefaultImpl; 7 | 8 | impl ServiceDescriptorAssembler for ServiceDescriptorAssemblerDefaultImpl { 9 | fn assemble( 10 | &self, 11 | input: HashMap>, 12 | deps : Vec>, 13 | ) -> anyhow::Result> { 14 | let mut map = HashMap::new(); 15 | // for (k, v) in input.into_iter() { 16 | // let service_name = k.split('.').map(|x| x.to_string()).collect::>(); 17 | // let files = protobuf::reflect::FileDescriptor::new_dynamic_fds(v, &[])?; 18 | // for i in files.into_iter() { 19 | // for x in i.services().into_iter() { 20 | // let sn = if let Some(ref sn) = x.proto().name { 21 | // sn 22 | // } else { 23 | // continue; 24 | // }; 25 | // if sn.eq(&service_name[1]) { 26 | // map.insert(k.to_string(), x); 27 | // } 28 | // } 29 | // } 30 | // } 31 | // let mut dep_des = vec![]; 32 | // for i in deps.into_iter().rev(){ 33 | // println!("---1"); 34 | // let des = protobuf::reflect::FileDescriptor::new_dynamic_fds(i, &dep_des)?; 35 | // for j in des.into_iter(){ 36 | // println!("---2 {}",j.name()); 37 | // if !dep_des.iter().any(|x|x.name() == j.name()) { 38 | // dep_des.push(j); 39 | // } 40 | // } 41 | // } 42 | let mut des = vec![]; 43 | for i in deps.into_iter().rev(){ 44 | for j in i.into_iter(){ 45 | if !des.iter().any(|x:&FileDescriptorProto|x.name() == j.name()) { 46 | des.push(j); 47 | } 48 | } 49 | 50 | } 51 | let deps = protobuf::reflect::FileDescriptor::new_dynamic_fds(des, &[])?; 52 | 53 | let mut services = HashMap::new(); 54 | let mut files = HashMap::new(); 55 | for (k, list) in input.into_iter() { 56 | let name = k.split('.').map(|x| x).collect::>(); 57 | services.insert(name[1].to_string(), k); 58 | for i in list.into_iter() { 59 | if !files.contains_key(i.name()) { 60 | files.insert(i.name().to_string(), i); 61 | } 62 | } 63 | } 64 | let files = files 65 | .into_iter() 66 | .map(|x| x.1) 67 | .collect::>(); 68 | 69 | let desc_files = protobuf::reflect::FileDescriptor::new_dynamic_fds(files, &deps)?; 70 | // println!("---> ok"); 71 | for i in desc_files.into_iter() { 72 | for sd in i.services().into_iter() { 73 | let sn = if let Some(ref sn) = sd.proto().name { 74 | sn 75 | } else { 76 | continue; 77 | }; 78 | if let Some(s) = services.get(sn) { 79 | map.insert(s.to_string(), sd); 80 | } 81 | } 82 | } 83 | Ok(map) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/infra/server/filter_middle_log.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::server::filter_middle_time::TimeMiddle; 2 | use crate::infra::server::{HttpFilter, RequestIdMiddle}; 3 | use hyper::{Body, Method, Request, Response}; 4 | use std::sync::Arc; 5 | use wd_run::Context; 6 | 7 | const HTTP_LOG_METHOD: &'static str = "HTTP_LOG_METHOD"; 8 | const HTTP_LOG_PATH: &'static str = "HTTP_LOG_PATH"; 9 | 10 | pub struct LogMiddle; 11 | 12 | impl LogMiddle { 13 | pub async fn method_string(ctx: &Context) -> String { 14 | ctx.get(HTTP_LOG_METHOD) 15 | .await 16 | .map(|x: Arc| x.to_string()) 17 | .unwrap_or("unknown".into()) 18 | // let opt = ctx.get::<_,Method>(HTTP_LOG_METHOD).await; 19 | // if opt.is_none() { 20 | // return String::new(); 21 | // } 22 | // opt.unwrap().to_string() 23 | } 24 | pub async fn path(ctx: &Context) -> Arc { 25 | ctx.get(HTTP_LOG_PATH) 26 | .await 27 | .unwrap_or(Arc::new(String::default())) 28 | } 29 | } 30 | 31 | #[async_trait::async_trait] 32 | impl HttpFilter for LogMiddle { 33 | async fn request( 34 | &self, 35 | ctx: Context, 36 | req: Request, 37 | ) -> Result, Response> { 38 | let method = req.method().clone(); 39 | let path = req.uri().to_string(); 40 | ctx.set(HTTP_LOG_METHOD, method).await; 41 | ctx.set(HTTP_LOG_PATH, path).await; 42 | Ok(req) 43 | } 44 | 45 | async fn response(&self, ctx: Context, resp: Response) -> Response { 46 | let request_id = RequestIdMiddle::request_id(&ctx).await.unwrap_or(0); 47 | let method = LogMiddle::method_string(&ctx).await; 48 | let path = LogMiddle::path(&ctx).await; 49 | let time = TimeMiddle::elapsed(&ctx).await.as_millis(); 50 | let status = resp.status(); 51 | wd_log::log_info_ln!( 52 | "request[{}:{}ms] method:[{}] path:{} response:{}", 53 | request_id, 54 | time, 55 | method, 56 | path, 57 | status 58 | ); 59 | resp 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/infra/server/filter_middle_req_id.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::server::HttpFilter; 2 | use hyper::{Body, Request, Response}; 3 | use wd_run::Context; 4 | use wd_sonyflake::SonyFlakeEntity; 5 | 6 | const HTTP_REQUEST_ID: &'static str = "HTTP_REQUEST_ID"; 7 | 8 | pub struct RequestIdMiddle { 9 | rand: SonyFlakeEntity, 10 | } 11 | 12 | impl RequestIdMiddle { 13 | pub fn new() -> Self { 14 | let rand = SonyFlakeEntity::new_default(); 15 | Self { rand } 16 | } 17 | pub async fn request_id(ctx: &Context) -> Option { 18 | ctx.copy(HTTP_REQUEST_ID).await 19 | } 20 | } 21 | 22 | #[async_trait::async_trait] 23 | impl HttpFilter for RequestIdMiddle { 24 | async fn request( 25 | &self, 26 | ctx: Context, 27 | req: Request, 28 | ) -> Result, Response> { 29 | let id = self.rand.get_id(); 30 | ctx.set(HTTP_REQUEST_ID, id).await; 31 | Ok(req) 32 | } 33 | 34 | async fn response(&self, _ctx: Context, req: Response) -> Response { 35 | req 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/infra/server/filter_middle_time.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::server::HttpFilter; 2 | use hyper::{Body, Request, Response}; 3 | use std::time::{Duration, Instant}; 4 | use wd_run::Context; 5 | 6 | const HTTP_REQUEST_ELAPSED_TIME: &'static str = "HTTP_REQUEST_ELAPSED_TIME"; 7 | 8 | pub struct TimeMiddle; 9 | 10 | impl TimeMiddle { 11 | pub async fn elapsed(ctx: &Context) -> Duration { 12 | let instant = if let Some(s) = ctx.get::<_, Instant>(HTTP_REQUEST_ELAPSED_TIME).await { 13 | s 14 | } else { 15 | return Duration::default(); 16 | }; 17 | instant.elapsed() 18 | } 19 | } 20 | 21 | #[async_trait::async_trait] 22 | impl HttpFilter for TimeMiddle { 23 | async fn request( 24 | &self, 25 | ctx: Context, 26 | req: Request, 27 | ) -> Result, Response> { 28 | let instant = Instant::now(); 29 | ctx.set(HTTP_REQUEST_ELAPSED_TIME, instant).await; 30 | Ok(req) 31 | } 32 | 33 | // async fn response(&self, ctx: Context, resp: Response) -> Response { 34 | // resp 35 | // } 36 | } 37 | -------------------------------------------------------------------------------- /src/infra/server/http_server.rs: -------------------------------------------------------------------------------- 1 | use crate::infra::server::shutdown_control::ShutDownControl; 2 | use crate::infra::server::{HttpFilter, HttpHandle, ShutDown}; 3 | use hyper::server::conn::{AddrIncoming, AddrStream}; 4 | use hyper::server::Builder; 5 | use hyper::service::{make_service_fn, service_fn}; 6 | use hyper::{Body, Request, Response, Server}; 7 | use std::convert::Infallible; 8 | use std::net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs}; 9 | use std::sync::Arc; 10 | use wd_run::Context; 11 | 12 | pub struct HyperHttpServer { 13 | filters: Vec>, 14 | handle: Box, 15 | } 16 | 17 | impl Default for HyperHttpServer { 18 | fn default() -> Self { 19 | HyperHttpServer::new(|_c, _r| async { Ok(Response::new(Body::from(r#"{"health":true}"#))) }) 20 | } 21 | } 22 | impl From> for HyperHttpServer { 23 | fn from(value: Box) -> Self { 24 | let mut server = HyperHttpServer::default(); 25 | server.handle = value; 26 | server 27 | } 28 | } 29 | impl HyperHttpServer { 30 | pub fn new(handle: H) -> Self { 31 | let filters = vec![]; 32 | let handle = Box::new(handle); 33 | Self { filters, handle } 34 | } 35 | pub fn append_handles( 36 | mut self, 37 | mut filters: Vec>, 38 | ) -> Self { 39 | self.filters.append(&mut filters); 40 | self 41 | } 42 | pub async fn service( 43 | self: Arc, 44 | req: Request, 45 | ) -> Result, Infallible> { 46 | let ctx = Context::new(); 47 | let mut index = 0; 48 | let mut req = Some(req); 49 | let mut resp = None; //Response::new(Body::empty()); 50 | 51 | for (i, filter) in self.filters.iter().enumerate() { 52 | index = i; 53 | req = match filter.request(ctx.clone(), req.unwrap()).await { 54 | Ok(o) => Some(o), 55 | Err(e) => { 56 | resp = Some(e); 57 | None 58 | } 59 | }; 60 | if req.is_none() { 61 | break; 62 | } 63 | } 64 | if req.is_some() { 65 | let result = self.handle.handle(ctx.clone(), req.unwrap()).await; 66 | resp = match result { 67 | Ok(o) => Some(o), 68 | Err(e) => { 69 | wd_log::log_error_ln!("HyperHttpServer service handle error:{}", e); 70 | Some( 71 | Response::builder() 72 | .status(500) 73 | .body(Body::from("unknown error")) 74 | .unwrap(), 75 | ) 76 | } 77 | }; 78 | } 79 | let skip = self.filters.len() - index - 1; 80 | for (_, filter) in self 81 | .filters 82 | .iter() 83 | .rev() 84 | .enumerate() 85 | .filter(|(i, _)| i >= &skip) 86 | { 87 | resp = Some(filter.response(ctx.clone(), resp.unwrap()).await) 88 | } 89 | Ok(resp.unwrap()) 90 | } 91 | } 92 | 93 | pub struct HyperHttpServerBuilder { 94 | addr: SocketAddr, 95 | filters: Vec>, 96 | handle: Option>, 97 | shutdown: Option, 98 | } 99 | 100 | impl HyperHttpServerBuilder { 101 | pub fn new() -> Self { 102 | let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6789); 103 | let filters = vec![]; 104 | let handle = None; 105 | let shutdown = None; 106 | Self { 107 | addr, 108 | filters, 109 | handle, 110 | shutdown, 111 | } 112 | } 113 | #[allow(dead_code)] 114 | pub fn set_addr(mut self, addr: SocketAddr) -> Self { 115 | self.addr = addr; 116 | self 117 | } 118 | pub fn shutdown_singe(mut self) -> (Self, ShutDown) { 119 | if self.shutdown.is_some() { 120 | let sd = self.shutdown.as_ref().unwrap().generate_shutdown(); 121 | return (self, sd); 122 | } 123 | let sdc = ShutDownControl::new(); 124 | let sd = sdc.generate_shutdown(); 125 | self.shutdown = Some(sdc); 126 | (self, sd) 127 | } 128 | pub fn set_shutdown_singe(mut self, sd: ShutDown) -> Self { 129 | self.shutdown = Some(ShutDownControl::from(sd)); 130 | self 131 | } 132 | pub fn handle(mut self, handle: H) -> Self { 133 | self.handle = Some(Box::new(handle)); 134 | self 135 | } 136 | #[allow(dead_code)] 137 | pub fn parse_addr>( 138 | mut self, 139 | addr: S, 140 | ) -> anyhow::Result { 141 | self.addr = addr.to_socket_addrs()?; 142 | Ok(self) 143 | } 144 | pub fn append_filter(mut self, filter: H) -> Self { 145 | self.filters.push(Box::new(filter)); 146 | self 147 | } 148 | pub async fn custom_run< 149 | C: FnOnce(Builder) -> anyhow::Result> + Send, 150 | >( 151 | self, 152 | custom_func: C, 153 | ) -> anyhow::Result<()> { 154 | if self.handle.is_none() { 155 | return Err(anyhow::anyhow!( 156 | "HyperHttpServerBuilder handle function is nil,please impl HttpHandle" 157 | )); 158 | } 159 | let ser = 160 | Arc::new(HyperHttpServer::from(self.handle.unwrap()).append_handles(self.filters)); 161 | let http_service = make_service_fn(move |_c: &AddrStream| { 162 | let ser = ser.clone(); 163 | let service = service_fn(move |req| HyperHttpServer::service(ser.clone(), req)); 164 | async move { Ok::<_, Infallible>(service) } 165 | }); 166 | let server = Server::bind(&self.addr); 167 | let server = custom_func(server)?.serve(http_service); 168 | if let Some(sdc) = self.shutdown { 169 | let asdc = sdc.clone(); 170 | let server = server.with_graceful_shutdown(async move { 171 | asdc.wait().await; 172 | }); 173 | let result = server.await; 174 | sdc.down().await; 175 | result?; 176 | return Ok(()); 177 | } 178 | server.await?; 179 | Ok(()) 180 | } 181 | #[allow(dead_code)] 182 | pub async fn run(self) -> anyhow::Result<()> { 183 | self.custom_run(|b| Ok(b)).await 184 | } 185 | #[allow(dead_code)] 186 | pub fn async_run(self) -> ShutDown { 187 | let (server, sd) = self.shutdown_singe(); 188 | tokio::spawn(async move { 189 | server 190 | .custom_run(|b| Ok(b)) 191 | .await 192 | .expect("async_run http server error") 193 | }); 194 | return sd; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/infra/server/mod.rs: -------------------------------------------------------------------------------- 1 | mod filter_middle_log; 2 | mod filter_middle_req_id; 3 | mod filter_middle_time; 4 | mod http_server; 5 | mod shutdown_control; 6 | 7 | pub use filter_middle_log::LogMiddle; 8 | pub use filter_middle_req_id::RequestIdMiddle; 9 | pub use filter_middle_time::TimeMiddle; 10 | pub use http_server::{HyperHttpServer, HyperHttpServerBuilder}; 11 | use hyper::{Body, Request, Response}; 12 | pub use shutdown_control::ShutDown; 13 | use std::future::Future; 14 | use wd_run::Context; 15 | 16 | #[async_trait::async_trait] 17 | pub trait HttpHandle { 18 | async fn handle(&self, ctx: Context, req: Request) -> anyhow::Result>; 19 | } 20 | 21 | #[allow(unused_variables)] 22 | #[async_trait::async_trait] 23 | pub trait HttpFilter: Sync { 24 | async fn request( 25 | &self, 26 | ctx: Context, 27 | req: Request, 28 | ) -> Result, Response> { 29 | Ok(req) 30 | } 31 | async fn response(&self, ctx: Context, resp: Response) -> Response { 32 | resp 33 | } 34 | } 35 | 36 | #[async_trait::async_trait] 37 | impl HttpHandle for T 38 | where 39 | T: Fn(Context, Request) -> F + Send + Sync, 40 | F: Future>> + Send, 41 | { 42 | async fn handle(&self, ctx: Context, req: Request) -> anyhow::Result> { 43 | self(ctx, req).await 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use crate::infra::server::HyperHttpServerBuilder; 50 | use hyper::{Body, Request, Response}; 51 | 52 | #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 53 | async fn test_http_server() { 54 | HyperHttpServerBuilder::new() 55 | .handle(|_c, _r: Request| async move { Ok(Response::new(Body::from("success"))) }) 56 | .run() 57 | .await 58 | .expect("http服务报错"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/infra/server/shutdown_control.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; 2 | use std::sync::Arc; 3 | use std::time::Duration; 4 | use tokio::time::sleep; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct ShutDownControl { 8 | status: Arc, 9 | enable: Arc, 10 | } 11 | 12 | #[derive(Debug, Clone, Default)] 13 | pub struct ShutDown { 14 | status: Arc, //关闭进度 15 | enable: Arc, //是否需要等待通知 16 | } 17 | 18 | impl ShutDown { 19 | pub fn new(status: Arc, enable: Arc) -> ShutDown { 20 | ShutDown { status, enable } 21 | } 22 | pub async fn wait_close(&self) { 23 | while self.status.load(Ordering::Relaxed) < 2 { 24 | sleep(Duration::from_millis(10)).await; 25 | } 26 | } 27 | //给关闭设置一个超时时间 28 | #[allow(dead_code)] 29 | pub async fn close_timeout(&self, timeout: Duration) { 30 | if !self.enable.load(Ordering::Relaxed) { 31 | return; 32 | } 33 | self.status.fetch_add(1, Ordering::Relaxed); 34 | let _ = tokio::time::timeout(timeout, async move { 35 | while self.status.load(Ordering::Relaxed) == 1 { 36 | sleep(Duration::from_millis(10)).await; 37 | } 38 | }); 39 | self.status.fetch_add(1, Ordering::Relaxed); 40 | } 41 | //等待关闭 直到跳出为止 42 | pub async fn close(&self) { 43 | if !self.enable.load(Ordering::Relaxed) { 44 | return; 45 | } 46 | self.status.fetch_add(1, Ordering::Relaxed); 47 | while self.status.load(Ordering::Relaxed) == 1 { 48 | sleep(Duration::from_millis(10)).await; 49 | } 50 | } 51 | } 52 | 53 | impl ShutDownControl { 54 | pub fn new() -> ShutDownControl { 55 | Self { 56 | status: Arc::new(AtomicU8::new(0)), 57 | enable: Arc::new(AtomicBool::new(false)), 58 | } 59 | } 60 | pub fn generate_shutdown(&self) -> ShutDown { 61 | ShutDown::new(self.status.clone(), self.enable.clone()) 62 | } 63 | //等待接受停止信号 64 | pub async fn wait(&self) { 65 | self.enable.store(true, Ordering::Relaxed); 66 | while self.status.load(Ordering::Relaxed) == 0 { 67 | sleep(Duration::from_millis(10)).await; 68 | } 69 | } 70 | //收尾动作完成后响应 71 | pub async fn down(&self) { 72 | self.status.store(2, Ordering::Relaxed); 73 | } 74 | } 75 | 76 | impl From for ShutDownControl { 77 | fn from(value: ShutDown) -> Self { 78 | ShutDownControl { 79 | status: value.status, 80 | enable: value.enable, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod cmd; 3 | mod config; 4 | mod infra; 5 | mod util; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | cmd::start().await; 10 | } 11 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | mod to_vec; 2 | -------------------------------------------------------------------------------- /src/util/to_vec.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webhook/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | ADD webhook /webhook 4 | ENTRYPOINT ["./webhook"] -------------------------------------------------------------------------------- /webhook/certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDMjCCAhoCCQCngc39AoTpWzANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwIx 3 | MjEKMAgGA1UECAwBMzEKMAgGA1UEBwwBMzEKMAgGA1UECgwBMzEKMAgGA1UECwwB 4 | MTEKMAgGA1UEAwwBMzEQMA4GCSqGSIb3DQEJARYBMTAeFw0yMzAyMjgwOTI0MjZa 5 | Fw0yMzAzMzAwOTI0MjZaMFsxCzAJBgNVBAYTAjEyMQowCAYDVQQIDAEzMQowCAYD 6 | VQQHDAEzMQowCAYDVQQKDAEzMQowCAYDVQQLDAExMQowCAYDVQQDDAEzMRAwDgYJ 7 | KoZIhvcNAQkBFgExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVbr 8 | fmEYYLBL7+ncKKWmaQpSmJ9UVh7A+e3QUG6cLHKaugxr2aIcxMa2jUWrVm5aZ2lF 9 | LdOp5IpUdvjCv9APlG0+kxwNh5tk7yQzdDOeKw7nhpa8rPM9rKFpCt59lIER+PA+ 10 | KSxzedfdPHO6YW+BYjfBjKQ8Fk3DhN9UlPI3OfkBr56hldD9soKDk4P2Xze06Fgy 11 | gcfz71oNoaSMiADe6oMbuxWKlfpfFERXwQSt2PNLUodbla+i0aY0TGOP+ygkv6YO 12 | 23FzJBoMJzFuFt9pviP/PbvsCDfQkwxauOz4JxfyXU0PNwZc/GdvlBb/bT4/J/pi 13 | 9aIurbrWEEGcXeSw6QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAFMlsMXEokM3nW 14 | A9YZu8DypIvZJn8sW9dUl/PsuYp5vb9OVCkNGK2hb6ilvB8cOU1DriAwadR/oO4O 15 | BdLUwxHqiLcefjJS7DtPy8ROlXmN7JsEQs4SIWKFsbprzBKBDvgQzwDvwBcFmwOe 16 | SoE50roTUAqSux+28WNE4i/b1yT3VfkGI4d9W0/eMq3jcU0/tyq1dOZ1OS4kSBV9 17 | Hl/u7ciXgdlSkO7aDR7F9CdnQMOYfY8ouqIIoqxTS34T6dtSsrCPRXPsAmNivtQB 18 | EWuCY6rbZDrF0LjSWp6wc1iK9OHAe1yf2DTFOoHfhfbD034wrcOJd1NBLEf7anHu 19 | zENrKJis 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /webhook/certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAvVbrfmEYYLBL7+ncKKWmaQpSmJ9UVh7A+e3QUG6cLHKaugxr 3 | 2aIcxMa2jUWrVm5aZ2lFLdOp5IpUdvjCv9APlG0+kxwNh5tk7yQzdDOeKw7nhpa8 4 | rPM9rKFpCt59lIER+PA+KSxzedfdPHO6YW+BYjfBjKQ8Fk3DhN9UlPI3OfkBr56h 5 | ldD9soKDk4P2Xze06Fgygcfz71oNoaSMiADe6oMbuxWKlfpfFERXwQSt2PNLUodb 6 | la+i0aY0TGOP+ygkv6YO23FzJBoMJzFuFt9pviP/PbvsCDfQkwxauOz4JxfyXU0P 7 | NwZc/GdvlBb/bT4/J/pi9aIurbrWEEGcXeSw6QIDAQABAoIBAHEmSZN7+PKHSMo2 8 | KqCL5fft+FBHH0JcHJz/lrcKcwBI+NhoZedobuLVkfC5CtybFCGqknIBhQ0T8cgr 9 | f07bywO7iWKmqBs7LUWQj8NIuaQnwwr2eAivUFMjHsAlGE0wGQp4n91BOD0/WHIx 10 | AtgJp/uHMH6ZA2Oc01RLlqNwYLqeYlB8uwqeXCaROFdvN5PLZOwuHKFVNx6G1ird 11 | buqlJk2ilokHmoKtHgRShZfQVEXBQE9yLV5urz3sm1V7BBpM+rea5VLcccNWGSfr 12 | bW0imU5B++6sXzx54yVTdoY8rQ82r4xJH6PzPr4RfAC1Bgb/q3DeEbbr78bg+zVb 13 | kJJn8gECgYEA3o0CYvk8eDmCCRJeLQjEd3UjCzOVIgi/TshUQ2D/cJ6+gJ8dWku2 14 | H+9TmdAzbJhPhGKhRg40FlCTX18L/ZF35OsYe1KisbtNEQ2NHsYAuT7axRxUJBmV 15 | 3hOEiZ4UpGKVsA6zKUi4utldG1TR4kwcOdq7+XHqJhPglqjiauKHKKkCgYEA2cwN 16 | 7Eu9xJN1gExL+OqbAMtpBYCpMSoosWlx5cjmYHdcOxQIiGLzXdzcBZ+sqCENXdA5 17 | +wogUl8fzg6GfJrtUA50loKUdZnglJhwRwYlIT7PAd+e3upRV52ZywPNgDxc4O1d 18 | +qGeTuZSYUDgtYomQo7NEPwkY+jrQvATnOA9LkECgYEAuGHJXWizAcRbiNqP7e26 19 | oGILE6e74gOvRD1TGrPNTmgm2vd44SujC5hEAkZtpIYYM01n4lu4Kdi+EKD1lTKl 20 | S6K/mMThCDFu6zb3+UwAo3mA1RfgWjxcXy56e23eGkeXTtMO+qBGdH/L1O4UKdH2 21 | /LPNUyBe5wdG5murkvR1GGkCgYB8s801ApkBOL0KXKUTPKDLb6wOLoCxWcY67nj8 22 | bG8bxLvWR74q+R0HHTINNkORcHtKv7Ky/jVLdJOtU7vFFJ/UOrKeit2eCCECc9W1 23 | lNslkeTM2dEJKXixhddM2jyLtAQp9T7eBjOyUUXR0WfYCeWmLkVT+EqNugsaJIPW 24 | Sr49AQKBgAUfpIqe2B+KBcZ+hytRjJ/HYSRYhbSZVRNsnS6S2GB9bzuQ4Q+0c38v 25 | dcJhFPRhqRyVTVTs9MNxYxsdhhZt6ntJRuiT8r80+abhlTljxw8TJFhk2OOFR6jK 26 | GMS9W5mLDcuO+vmSp9rjxyEtr0otfsNORYB6jS7m+20FMIz6hsJJ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /webhook/go.mod: -------------------------------------------------------------------------------- 1 | module webhook 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.0 7 | github.com/sirupsen/logrus v1.9.0 8 | k8s.io/api v0.26.1 9 | k8s.io/apimachinery v0.26.1 10 | ) 11 | 12 | require ( 13 | github.com/bytedance/sonic v1.8.2 // indirect 14 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 15 | github.com/gin-contrib/sse v0.1.0 // indirect 16 | github.com/go-logr/logr v1.2.3 // indirect 17 | github.com/go-playground/locales v0.14.1 // indirect 18 | github.com/go-playground/universal-translator v0.18.1 // indirect 19 | github.com/go-playground/validator/v10 v10.11.2 // indirect 20 | github.com/goccy/go-json v0.10.0 // indirect 21 | github.com/gogo/protobuf v1.3.2 // indirect 22 | github.com/google/gofuzz v1.1.0 // indirect 23 | github.com/json-iterator/go v1.1.12 // indirect 24 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 25 | github.com/leodido/go-urn v1.2.1 // indirect 26 | github.com/mattn/go-isatty v0.0.17 // indirect 27 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 28 | github.com/modern-go/reflect2 v1.0.2 // indirect 29 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 30 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 31 | github.com/ugorji/go/codec v1.2.10 // indirect 32 | golang.org/x/arch v0.2.0 // indirect 33 | golang.org/x/crypto v0.6.0 // indirect 34 | golang.org/x/net v0.7.0 // indirect 35 | golang.org/x/sys v0.5.0 // indirect 36 | golang.org/x/text v0.7.0 // indirect 37 | google.golang.org/protobuf v1.28.1 // indirect 38 | gopkg.in/inf.v0 v0.9.1 // indirect 39 | gopkg.in/yaml.v2 v2.4.0 // indirect 40 | gopkg.in/yaml.v3 v3.0.1 // indirect 41 | k8s.io/klog/v2 v2.80.1 // indirect 42 | k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect 43 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 44 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 45 | sigs.k8s.io/yaml v1.3.0 // indirect 46 | ) 47 | -------------------------------------------------------------------------------- /webhook/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY= 3 | github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 11 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 12 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= 13 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= 14 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 15 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 16 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 17 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 18 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 19 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 20 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 21 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 22 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= 23 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= 24 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 25 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 26 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 27 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 28 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 31 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 32 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 33 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 34 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 35 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 36 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 37 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 38 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 39 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 40 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 41 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 42 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 43 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 44 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 45 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 46 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 47 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 50 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 51 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 52 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 53 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 54 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 55 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 56 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 57 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 58 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 59 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 60 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 61 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 62 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 63 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 64 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 65 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 66 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 67 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 68 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 69 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 70 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 71 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 72 | github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= 73 | github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 74 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 75 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 76 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 77 | golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= 78 | golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 79 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 80 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 81 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 82 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 83 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 84 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 85 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 86 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 87 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 88 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 89 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 90 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 91 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 92 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 93 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 95 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 96 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 102 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 104 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 105 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 106 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 107 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 108 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 109 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 110 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 111 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 113 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 114 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 115 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 116 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 117 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 119 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 120 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 121 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 122 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 123 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 124 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 125 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 126 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 127 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 128 | k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= 129 | k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= 130 | k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= 131 | k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= 132 | k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= 133 | k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 134 | k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= 135 | k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 136 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 137 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= 138 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 139 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 140 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 141 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 142 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 143 | -------------------------------------------------------------------------------- /webhook/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | "net/http" 10 | ) 11 | 12 | var ( 13 | certFile string 14 | keyFile string 15 | port int 16 | sidecarImage string 17 | ) 18 | 19 | func initFlag() { 20 | flag.IntVar(&port, "port", 443, "Webhook server port.") 21 | flag.StringVar(&certFile, "tlsCertFile", "./certs/cert.pem", "File containing the x509 Certificate for HTTPS.") 22 | flag.StringVar(&keyFile, "tlsKeyFile", "./certs/key.pem", "File containing the x509 private key to --tlsCertFile.") 23 | flag.Parse() 24 | } 25 | 26 | func configTLS(cert, key string) *tls.Config { 27 | sCert, err := tls.LoadX509KeyPair(cert, key) 28 | if err != nil { 29 | panic(err) 30 | } 31 | return &tls.Config{ 32 | Certificates: []tls.Certificate{sCert}, 33 | // TODO: uses mutual tls after we agree on what cert the apiserver should use. 34 | // ClientAuth: tls.RequireAndVerifyClientCert, 35 | } 36 | } 37 | 38 | func main() { 39 | initFlag() 40 | gin.SetMode(gin.ReleaseMode) 41 | 42 | app := gin.Default() 43 | 44 | app.POST("/sidecar/rust-grpc-proxy", SidecarRustGrpcProxy) 45 | 46 | server := &http.Server{ 47 | Addr: fmt.Sprintf(":%d", port), 48 | TLSConfig: configTLS(certFile, keyFile), 49 | Handler: app, 50 | } 51 | logrus.Infoln("webhook server running") 52 | err := server.ListenAndServeTLS("", "") 53 | if err != nil { 54 | panic(err) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /webhook/sidecar_rust_grpc_proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/sirupsen/logrus" 7 | "io/ioutil" 8 | v1 "k8s.io/api/admission/v1" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/apimachinery/pkg/runtime/serializer" 13 | "k8s.io/apimachinery/pkg/types" 14 | "net/http" 15 | ) 16 | 17 | var ( 18 | sidecarName = "rust-grpc-proxy-sidecar" 19 | lable = "rustGrpcProxyEnable" 20 | podsSidecarPatch = `[{"op":"add", "path":"/spec/containers/-","value":{"env":[{"name":"RUST_GRPC_PROXY_ADDR","value": "%s"}],"image":"%s","name":"%s","resources":{}}}]` 21 | image = "wdshihaoren/rust-grpc-proxy:latest" 22 | runtimeScheme = runtime.NewScheme() 23 | codecs = serializer.NewCodecFactory(runtimeScheme) 24 | deserializer = codecs.UniversalDeserializer() 25 | ) 26 | 27 | func SidecarRustGrpcProxy(c *gin.Context) { 28 | body, err := ioutil.ReadAll(c.Request.Body) 29 | if err != nil { 30 | ResponseError(c, "read body error:%v", err) 31 | return 32 | } 33 | logrus.Infoln("request--->", string(body)) 34 | contentType := c.GetHeader("Content-Type") 35 | if contentType != "application/json" { 36 | c.String(http.StatusUnsupportedMediaType, "md not real") 37 | return 38 | } 39 | review := new(v1.AdmissionReview) 40 | if _, _, err = deserializer.Decode(body, nil, review); err != nil { 41 | ResponseError(c, "Unmarshal AdmissionReview error:%v", err) 42 | return 43 | } 44 | podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} 45 | if review.Request.Resource != podResource { 46 | ResponseError(c, "expect resource to be %s", podResource) 47 | return 48 | } 49 | pod := new(corev1.Pod) 50 | if _, _, err = deserializer.Decode(review.Request.Object.Raw, nil, pod); err != nil { 51 | ResponseError(c, "deserializer pod error:%v", err) 52 | return 53 | } 54 | for _, i := range pod.Spec.Containers { 55 | if i.Name == sidecarName { 56 | ResponseAllow(c, review.Request.UID) 57 | return 58 | } 59 | } 60 | val, ok := pod.Labels[lable] 61 | if !ok { 62 | ResponseAllow(c, review.Request.UID) 63 | return 64 | } 65 | resp := new(v1.AdmissionResponse) 66 | resp.UID = review.Request.UID 67 | resp.Allowed = true 68 | resp.Patch = []byte(fmt.Sprintf(podsSidecarPatch, "127.0.0.1:"+val, image, sidecarName)) 69 | pt := v1.PatchTypeJSONPatch 70 | resp.PatchType = &pt 71 | 72 | respReview := &v1.AdmissionReview{ 73 | Request: nil, 74 | Response: resp, 75 | } 76 | 77 | respReview.Kind = "AdmissionReview" 78 | respReview.APIVersion = "admission.k8s.io/v1" 79 | c.JSON(http.StatusOK, respReview) 80 | } 81 | 82 | func ResponseError(c *gin.Context, format string, values ...any) { 83 | logrus.Infoln("error:", fmt.Sprintf(format, values...)) 84 | c.String(http.StatusBadRequest, fmt.Sprintf(format, values...)) 85 | } 86 | func ResponseAllow(c *gin.Context, uid types.UID) { 87 | ar := &v1.AdmissionReview{ 88 | Response: &v1.AdmissionResponse{ 89 | UID: uid, 90 | Allowed: true, 91 | }, 92 | } 93 | ar.Kind = "AdmissionReview" 94 | ar.APIVersion = "admission.k8s.io/v1" 95 | c.JSON(http.StatusOK, ar) 96 | } 97 | --------------------------------------------------------------------------------