Compare commits
247 Commits
Author | SHA1 | Date | |
---|---|---|---|
786bbb7185 | |||
95207c9617 | |||
d646deb468 | |||
468819f0a0 | |||
832f1034a8 | |||
f0b6370ee1 | |||
3d522b094b | |||
92dcd1acd7 | |||
dc8a736e13 | |||
|
4219919c9e | ||
1f447ea747 | |||
|
30c0e01397 | ||
244f3def4d | |||
|
55cbc89e11 | ||
df00f718cf | |||
|
bc3369f3a6 | ||
8d4c661ce5 | |||
|
7b97212e26 | ||
|
9d5a2c1168 | ||
|
483c6bb801 | ||
|
7ba5fd5fee | ||
080705a5df | |||
|
79df512e5e | ||
|
3e893b78c8 | ||
8e9c64d78b | |||
|
d66aa424d2 | ||
|
f8d3695962 | ||
ae158ce5fc | |||
8125c9003c | |||
a6f6df257b | |||
6b19cb2fb7 | |||
|
db6fee9760 | ||
309f100532 | |||
|
22ae55f739 | ||
70700a3f86 | |||
7dd327086c | |||
a67efa39ae | |||
|
8ee91422cc | ||
f8ae500c5f | |||
|
7fcc042fbf | ||
3a22f3a900 | |||
|
452a124aee | ||
|
26d3adfe95 | ||
|
18d6584c8f | ||
f26dde5d63 | |||
|
66d3feb263 | ||
5c8effa23f | |||
|
c1e318d0b3 | ||
617764706c | |||
0f3e56f697 | |||
|
b87462c465 | ||
|
e877a92718 | ||
|
c60f0ccb26 | ||
7cf4a8d293 | |||
84b1b862a7 | |||
|
eb17921feb | ||
|
ddeb0a23c3 | ||
|
830d8d8fda | ||
|
ceaff6bf88 | ||
|
01848b8ec7 | ||
|
8ddfa39811 | ||
110a8a8a9c | |||
|
f2587f0876 | ||
eccdad9752 | |||
|
c05996ee6e | ||
734d6fa7af | |||
4c0ca3664a | |||
|
d34ce4f314 | ||
|
d9932033ee | ||
847887de84 | |||
|
10ea1928f4 | ||
|
52d37c6579 | ||
|
0af18ab84b | ||
|
c46d11a2d4 | ||
|
117f48aac5 | ||
b4a2fbdeeb | |||
e36db68d4d | |||
b6b101b140 | |||
cbafc74e41 | |||
1e19359ab1 | |||
8e88abbbca | |||
adf9a0d82d | |||
a1a5980534 | |||
25a74b6935 | |||
|
5ed755f5bb | ||
8dd303472d | |||
|
1f6b2b34ec | ||
ce3da2c9fa | |||
b248593e35 | |||
|
dda5d46e47 | ||
0f50e30f9c | |||
|
f983ccc6d9 | ||
|
ef9ee6d837 | ||
0103c05275 | |||
d1c11644e8 | |||
dc01d20561 | |||
cdee03a96e | |||
48bce4ed2f | |||
|
f00e34380b | ||
86ebd173f7 | |||
9923c98469 | |||
|
51b93c413c | ||
|
9617533481 | ||
|
71f7ea49e4 | ||
|
8e89b4b32f | ||
9a2965b4d4 | |||
|
d90ff34a67 | ||
d2ef32f628 | |||
|
d14209e31e | ||
d82f7c49c7 | |||
|
98c02c72b0 | ||
35ad59444a | |||
|
9e63562468 | ||
978f500ce2 | |||
|
8c8073ca01 | ||
|
8d283ce2b2 | ||
d98376446a | |||
|
e95d956ff7 | ||
|
ad2dc43aed | ||
54562aa346 | |||
|
6f202b3a97 | ||
aa73377fb5 | |||
|
6b1e5195ba | ||
cbbcb2c10a | |||
|
7d7fc39779 | ||
|
483eda5adc | ||
25d47a5b01 | |||
0e1531f6a3 | |||
ce9b97f24f | |||
82d14b4ea7 | |||
5beb4bc7d9 | |||
|
c42770b800 | ||
30c852913b | |||
97ee4eaa33 | |||
|
d77c36d479 | ||
|
4696079471 | ||
|
bf3c302813 | ||
a36801b7ee | |||
3b81209099 | |||
25abc921d5 | |||
d3ef82346d | |||
|
df72327b91 | ||
|
68e3240e8e | ||
|
f1671c0150 | ||
|
15cd1aaaa5 | ||
|
d9bae52513 | ||
|
a39577fb6a | ||
|
f5a44c602c | ||
|
676efbae2f | ||
|
39b8b610d6 | ||
|
4667c94f11 | ||
|
3d34b06e4f | ||
|
0e859116bc | ||
1206fecf93 | |||
e091caef5d | |||
d8508301cf | |||
|
03dd750f7c | ||
|
5914e610a1 | ||
|
48841609ba | ||
|
dfc0ac0ea8 | ||
d0bc3d0d3b | |||
683eb62973 | |||
|
b0cad975e4 | ||
|
488d3995e7 | ||
8eeab44898 | |||
|
668e034041 | ||
|
366afe2374 | ||
|
0812a98bf9 | ||
|
eab671950b | ||
|
8409d351ea | ||
|
c76406592c | ||
|
d7b0517712 | ||
|
c2f50bce8f | ||
|
476cb410f7 | ||
|
065f490a4d | ||
|
ebadb2725f | ||
|
92bd0ee0e5 | ||
|
aa5de41ce2 | ||
|
9b6c5ab540 | ||
|
138d6faba6 | ||
|
41fbaf40ea | ||
|
55cf5a1087 | ||
|
545b411d9e | ||
|
837fdb4c6c | ||
|
1d031ac40b | ||
|
0311bca40e | ||
|
e805df017f | ||
72fdde0445 | |||
|
dd87cf534f | ||
|
9f40381716 | ||
13a16ef7c1 | |||
3278aa8802 | |||
|
ba4e62210e | ||
|
57243bd002 | ||
|
c93f928a96 | ||
|
a9064a3e6d | ||
|
794a933cea | ||
|
f316049a33 | ||
|
d8b074332d | ||
|
2f0964f518 | ||
8ea3fec0c5 | |||
|
d96abdeb4e | ||
|
445661115d | ||
|
54c3df0490 | ||
|
722ffb0e6c | ||
|
2609b2bcba | ||
|
dd2898428b | ||
1efb59b511 | |||
|
42a6cb435b | ||
8449d8730a | |||
d593678b56 | |||
|
43a8e736a4 | ||
b20cb5c14b | |||
acea44e64f | |||
46bc76270f | |||
c3fbdcab7a | |||
3eff969d95 | |||
|
3d4b972933 | ||
a4bde9db62 | |||
|
8b3c1714af | ||
|
fd478d9abc | ||
|
7c6d4dc0ab | ||
0f45d7e871 | |||
|
71471c8e40 | ||
0a21e356d0 | |||
1741eece94 | |||
4da14b61c1 | |||
b2aa849c1e | |||
a149084e9a | |||
36040a5765 | |||
5098a36a6b | |||
241e452ecf | |||
31c35661ae | |||
ad418b5905 | |||
78ef338fe3 | |||
017f5600e7 | |||
ef9ccf1615 | |||
46ed1f8fa4 | |||
edaf9eaaa6 | |||
2f6c352a57 | |||
e6f7ceb900 | |||
bc66ccb9d3 | |||
d907057790 | |||
ff05219e28 | |||
4209736cb8 | |||
0d3d13b7ab | |||
a41e085d18 |
19
.github/dependabot.yml
vendored
Normal file
19
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
|
||||||
|
# Maintain dependencies for GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
|
||||||
|
# Maintain dependencies for Golang
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
13
.github/stale.sh
vendored
13
.github/stale.sh
vendored
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash -ex
|
|
||||||
|
|
||||||
export PATH=$PATH:$(pwd)/bin
|
|
||||||
export GO111MODULE=on
|
|
||||||
export GOBIN=$(pwd)/bin
|
|
||||||
|
|
||||||
#go get github.com/rvflash/goup@v0.4.1
|
|
||||||
|
|
||||||
#goup -v ./...
|
|
||||||
#go get github.com/psampaz/go-mod-outdated@v0.6.0
|
|
||||||
go list -u -m -mod=mod -json all | go-mod-outdated -update -direct -ci || true
|
|
||||||
|
|
||||||
#go list -u -m -json all | go-mod-outdated -update
|
|
20
.github/workflows/autoapprove.yml
vendored
Normal file
20
.github/workflows/autoapprove.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: "autoapprove"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [assigned, opened, synchronize, reopened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
autoapprove:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: approve
|
||||||
|
uses: hmarr/auto-approve-action@v3
|
||||||
|
if: github.actor == 'vtolstov' || github.actor == 'dependabot[bot]'
|
||||||
|
id: approve
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
21
.github/workflows/automerge.yml
vendored
Normal file
21
.github/workflows/automerge.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: "automerge"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [assigned, opened, synchronize, reopened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
automerge:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.actor == 'vtolstov'
|
||||||
|
steps:
|
||||||
|
- name: merge
|
||||||
|
id: merge
|
||||||
|
run: gh pr merge --auto --merge "$PR_URL"
|
||||||
|
env:
|
||||||
|
PR_URL: ${{github.event.pull_request.html_url}}
|
||||||
|
GITHUB_TOKEN: ${{secrets.TOKEN}}
|
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -3,19 +3,20 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- v3
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: test
|
name: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: setup
|
- name: setup
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.15
|
go-version: 1.17
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: cache
|
- name: cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
@@ -31,9 +32,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: lint
|
- name: lint
|
||||||
uses: golangci/golangci-lint-action@v1
|
uses: golangci/golangci-lint-action@v3.4.0
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||||
|
78
.github/workflows/codeql-analysis.yml
vendored
Normal file
78
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "codeql"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["prbuild"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
push:
|
||||||
|
branches: [ master, v3 ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master, v3 ]
|
||||||
|
schedule:
|
||||||
|
- cron: '34 1 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: setup
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.17
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: init
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: analyze
|
||||||
|
uses: github/codeql-action/analyze@v2
|
27
.github/workflows/dependabot-automerge.yml
vendored
Normal file
27
.github/workflows/dependabot-automerge.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: "dependabot-automerge"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [assigned, opened, synchronize, reopened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
automerge:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.actor == 'dependabot[bot]'
|
||||||
|
steps:
|
||||||
|
- name: metadata
|
||||||
|
id: metadata
|
||||||
|
uses: dependabot/fetch-metadata@v1.3.6
|
||||||
|
with:
|
||||||
|
github-token: "${{ secrets.TOKEN }}"
|
||||||
|
- name: merge
|
||||||
|
id: merge
|
||||||
|
if: ${{contains(steps.metadata.outputs.dependency-names, 'go.unistack.org')}}
|
||||||
|
run: gh pr merge --auto --merge "$PR_URL"
|
||||||
|
env:
|
||||||
|
PR_URL: ${{github.event.pull_request.html_url}}
|
||||||
|
GITHUB_TOKEN: ${{secrets.TOKEN}}
|
13
.github/workflows/pr.yml
vendored
13
.github/workflows/pr.yml
vendored
@@ -3,19 +3,20 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- v3
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: test
|
name: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: setup
|
- name: setup
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.15
|
go-version: 1.17
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: cache
|
- name: cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
@@ -31,9 +32,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: lint
|
- name: lint
|
||||||
uses: golangci/golangci-lint-action@v1
|
uses: golangci/golangci-lint-action@v3.4.0
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||||
|
44
.golangci.yml
Normal file
44
.golangci.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
run:
|
||||||
|
concurrency: 4
|
||||||
|
deadline: 5m
|
||||||
|
issues-exit-code: 1
|
||||||
|
tests: true
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
enable:
|
||||||
|
- fieldalignment
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- govet
|
||||||
|
- deadcode
|
||||||
|
- errcheck
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
- bodyclose
|
||||||
|
- gci
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gosimple
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- golint
|
||||||
|
- gosec
|
||||||
|
- makezero
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nilerr
|
||||||
|
- noctx
|
||||||
|
- prealloc
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
disable-all: false
|
192
LICENSE
Normal file
192
LICENSE
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2015-2020 Asim Aslam.
|
||||||
|
Copyright 2019-2020 Unistack LLC.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
30
README.md
30
README.md
@@ -1,30 +0,0 @@
|
|||||||
# GRPC Server
|
|
||||||
|
|
||||||
The grpc server is a [micro.Server](https://godoc.org/github.com/micro/go-micro/server#Server) compatible server.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The server makes use of the [google.golang.org/grpc](google.golang.org/grpc) framework for the underlying server
|
|
||||||
but continues to use micro handler signatures and protoc-gen-micro generated code.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Specify the server to your micro service
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"github.com/micro/go-micro"
|
|
||||||
"github.com/micro/go-plugins/server/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
service := micro.NewService(
|
|
||||||
// This needs to be first as it replaces the underlying server
|
|
||||||
// which causes any configuration set before it
|
|
||||||
// to be discarded
|
|
||||||
micro.Server(grpc.NewServer()),
|
|
||||||
micro.Name("greeter"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**NOTE**: Setting the gRPC server and/or client causes the underlying the server/client to be replaced which causes any previous configuration set on that server/client to be discarded. It is therefore recommended to set gRPC server/client before any other configuration
|
|
232
codec.go
232
codec.go
@@ -1,233 +1,77 @@
|
|||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
b "bytes"
|
"io"
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
oldjsonpb "github.com/golang/protobuf/jsonpb"
|
"go.unistack.org/micro/v3/codec"
|
||||||
oldproto "github.com/golang/protobuf/proto"
|
|
||||||
bytes "github.com/unistack-org/micro-codec-bytes"
|
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/encoding"
|
"google.golang.org/grpc/encoding"
|
||||||
"google.golang.org/grpc/metadata"
|
|
||||||
jsonpb "google.golang.org/protobuf/encoding/protojson"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type jsonCodec struct{}
|
|
||||||
type bytesCodec struct{}
|
|
||||||
type protoCodec struct{}
|
|
||||||
type wrapCodec struct{ encoding.Codec }
|
|
||||||
|
|
||||||
var (
|
|
||||||
jsonpbMarshaler = jsonpb.MarshalOptions{
|
|
||||||
UseEnumNumbers: false,
|
|
||||||
EmitUnpopulated: false,
|
|
||||||
UseProtoNames: true,
|
|
||||||
AllowPartial: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonpbUnmarshaler = jsonpb.UnmarshalOptions{
|
|
||||||
DiscardUnknown: false,
|
|
||||||
AllowPartial: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
oldjsonpbMarshaler = oldjsonpb.Marshaler{
|
|
||||||
OrigName: true,
|
|
||||||
EmitDefaults: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
oldjsonpbUnmarshaler = oldjsonpb.Unmarshaler{
|
|
||||||
AllowUnknownFields: false,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultGRPCCodecs = map[string]encoding.Codec{
|
_ codec.Codec = &wrapGrpcCodec{}
|
||||||
"application/json": jsonCodec{},
|
_ encoding.Codec = &wrapMicroCodec{}
|
||||||
"application/proto": protoCodec{},
|
|
||||||
"application/protobuf": protoCodec{},
|
|
||||||
"application/octet-stream": protoCodec{},
|
|
||||||
"application/grpc": protoCodec{},
|
|
||||||
"application/grpc+json": jsonCodec{},
|
|
||||||
"application/grpc+proto": protoCodec{},
|
|
||||||
"application/grpc+bytes": bytesCodec{},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (w wrapCodec) String() string {
|
type wrapMicroCodec struct{ codec.Codec }
|
||||||
|
|
||||||
|
func (w *wrapMicroCodec) Name() string {
|
||||||
|
return w.Codec.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapMicroCodec) Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return w.Codec.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapMicroCodec) Unmarshal(d []byte, v interface{}) error {
|
||||||
|
return w.Codec.Unmarshal(d, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapGrpcCodec struct{ encoding.Codec }
|
||||||
|
|
||||||
|
func (w *wrapGrpcCodec) String() string {
|
||||||
return w.Codec.Name()
|
return w.Codec.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w wrapCodec) Marshal(v interface{}) ([]byte, error) {
|
func (w *wrapGrpcCodec) Marshal(v interface{}, opts ...codec.Option) ([]byte, error) {
|
||||||
switch m := v.(type) {
|
if m, ok := v.(*codec.Frame); ok {
|
||||||
case *bytes.Frame:
|
|
||||||
return m.Data, nil
|
return m.Data, nil
|
||||||
}
|
}
|
||||||
return w.Codec.Marshal(v)
|
return w.Codec.Marshal(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w wrapCodec) Unmarshal(data []byte, v interface{}) error {
|
func (w *wrapGrpcCodec) Unmarshal(d []byte, v interface{}, opts ...codec.Option) error {
|
||||||
if len(data) == 0 {
|
if d == nil || v == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if v == nil {
|
if m, ok := v.(*codec.Frame); ok {
|
||||||
|
m.Data = d
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch m := v.(type) {
|
return w.Codec.Unmarshal(d, v)
|
||||||
case *bytes.Frame:
|
|
||||||
m.Data = data
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return w.Codec.Unmarshal(data, v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (protoCodec) Marshal(v interface{}) ([]byte, error) {
|
func (w *wrapGrpcCodec) ReadHeader(conn io.Reader, m *codec.Message, mt codec.MessageType) error {
|
||||||
switch m := v.(type) {
|
|
||||||
case proto.Message:
|
|
||||||
return proto.Marshal(m)
|
|
||||||
case oldproto.Message:
|
|
||||||
return oldproto.Marshal(m)
|
|
||||||
}
|
|
||||||
return nil, codec.ErrInvalidMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (protoCodec) Unmarshal(data []byte, v interface{}) error {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch m := v.(type) {
|
|
||||||
case proto.Message:
|
|
||||||
return proto.Unmarshal(data, m)
|
|
||||||
case oldproto.Message:
|
|
||||||
return oldproto.Unmarshal(data, m)
|
|
||||||
}
|
|
||||||
return codec.ErrInvalidMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (protoCodec) Name() string {
|
|
||||||
return "proto"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (jsonCodec) Marshal(v interface{}) ([]byte, error) {
|
|
||||||
switch m := v.(type) {
|
|
||||||
case proto.Message:
|
|
||||||
return jsonpbMarshaler.Marshal(m)
|
|
||||||
case oldproto.Message:
|
|
||||||
buf := b.NewBuffer(nil)
|
|
||||||
err := oldjsonpbMarshaler.Marshal(buf, m)
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
|
||||||
return json.Marshal(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch m := v.(type) {
|
|
||||||
case proto.Message:
|
|
||||||
return jsonpbUnmarshaler.Unmarshal(data, m)
|
|
||||||
case oldproto.Message:
|
|
||||||
return oldjsonpbUnmarshaler.Unmarshal(b.NewReader(data), m)
|
|
||||||
}
|
|
||||||
return json.Unmarshal(data, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (jsonCodec) Name() string {
|
|
||||||
return "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bytesCodec) Marshal(v interface{}) ([]byte, error) {
|
|
||||||
switch m := v.(type) {
|
|
||||||
case *[]byte:
|
|
||||||
return *m, nil
|
|
||||||
}
|
|
||||||
return nil, codec.ErrInvalidMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bytesCodec) Unmarshal(data []byte, v interface{}) error {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch m := v.(type) {
|
|
||||||
case *[]byte:
|
|
||||||
*m = data
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return codec.ErrInvalidMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bytesCodec) Name() string {
|
|
||||||
return "bytes"
|
|
||||||
}
|
|
||||||
|
|
||||||
type grpcCodec struct {
|
|
||||||
grpc.ServerStream
|
|
||||||
// headers
|
|
||||||
id string
|
|
||||||
target string
|
|
||||||
method string
|
|
||||||
endpoint string
|
|
||||||
|
|
||||||
c encoding.Codec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *grpcCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
|
|
||||||
md, _ := metadata.FromIncomingContext(g.ServerStream.Context())
|
|
||||||
if m == nil {
|
|
||||||
m = new(codec.Message)
|
|
||||||
}
|
|
||||||
if m.Header == nil {
|
|
||||||
m.Header = make(map[string]string, len(md))
|
|
||||||
}
|
|
||||||
for k, v := range md {
|
|
||||||
m.Header[k] = strings.Join(v, ",")
|
|
||||||
}
|
|
||||||
m.Id = g.id
|
|
||||||
m.Target = g.target
|
|
||||||
m.Method = g.method
|
|
||||||
m.Endpoint = g.endpoint
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *grpcCodec) ReadBody(v interface{}) error {
|
func (w *wrapGrpcCodec) ReadBody(conn io.Reader, v interface{}) error {
|
||||||
// caller has requested a frame
|
if m, ok := v.(*codec.Frame); ok {
|
||||||
switch m := v.(type) {
|
_, err := conn.Read(m.Data)
|
||||||
case *bytes.Frame:
|
return err
|
||||||
return g.ServerStream.RecvMsg(m)
|
|
||||||
}
|
}
|
||||||
return g.ServerStream.RecvMsg(v)
|
return codec.ErrInvalidMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *grpcCodec) Write(m *codec.Message, v interface{}) error {
|
func (w *wrapGrpcCodec) Write(conn io.Writer, m *codec.Message, v interface{}) error {
|
||||||
// if we don't have a body
|
// if we don't have a body
|
||||||
if v != nil {
|
if v != nil {
|
||||||
b, err := g.c.Marshal(v)
|
b, err := w.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.Body = b
|
m.Body = b
|
||||||
}
|
}
|
||||||
// write the body using the framing codec
|
// write the body using the framing codec
|
||||||
return g.ServerStream.SendMsg(&bytes.Frame{Data: m.Body})
|
_, err := conn.Write(m.Body)
|
||||||
}
|
return err
|
||||||
|
|
||||||
func (g *grpcCodec) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *grpcCodec) String() string {
|
|
||||||
return "grpc"
|
|
||||||
}
|
}
|
||||||
|
16
context.go
16
context.go
@@ -1,16 +0,0 @@
|
|||||||
package grpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setServerOption(k, v interface{}) server.Option {
|
|
||||||
return func(o *server.Options) {
|
|
||||||
if o.Context == nil {
|
|
||||||
o.Context = context.Background()
|
|
||||||
}
|
|
||||||
o.Context = context.WithValue(o.Context, k, v)
|
|
||||||
}
|
|
||||||
}
|
|
49
error.go
49
error.go
@@ -6,26 +6,23 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
pb "github.com/unistack-org/micro-server-grpc/internal/errors"
|
"go.unistack.org/micro/v3/errors"
|
||||||
"github.com/unistack-org/micro/v3/errors"
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var errMapping = map[int32]codes.Code{
|
||||||
errMapping = map[int32]codes.Code{
|
http.StatusOK: codes.OK,
|
||||||
http.StatusOK: codes.OK,
|
http.StatusBadRequest: codes.InvalidArgument,
|
||||||
http.StatusBadRequest: codes.InvalidArgument,
|
http.StatusRequestTimeout: codes.DeadlineExceeded,
|
||||||
http.StatusRequestTimeout: codes.DeadlineExceeded,
|
http.StatusNotFound: codes.NotFound,
|
||||||
http.StatusNotFound: codes.NotFound,
|
http.StatusConflict: codes.AlreadyExists,
|
||||||
http.StatusConflict: codes.AlreadyExists,
|
http.StatusForbidden: codes.PermissionDenied,
|
||||||
http.StatusForbidden: codes.PermissionDenied,
|
http.StatusUnauthorized: codes.Unauthenticated,
|
||||||
http.StatusUnauthorized: codes.Unauthenticated,
|
http.StatusPreconditionFailed: codes.FailedPrecondition,
|
||||||
http.StatusPreconditionFailed: codes.FailedPrecondition,
|
http.StatusNotImplemented: codes.Unimplemented,
|
||||||
http.StatusNotImplemented: codes.Unimplemented,
|
http.StatusInternalServerError: codes.Internal,
|
||||||
http.StatusInternalServerError: codes.Internal,
|
http.StatusServiceUnavailable: codes.Unavailable,
|
||||||
http.StatusServiceUnavailable: codes.Unavailable,
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// convertCode converts a standard Go error into its canonical code. Note that
|
// convertCode converts a standard Go error into its canonical code. Note that
|
||||||
// this is only used to translate the error returned by the server applications.
|
// this is only used to translate the error returned by the server applications.
|
||||||
@@ -61,10 +58,7 @@ func microError(err error) codes.Code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ec int32
|
var ec int32
|
||||||
switch verr := err.(type) {
|
if verr, ok := err.(*errors.Error); ok {
|
||||||
case *errors.Error:
|
|
||||||
ec = verr.Code
|
|
||||||
case *pb.Error:
|
|
||||||
ec = verr.Code
|
ec = verr.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,16 +68,3 @@ func microError(err error) codes.Code {
|
|||||||
|
|
||||||
return codes.Unknown
|
return codes.Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func pbError(err error) *pb.Error {
|
|
||||||
switch verr := err.(type) {
|
|
||||||
case nil:
|
|
||||||
return nil
|
|
||||||
case *errors.Error:
|
|
||||||
return &pb.Error{Id: verr.Id, Code: verr.Code, Detail: verr.Detail, Status: verr.Status}
|
|
||||||
case *pb.Error:
|
|
||||||
return verr
|
|
||||||
default:
|
|
||||||
return &pb.Error{Code: 500, Detail: err.Error()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
package grpc_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
pb "github.com/unistack-org/micro-server-grpc/internal/errors"
|
|
||||||
"google.golang.org/protobuf/types/known/anypb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestErrors(t *testing.T) {
|
|
||||||
any, err := anypb.New(&pb.Error{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("srv any: %#+v\n", any)
|
|
||||||
}
|
|
122
extractor.go
122
extractor.go
@@ -1,122 +0,0 @@
|
|||||||
package grpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func extractValue(v reflect.Type, d int) *registry.Value {
|
|
||||||
if d == 3 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Kind() == reflect.Ptr {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
arg := ®istry.Value{
|
|
||||||
Name: v.Name(),
|
|
||||||
Type: v.Name(),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
f := v.Field(i)
|
|
||||||
val := extractValue(f.Type, d+1)
|
|
||||||
if val == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we can find a json tag use it
|
|
||||||
if tags := f.Tag.Get("json"); len(tags) > 0 {
|
|
||||||
parts := strings.Split(tags, ",")
|
|
||||||
if parts[0] == "-" || parts[0] == "omitempty" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val.Name = parts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's no name default it
|
|
||||||
if len(val.Name) == 0 {
|
|
||||||
val.Name = v.Field(i).Name
|
|
||||||
}
|
|
||||||
|
|
||||||
arg.Values = append(arg.Values, val)
|
|
||||||
}
|
|
||||||
case reflect.Slice:
|
|
||||||
p := v.Elem()
|
|
||||||
if p.Kind() == reflect.Ptr {
|
|
||||||
p = p.Elem()
|
|
||||||
}
|
|
||||||
arg.Type = "[]" + p.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
return arg
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractEndpoint(method reflect.Method) *registry.Endpoint {
|
|
||||||
if method.PkgPath != "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var rspType, reqType reflect.Type
|
|
||||||
var stream bool
|
|
||||||
mt := method.Type
|
|
||||||
|
|
||||||
switch mt.NumIn() {
|
|
||||||
case 3:
|
|
||||||
reqType = mt.In(1)
|
|
||||||
rspType = mt.In(2)
|
|
||||||
case 4:
|
|
||||||
reqType = mt.In(2)
|
|
||||||
rspType = mt.In(3)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// are we dealing with a stream?
|
|
||||||
switch rspType.Kind() {
|
|
||||||
case reflect.Func, reflect.Interface:
|
|
||||||
stream = true
|
|
||||||
}
|
|
||||||
|
|
||||||
request := extractValue(reqType, 0)
|
|
||||||
response := extractValue(rspType, 0)
|
|
||||||
|
|
||||||
ep := ®istry.Endpoint{
|
|
||||||
Name: method.Name,
|
|
||||||
Request: request,
|
|
||||||
Response: response,
|
|
||||||
Metadata: make(map[string]string),
|
|
||||||
}
|
|
||||||
|
|
||||||
if stream {
|
|
||||||
ep.Metadata = map[string]string{
|
|
||||||
"stream": fmt.Sprintf("%v", stream),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ep
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractSubValue(typ reflect.Type) *registry.Value {
|
|
||||||
var reqType reflect.Type
|
|
||||||
switch typ.NumIn() {
|
|
||||||
case 1:
|
|
||||||
reqType = typ.In(0)
|
|
||||||
case 2:
|
|
||||||
reqType = typ.In(1)
|
|
||||||
case 3:
|
|
||||||
reqType = typ.In(2)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return extractValue(reqType, 0)
|
|
||||||
}
|
|
@@ -1,65 +0,0 @@
|
|||||||
package grpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testHandler struct{}
|
|
||||||
|
|
||||||
type testRequest struct{}
|
|
||||||
|
|
||||||
type testResponse struct{}
|
|
||||||
|
|
||||||
func (t *testHandler) Test(ctx context.Context, req *testRequest, rsp *testResponse) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtractEndpoint(t *testing.T) {
|
|
||||||
handler := &testHandler{}
|
|
||||||
typ := reflect.TypeOf(handler)
|
|
||||||
|
|
||||||
var endpoints []*registry.Endpoint
|
|
||||||
|
|
||||||
for m := 0; m < typ.NumMethod(); m++ {
|
|
||||||
if e := extractEndpoint(typ.Method(m)); e != nil {
|
|
||||||
endpoints = append(endpoints, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i := len(endpoints); i != 1 {
|
|
||||||
t.Errorf("Expected 1 endpoint, have %d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoints[0].Name != "Test" {
|
|
||||||
t.Errorf("Expected handler Test, got %s", endpoints[0].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoints[0].Request == nil {
|
|
||||||
t.Error("Expected non nil request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoints[0].Response == nil {
|
|
||||||
t.Error("Expected non nil request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoints[0].Request.Name != "testRequest" {
|
|
||||||
t.Errorf("Expected testRequest got %s", endpoints[0].Request.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoints[0].Response.Name != "testResponse" {
|
|
||||||
t.Errorf("Expected testResponse got %s", endpoints[0].Response.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoints[0].Request.Type != "testRequest" {
|
|
||||||
t.Errorf("Expected testRequest type got %s", endpoints[0].Request.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoints[0].Response.Type != "testResponse" {
|
|
||||||
t.Errorf("Expected testResponse type got %s", endpoints[0].Response.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
package grpc
|
|
||||||
|
|
||||||
//go:generate protoc -I./internal/errors -I. --go_out=paths=source_relative:./internal/errors internal/errors/server_errors.proto
|
|
19
go.mod
19
go.mod
@@ -1,16 +1,11 @@
|
|||||||
module github.com/unistack-org/micro-server-grpc
|
module go.unistack.org/micro-server-grpc/v3
|
||||||
|
|
||||||
go 1.15
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.4.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/google/go-cmp v0.5.1 // indirect
|
go.unistack.org/micro/v3 v3.10.14
|
||||||
github.com/unistack-org/micro-codec-bytes v0.0.0-20200828083432-4e49e953d844
|
golang.org/x/net v0.5.0
|
||||||
github.com/unistack-org/micro/v3 v3.0.0-gamma.0.20200920124807-9b11ea527aeb
|
google.golang.org/grpc v1.52.3
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73
|
google.golang.org/protobuf v1.28.1
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 // indirect
|
|
||||||
golang.org/x/text v0.3.3 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
google.golang.org/grpc v1.31.1
|
|
||||||
google.golang.org/protobuf v1.25.0
|
|
||||||
)
|
)
|
||||||
|
26
handler.go
26
handler.go
@@ -3,34 +3,28 @@ package grpc
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/registry"
|
"go.unistack.org/micro/v3/register"
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v3/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rpcHandler struct {
|
type rpcHandler struct {
|
||||||
name string
|
|
||||||
handler interface{}
|
|
||||||
endpoints []*registry.Endpoint
|
|
||||||
opts server.HandlerOptions
|
opts server.HandlerOptions
|
||||||
|
handler interface{}
|
||||||
|
name string
|
||||||
|
endpoints []*register.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRpcHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
|
func newRPCHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
|
||||||
options := server.HandlerOptions{
|
options := server.NewHandlerOptions(opts...)
|
||||||
Metadata: make(map[string]map[string]string),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := reflect.TypeOf(handler)
|
typ := reflect.TypeOf(handler)
|
||||||
hdlr := reflect.ValueOf(handler)
|
hdlr := reflect.ValueOf(handler)
|
||||||
name := reflect.Indirect(hdlr).Type().Name()
|
name := reflect.Indirect(hdlr).Type().Name()
|
||||||
|
|
||||||
var endpoints []*registry.Endpoint
|
var endpoints []*register.Endpoint
|
||||||
|
|
||||||
for m := 0; m < typ.NumMethod(); m++ {
|
for m := 0; m < typ.NumMethod(); m++ {
|
||||||
if e := extractEndpoint(typ.Method(m)); e != nil {
|
if e := register.ExtractEndpoint(typ.Method(m)); e != nil {
|
||||||
e.Name = name + "." + e.Name
|
e.Name = name + "." + e.Name
|
||||||
|
|
||||||
for k, v := range options.Metadata[e.Name] {
|
for k, v := range options.Metadata[e.Name] {
|
||||||
@@ -57,7 +51,7 @@ func (r *rpcHandler) Handler() interface{} {
|
|||||||
return r.handler
|
return r.handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcHandler) Endpoints() []*registry.Endpoint {
|
func (r *rpcHandler) Endpoints() []*register.Endpoint {
|
||||||
return r.endpoints
|
return r.endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,159 +0,0 @@
|
|||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
return fmt.Sprintf(`{"id":"%s","code":%d,"detail":"%s","status":"%s"}`, e.Id, e.Code, e.Detail, e.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// New generates a custom error.
|
|
||||||
func New(id, detail string, code int32) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: code,
|
|
||||||
Detail: detail,
|
|
||||||
// Status: http.StatusText(int(code)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BadRequest generates a 400 error.
|
|
||||||
func BadRequest(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 400,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
// Status: http.StatusText(400),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unauthorized generates a 401 error.
|
|
||||||
func Unauthorized(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 401,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
// Status: http.StatusText(401),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forbidden generates a 403 error.
|
|
||||||
func Forbidden(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 403,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
// Status: http.StatusText(403),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotFound generates a 404 error.
|
|
||||||
func NotFound(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 404,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
// Status: http.StatusText(404),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodNotAllowed generates a 405 error.
|
|
||||||
func MethodNotAllowed(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 405,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
//Status: http.StatusText(405),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timeout generates a 408 error.
|
|
||||||
func Timeout(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 408,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
//Status: http.StatusText(408),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conflict generates a 409 error.
|
|
||||||
func Conflict(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 409,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
//Status: http.StatusText(409),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalServerError generates a 500 error.
|
|
||||||
func InternalServerError(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 500,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
//Status: http.StatusText(500),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotImplemented generates a 501 error
|
|
||||||
func NotImplemented(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 501,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
//Status: http.StatusText(501),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BadGateway generates a 502 error
|
|
||||||
func BadGateway(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 502,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
// Status: http.StatusText(502),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceUnavailable generates a 503 error
|
|
||||||
func ServiceUnavailable(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 503,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
//Status: http.StatusText(503),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GatewayTimeout generates a 504 error
|
|
||||||
func GatewayTimeout(id, format string, a ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
Id: id,
|
|
||||||
Code: 504,
|
|
||||||
Detail: fmt.Sprintf(format, a...),
|
|
||||||
//Status: http.StatusText(504),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tries to compare errors
|
|
||||||
func Equal(err1 error, err2 error) bool {
|
|
||||||
verr1, ok1 := err1.(*Error)
|
|
||||||
verr2, ok2 := err2.(*Error)
|
|
||||||
|
|
||||||
if ok1 != ok2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok1 {
|
|
||||||
return err1 == err2
|
|
||||||
}
|
|
||||||
|
|
||||||
if verr1.Code != verr2.Code {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
@@ -1,179 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.25.0-devel
|
|
||||||
// protoc v3.6.1
|
|
||||||
// source: server_errors.proto
|
|
||||||
|
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
|
||||||
// of the legacy proto package is being used.
|
|
||||||
const _ = proto.ProtoPackageIsVersion4
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
|
||||||
Code int32 `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"`
|
|
||||||
Detail string `protobuf:"bytes,3,opt,name=detail,proto3" json:"detail,omitempty"`
|
|
||||||
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Error) Reset() {
|
|
||||||
*x = Error{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_server_errors_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Error) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Error) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Error) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_server_errors_proto_msgTypes[0]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Error.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Error) Descriptor() ([]byte, []int) {
|
|
||||||
return file_server_errors_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Error) GetId() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Id
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Error) GetCode() int32 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Code
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Error) GetDetail() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Detail
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Error) GetStatus() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Status
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_server_errors_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
var file_server_errors_proto_rawDesc = []byte{
|
|
||||||
0x0a, 0x13, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e,
|
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x6f, 0x72, 0x67, 0x2e, 0x75, 0x6e, 0x69, 0x73, 0x74,
|
|
||||||
0x61, 0x63, 0x6b, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73,
|
|
||||||
0x22, 0x5b, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64,
|
|
||||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a,
|
|
||||||
0x06, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64,
|
|
||||||
0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
|
|
||||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x3b, 0x5a,
|
|
||||||
0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x6e, 0x69, 0x73,
|
|
||||||
0x74, 0x61, 0x63, 0x6b, 0x2d, 0x6f, 0x72, 0x67, 0x2f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x2d, 0x73,
|
|
||||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
|
||||||
0x6e, 0x61, 0x6c, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
|
||||||
0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_server_errors_proto_rawDescOnce sync.Once
|
|
||||||
file_server_errors_proto_rawDescData = file_server_errors_proto_rawDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_server_errors_proto_rawDescGZIP() []byte {
|
|
||||||
file_server_errors_proto_rawDescOnce.Do(func() {
|
|
||||||
file_server_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_server_errors_proto_rawDescData)
|
|
||||||
})
|
|
||||||
return file_server_errors_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_server_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
|
||||||
var file_server_errors_proto_goTypes = []interface{}{
|
|
||||||
(*Error)(nil), // 0: org.unistack.micro.errors.Error
|
|
||||||
}
|
|
||||||
var file_server_errors_proto_depIdxs = []int32{
|
|
||||||
0, // [0:0] is the sub-list for method output_type
|
|
||||||
0, // [0:0] is the sub-list for method input_type
|
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
|
||||||
0, // [0:0] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_server_errors_proto_init() }
|
|
||||||
func file_server_errors_proto_init() {
|
|
||||||
if File_server_errors_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_server_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*Error); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: file_server_errors_proto_rawDesc,
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 1,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 0,
|
|
||||||
},
|
|
||||||
GoTypes: file_server_errors_proto_goTypes,
|
|
||||||
DependencyIndexes: file_server_errors_proto_depIdxs,
|
|
||||||
MessageInfos: file_server_errors_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_server_errors_proto = out.File
|
|
||||||
file_server_errors_proto_rawDesc = nil
|
|
||||||
file_server_errors_proto_goTypes = nil
|
|
||||||
file_server_errors_proto_depIdxs = nil
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option go_package = "github.com/unistack-org/micro-server-grpc/internal/errors";
|
|
||||||
package org.unistack.micro.errors;
|
|
||||||
|
|
||||||
message Error {
|
|
||||||
string id = 1;
|
|
||||||
int32 code = 2;
|
|
||||||
string detail = 3;
|
|
||||||
string status = 4;
|
|
||||||
};
|
|
47
options.go
47
options.go
@@ -2,21 +2,19 @@ package grpc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v3/server"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/encoding"
|
"google.golang.org/grpc/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
type codecsKey struct{}
|
type (
|
||||||
type grpcOptions struct{}
|
codecsKey struct{}
|
||||||
type netListener struct{}
|
grpcOptions struct{}
|
||||||
type maxMsgSizeKey struct{}
|
maxMsgSizeKey struct{}
|
||||||
type maxConnKey struct{}
|
reflectionKey struct{}
|
||||||
type tlsAuth struct{}
|
unknownServiceHandlerKey struct{}
|
||||||
type reflectionKey struct{}
|
)
|
||||||
|
|
||||||
// gRPC Codec to be used to encode/decode requests for a given content type
|
// gRPC Codec to be used to encode/decode requests for a given content type
|
||||||
func Codec(contentType string, c encoding.Codec) server.Option {
|
func Codec(contentType string, c encoding.Codec) server.Option {
|
||||||
@@ -33,35 +31,24 @@ func Codec(contentType string, c encoding.Codec) server.Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthTLS should be used to setup a secure authentication using TLS
|
|
||||||
func AuthTLS(t *tls.Config) server.Option {
|
|
||||||
return setServerOption(tlsAuth{}, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxConn specifies maximum number of max simultaneous connections to server
|
|
||||||
func MaxConn(n int) server.Option {
|
|
||||||
return setServerOption(maxConnKey{}, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listener specifies the net.Listener to use instead of the default
|
|
||||||
func Listener(l net.Listener) server.Option {
|
|
||||||
return setServerOption(netListener{}, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options to be used to configure gRPC options
|
// Options to be used to configure gRPC options
|
||||||
func Options(opts ...grpc.ServerOption) server.Option {
|
func Options(opts ...grpc.ServerOption) server.Option {
|
||||||
return setServerOption(grpcOptions{}, opts)
|
return server.SetOption(grpcOptions{}, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// MaxMsgSize set the maximum message in bytes the server can receive and
|
// MaxMsgSize set the maximum message in bytes the server can receive and
|
||||||
// send. Default maximum message size is 4 MB.
|
// send. Default maximum message size is 4 MB.
|
||||||
//
|
|
||||||
func MaxMsgSize(s int) server.Option {
|
func MaxMsgSize(s int) server.Option {
|
||||||
return setServerOption(maxMsgSizeKey{}, s)
|
return server.SetOption(maxMsgSizeKey{}, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reflection enables reflection support in grpc server
|
// Reflection enables reflection support in grpc server
|
||||||
func Reflection(b bool) server.Option {
|
func Reflection(b bool) server.Option {
|
||||||
return setServerOption(reflectionKey{}, b)
|
return server.SetOption(reflectionKey{}, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnknownServiceHandler enables support for all services
|
||||||
|
func UnknownServiceHandler(h grpc.StreamHandler) server.Option {
|
||||||
|
return server.SetOption(unknownServiceHandlerKey{}, h)
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2016 gRPC authors.
|
* Copyright 2016 gRPC authors.
|
||||||
|
54
request.go
54
request.go
@@ -1,28 +1,36 @@
|
|||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"io"
|
||||||
"github.com/unistack-org/micro-codec-bytes"
|
|
||||||
|
"go.unistack.org/micro/v3/codec"
|
||||||
|
"go.unistack.org/micro/v3/metadata"
|
||||||
|
"go.unistack.org/micro/v3/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ server.Request = &rpcRequest{}
|
||||||
|
_ server.Message = &rpcMessage{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type rpcRequest struct {
|
type rpcRequest struct {
|
||||||
service string
|
rw io.ReadWriter
|
||||||
method string
|
|
||||||
contentType string
|
|
||||||
codec codec.Codec
|
|
||||||
header map[string]string
|
|
||||||
body []byte
|
|
||||||
stream bool
|
|
||||||
payload interface{}
|
payload interface{}
|
||||||
|
codec codec.Codec
|
||||||
|
header metadata.Metadata
|
||||||
|
method string
|
||||||
|
endpoint string
|
||||||
|
contentType string
|
||||||
|
service string
|
||||||
|
stream bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcMessage struct {
|
type rpcMessage struct {
|
||||||
|
payload interface{}
|
||||||
|
codec codec.Codec
|
||||||
|
header metadata.Metadata
|
||||||
topic string
|
topic string
|
||||||
contentType string
|
contentType string
|
||||||
payload interface{}
|
|
||||||
header map[string]string
|
|
||||||
body []byte
|
|
||||||
codec codec.Codec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcRequest) ContentType() string {
|
func (r *rpcRequest) ContentType() string {
|
||||||
@@ -38,20 +46,20 @@ func (r *rpcRequest) Method() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcRequest) Endpoint() string {
|
func (r *rpcRequest) Endpoint() string {
|
||||||
return r.method
|
return r.endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcRequest) Codec() codec.Reader {
|
func (r *rpcRequest) Codec() codec.Codec {
|
||||||
return r.codec
|
return r.codec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcRequest) Header() map[string]string {
|
func (r *rpcRequest) Header() metadata.Metadata {
|
||||||
return r.header
|
return r.header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcRequest) Read() ([]byte, error) {
|
func (r *rpcRequest) Read() ([]byte, error) {
|
||||||
f := &bytes.Frame{}
|
f := &codec.Frame{}
|
||||||
if err := r.codec.ReadBody(f); err != nil {
|
if err := r.codec.ReadBody(r.rw, f); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return f.Data, nil
|
return f.Data, nil
|
||||||
@@ -73,18 +81,14 @@ func (r *rpcMessage) Topic() string {
|
|||||||
return r.topic
|
return r.topic
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcMessage) Payload() interface{} {
|
func (r *rpcMessage) Body() interface{} {
|
||||||
return r.payload
|
return r.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcMessage) Header() map[string]string {
|
func (r *rpcMessage) Header() metadata.Metadata {
|
||||||
return r.header
|
return r.header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcMessage) Body() []byte {
|
func (r *rpcMessage) Codec() codec.Codec {
|
||||||
return r.body
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rpcMessage) Codec() codec.Reader {
|
|
||||||
return r.codec
|
return r.codec
|
||||||
}
|
}
|
||||||
|
17
response.go
17
response.go
@@ -1,26 +1,33 @@
|
|||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"io"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v3/codec"
|
||||||
|
"go.unistack.org/micro/v3/metadata"
|
||||||
|
"go.unistack.org/micro/v3/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ server.Response = &rpcResponse{}
|
||||||
|
|
||||||
type rpcResponse struct {
|
type rpcResponse struct {
|
||||||
header map[string]string
|
rw io.ReadWriter
|
||||||
|
header metadata.Metadata
|
||||||
codec codec.Codec
|
codec codec.Codec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcResponse) Codec() codec.Writer {
|
func (r *rpcResponse) Codec() codec.Codec {
|
||||||
return r.codec
|
return r.codec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcResponse) WriteHeader(hdr map[string]string) {
|
func (r *rpcResponse) WriteHeader(hdr metadata.Metadata) {
|
||||||
for k, v := range hdr {
|
for k, v := range hdr {
|
||||||
r.header[k] = v
|
r.header[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcResponse) Write(b []byte) error {
|
func (r *rpcResponse) Write(b []byte) error {
|
||||||
return r.codec.Write(&codec.Message{
|
return r.codec.Write(r.rw, &codec.Message{
|
||||||
Header: r.header,
|
Header: r.header,
|
||||||
Body: b,
|
Body: b,
|
||||||
}, nil)
|
}, nil)
|
||||||
|
92
server.go
92
server.go
@@ -14,38 +14,35 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"go.unistack.org/micro/v3/server"
|
||||||
"github.com/unistack-org/micro/v3/server"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// Precompute the reflect type for error. Can't use error directly
|
||||||
// Precompute the reflect type for error. Can't use error directly
|
// because Typeof takes an empty interface value. This is annoying.
|
||||||
// because Typeof takes an empty interface value. This is annoying.
|
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
|
||||||
)
|
|
||||||
|
|
||||||
type methodType struct {
|
type methodType struct {
|
||||||
method reflect.Method
|
|
||||||
ArgType reflect.Type
|
ArgType reflect.Type
|
||||||
ReplyType reflect.Type
|
ReplyType reflect.Type
|
||||||
ContextType reflect.Type
|
ContextType reflect.Type
|
||||||
|
method reflect.Method
|
||||||
stream bool
|
stream bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type reflectionType func(context.Context, server.Stream) error
|
// type reflectionType func(context.Context, server.Stream) error
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
name string // name of service
|
typ reflect.Type
|
||||||
rcvr reflect.Value // receiver of methods for the service
|
method map[string]*methodType
|
||||||
typ reflect.Type // type of the receiver
|
rcvr reflect.Value
|
||||||
method map[string]*methodType // registered methods
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// server represents an RPC Server.
|
// server represents an RPC Server.
|
||||||
type rServer struct {
|
type rServer struct {
|
||||||
mu sync.RWMutex // protects the serviceMap
|
|
||||||
serviceMap map[string]*service
|
serviceMap map[string]*service
|
||||||
reflection bool
|
mu sync.RWMutex
|
||||||
|
// reflection bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this an exported - upper case - name?
|
// Is this an exported - upper case - name?
|
||||||
@@ -66,7 +63,7 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
|
|||||||
|
|
||||||
// prepareEndpoint() returns a methodType for the provided method or nil
|
// prepareEndpoint() returns a methodType for the provided method or nil
|
||||||
// in case if the method was unsuitable.
|
// in case if the method was unsuitable.
|
||||||
func prepareEndpoint(method reflect.Method) *methodType {
|
func prepareEndpoint(method reflect.Method) (*methodType, error) {
|
||||||
mtype := method.Type
|
mtype := method.Type
|
||||||
mname := method.Name
|
mname := method.Name
|
||||||
var replyType, argType, contextType reflect.Type
|
var replyType, argType, contextType reflect.Type
|
||||||
@@ -74,7 +71,7 @@ func prepareEndpoint(method reflect.Method) *methodType {
|
|||||||
|
|
||||||
// Endpoint() must be exported.
|
// Endpoint() must be exported.
|
||||||
if method.PkgPath != "" {
|
if method.PkgPath != "" {
|
||||||
return nil
|
return nil, fmt.Errorf("Endpoint must be exported")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mtype.NumIn() {
|
switch mtype.NumIn() {
|
||||||
@@ -89,63 +86,41 @@ func prepareEndpoint(method reflect.Method) *methodType {
|
|||||||
replyType = mtype.In(3)
|
replyType = mtype.In(3)
|
||||||
contextType = mtype.In(1)
|
contextType = mtype.In(1)
|
||||||
default:
|
default:
|
||||||
if logger.V(logger.ErrorLevel) {
|
return nil, fmt.Errorf("method %v of %v has wrong number of ins: %v", mname, mtype, mtype.NumIn())
|
||||||
logger.Errorf("method %v of %v has wrong number of ins: %v", mname, mtype, mtype.NumIn())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if stream {
|
switch stream {
|
||||||
|
case true:
|
||||||
// check stream type
|
// check stream type
|
||||||
streamType := reflect.TypeOf((*server.Stream)(nil)).Elem()
|
streamType := reflect.TypeOf((*server.Stream)(nil)).Elem()
|
||||||
if !argType.Implements(streamType) {
|
if !argType.Implements(streamType) {
|
||||||
if logger.V(logger.ErrorLevel) {
|
return nil, fmt.Errorf("%v argument does not implement Streamer interface: %v", mname, argType)
|
||||||
logger.Errorf("%v argument does not implement Streamer interface: %v", mname, argType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
// if not stream check the replyType
|
|
||||||
|
|
||||||
// First arg need not be a pointer.
|
// First arg need not be a pointer.
|
||||||
if !isExportedOrBuiltinType(argType) {
|
if !isExportedOrBuiltinType(argType) {
|
||||||
if logger.V(logger.ErrorLevel) {
|
return nil, fmt.Errorf("%v argument type not exported: %v", mname, argType)
|
||||||
logger.Errorf("%v argument type not exported: %v", mname, argType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if replyType.Kind() != reflect.Ptr {
|
if replyType.Kind() != reflect.Ptr {
|
||||||
if logger.V(logger.ErrorLevel) {
|
return nil, fmt.Errorf("method %v reply type not a pointer: %v", mname, replyType)
|
||||||
logger.Errorf("method %v reply type not a pointer: %v", mname, replyType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply type must be exported.
|
// Reply type must be exported.
|
||||||
if !isExportedOrBuiltinType(replyType) {
|
if !isExportedOrBuiltinType(replyType) {
|
||||||
if logger.V(logger.ErrorLevel) {
|
return nil, fmt.Errorf("method %v reply type not exported: %v", mname, replyType)
|
||||||
logger.Errorf("method %v reply type not exported: %v", mname, replyType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint() needs one out.
|
// Endpoint() needs one out.
|
||||||
if mtype.NumOut() != 1 {
|
if mtype.NumOut() != 1 {
|
||||||
if logger.V(logger.ErrorLevel) {
|
return nil, fmt.Errorf("method %v has wrong number of outs: %v", mname, mtype.NumOut())
|
||||||
logger.Errorf("method %v has wrong number of outs: %v", mname, mtype.NumOut())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
// The return type of the method must be error.
|
// The return type of the method must be error.
|
||||||
if returnType := mtype.Out(0); returnType != typeOfError {
|
if returnType := mtype.Out(0); returnType != typeOfError {
|
||||||
if logger.V(logger.ErrorLevel) {
|
return nil, fmt.Errorf("method %v returns %v not error", mname, returnType.String())
|
||||||
logger.Errorf("method %v returns %v not error", mname, returnType.String())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return &methodType{method: method, ArgType: argType, ReplyType: replyType, ContextType: contextType, stream: stream}
|
return &methodType{method: method, ArgType: argType, ReplyType: replyType, ContextType: contextType, stream: stream}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *rServer) register(rcvr interface{}) error {
|
func (server *rServer) register(rcvr interface{}) error {
|
||||||
@@ -162,11 +137,7 @@ func (server *rServer) register(rcvr interface{}) error {
|
|||||||
return fmt.Errorf("rpc: no service name for type %v", s.typ.String())
|
return fmt.Errorf("rpc: no service name for type %v", s.typ.String())
|
||||||
}
|
}
|
||||||
if !isExported(sname) {
|
if !isExported(sname) {
|
||||||
s := "rpc Register: type " + sname + " is not exported"
|
return fmt.Errorf("rpc Register: type %s is not exported", sname)
|
||||||
if logger.V(logger.ErrorLevel) {
|
|
||||||
logger.Error(s)
|
|
||||||
}
|
|
||||||
return fmt.Errorf(s)
|
|
||||||
}
|
}
|
||||||
if _, present := server.serviceMap[sname]; present {
|
if _, present := server.serviceMap[sname]; present {
|
||||||
return fmt.Errorf("rpc: service already defined: " + sname)
|
return fmt.Errorf("rpc: service already defined: " + sname)
|
||||||
@@ -177,17 +148,16 @@ func (server *rServer) register(rcvr interface{}) error {
|
|||||||
// Install the methods
|
// Install the methods
|
||||||
for m := 0; m < s.typ.NumMethod(); m++ {
|
for m := 0; m < s.typ.NumMethod(); m++ {
|
||||||
method := s.typ.Method(m)
|
method := s.typ.Method(m)
|
||||||
if mt := prepareEndpoint(method); mt != nil {
|
mt, err := prepareEndpoint(method)
|
||||||
|
if mt != nil && err == nil {
|
||||||
s.method[method.Name] = mt
|
s.method[method.Name] = mt
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.method) == 0 {
|
if len(s.method) == 0 {
|
||||||
s := "rpc Register: type " + sname + " has no exported methods of suitable type"
|
return fmt.Errorf("rpc Register: type %s has no exported methods of suitable type", sname)
|
||||||
if logger.V(logger.ErrorLevel) {
|
|
||||||
logger.Error(s)
|
|
||||||
}
|
|
||||||
return fmt.Errorf(s)
|
|
||||||
}
|
}
|
||||||
server.serviceMap[s.name] = s
|
server.serviceMap[s.name] = s
|
||||||
return nil
|
return nil
|
||||||
|
@@ -3,7 +3,7 @@ package grpc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v3/server"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
119
subscriber.go
119
subscriber.go
@@ -7,22 +7,18 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/broker"
|
"go.unistack.org/micro/v3/broker"
|
||||||
"github.com/unistack-org/micro/v3/errors"
|
"go.unistack.org/micro/v3/errors"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"go.unistack.org/micro/v3/logger"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"go.unistack.org/micro/v3/metadata"
|
||||||
"github.com/unistack-org/micro/v3/registry"
|
"go.unistack.org/micro/v3/register"
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v3/server"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
subSig = "func(context.Context, interface{}) error"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
method reflect.Value
|
|
||||||
reqType reflect.Type
|
reqType reflect.Type
|
||||||
ctxType reflect.Type
|
ctxType reflect.Type
|
||||||
|
method reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
type subscriber struct {
|
type subscriber struct {
|
||||||
@@ -31,17 +27,14 @@ type subscriber struct {
|
|||||||
typ reflect.Type
|
typ reflect.Type
|
||||||
subscriber interface{}
|
subscriber interface{}
|
||||||
handlers []*handler
|
handlers []*handler
|
||||||
endpoints []*registry.Endpoint
|
endpoints []*register.Endpoint
|
||||||
opts server.SubscriberOptions
|
opts server.SubscriberOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOption) server.Subscriber {
|
func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOption) server.Subscriber {
|
||||||
options := server.NewSubscriberOptions()
|
options := server.NewSubscriberOptions(opts...)
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoints []*registry.Endpoint
|
var endpoints []*register.Endpoint
|
||||||
var handlers []*handler
|
var handlers []*handler
|
||||||
|
|
||||||
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
|
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
|
||||||
@@ -59,9 +52,9 @@ func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOptio
|
|||||||
|
|
||||||
handlers = append(handlers, h)
|
handlers = append(handlers, h)
|
||||||
|
|
||||||
endpoints = append(endpoints, ®istry.Endpoint{
|
endpoints = append(endpoints, ®ister.Endpoint{
|
||||||
Name: "Func",
|
Name: "Func",
|
||||||
Request: extractSubValue(typ),
|
Request: register.ExtractSubValue(typ),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"topic": topic,
|
"topic": topic,
|
||||||
"subscriber": "true",
|
"subscriber": "true",
|
||||||
@@ -87,9 +80,9 @@ func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOptio
|
|||||||
|
|
||||||
handlers = append(handlers, h)
|
handlers = append(handlers, h)
|
||||||
|
|
||||||
endpoints = append(endpoints, ®istry.Endpoint{
|
endpoints = append(endpoints, ®ister.Endpoint{
|
||||||
Name: name + "." + method.Name,
|
Name: name + "." + method.Name,
|
||||||
Request: extractSubValue(method.Type),
|
Request: register.ExtractSubValue(method.Type),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"topic": topic,
|
"topic": topic,
|
||||||
"subscriber": "true",
|
"subscriber": "true",
|
||||||
@@ -109,67 +102,13 @@ func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOptio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSubscriber(sub server.Subscriber) error {
|
func (g *Server) createSubHandler(sb *subscriber, opts server.Options) broker.Handler {
|
||||||
typ := reflect.TypeOf(sub.Subscriber())
|
|
||||||
var argType reflect.Type
|
|
||||||
|
|
||||||
if typ.Kind() == reflect.Func {
|
|
||||||
name := "Func"
|
|
||||||
switch typ.NumIn() {
|
|
||||||
case 2:
|
|
||||||
argType = typ.In(1)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
|
|
||||||
}
|
|
||||||
if !isExportedOrBuiltinType(argType) {
|
|
||||||
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
|
|
||||||
}
|
|
||||||
if typ.NumOut() != 1 {
|
|
||||||
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
|
|
||||||
name, typ.NumOut(), subSig)
|
|
||||||
}
|
|
||||||
if returnType := typ.Out(0); returnType != typeOfError {
|
|
||||||
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hdlr := reflect.ValueOf(sub.Subscriber())
|
|
||||||
name := reflect.Indirect(hdlr).Type().Name()
|
|
||||||
|
|
||||||
for m := 0; m < typ.NumMethod(); m++ {
|
|
||||||
method := typ.Method(m)
|
|
||||||
|
|
||||||
switch method.Type.NumIn() {
|
|
||||||
case 3:
|
|
||||||
argType = method.Type.In(2)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
|
|
||||||
name, method.Name, method.Type.NumIn(), subSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isExportedOrBuiltinType(argType) {
|
|
||||||
return fmt.Errorf("%v argument type not exported: %v", name, argType)
|
|
||||||
}
|
|
||||||
if method.Type.NumOut() != 1 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"subscriber %v.%v has wrong number of outs: %v require signature %s",
|
|
||||||
name, method.Name, method.Type.NumOut(), subSig)
|
|
||||||
}
|
|
||||||
if returnType := method.Type.Out(0); returnType != typeOfError {
|
|
||||||
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broker.Handler {
|
|
||||||
return func(p broker.Event) (err error) {
|
return func(p broker.Event) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
if logger.V(logger.ErrorLevel) {
|
if g.opts.Logger.V(logger.ErrorLevel) {
|
||||||
logger.Error("panic recovered: ", r)
|
g.opts.Logger.Error(g.opts.Context, "panic recovered: ", r)
|
||||||
logger.Error(string(debug.Stack()))
|
g.opts.Logger.Error(g.opts.Context, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
err = errors.InternalServerError(g.opts.Name+".subscriber", "panic recovered: %v", r)
|
err = errors.InternalServerError(g.opts.Name+".subscriber", "panic recovered: %v", r)
|
||||||
}
|
}
|
||||||
@@ -183,20 +122,23 @@ func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broke
|
|||||||
|
|
||||||
ct := msg.Header["Content-Type"]
|
ct := msg.Header["Content-Type"]
|
||||||
if len(ct) == 0 {
|
if len(ct) == 0 {
|
||||||
msg.Header["Content-Type"] = defaultContentType
|
msg.Header["Content-Type"] = DefaultContentType
|
||||||
ct = defaultContentType
|
ct = DefaultContentType
|
||||||
}
|
}
|
||||||
cf, err := g.newGRPCCodec(ct)
|
cf, err := g.newCodec(ct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr := make(map[string]string, len(msg.Header))
|
hdr := make(map[string]string, len(msg.Header))
|
||||||
for k, v := range msg.Header {
|
for k, v := range msg.Header {
|
||||||
|
if k == "Content-Type" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
hdr[k] = v
|
hdr[k] = v
|
||||||
}
|
}
|
||||||
delete(hdr, "Content-Type")
|
|
||||||
ctx := metadata.NewContext(sb.opts.Context, hdr)
|
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
|
||||||
|
|
||||||
results := make(chan error, len(sb.handlers))
|
results := make(chan error, len(sb.handlers))
|
||||||
|
|
||||||
@@ -229,7 +171,7 @@ func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broke
|
|||||||
vals = append(vals, reflect.ValueOf(ctx))
|
vals = append(vals, reflect.ValueOf(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
vals = append(vals, reflect.ValueOf(msg.Payload()))
|
vals = append(vals, reflect.ValueOf(msg.Body()))
|
||||||
|
|
||||||
returnValues := handler.method.Call(vals)
|
returnValues := handler.method.Call(vals)
|
||||||
if rerr := returnValues[0].Interface(); rerr != nil {
|
if rerr := returnValues[0].Interface(); rerr != nil {
|
||||||
@@ -249,14 +191,13 @@ func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broke
|
|||||||
if g.wg != nil {
|
if g.wg != nil {
|
||||||
defer g.wg.Done()
|
defer g.wg.Done()
|
||||||
}
|
}
|
||||||
err := fn(ctx, &rpcMessage{
|
cerr := fn(ctx, &rpcMessage{
|
||||||
topic: sb.topic,
|
topic: sb.topic,
|
||||||
contentType: ct,
|
contentType: ct,
|
||||||
payload: req.Interface(),
|
payload: req.Interface(),
|
||||||
header: msg.Header,
|
header: msg.Header,
|
||||||
body: msg.Body,
|
|
||||||
})
|
})
|
||||||
results <- err
|
results <- cerr
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
var errors []string
|
var errors []string
|
||||||
@@ -281,7 +222,7 @@ func (s *subscriber) Subscriber() interface{} {
|
|||||||
return s.subscriber
|
return s.subscriber
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *subscriber) Endpoints() []*registry.Endpoint {
|
func (s *subscriber) Endpoints() []*register.Endpoint {
|
||||||
return s.endpoints
|
return s.endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
59
util.go
Normal file
59
util.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceMethod converts a gRPC method to a Go method
|
||||||
|
// Input:
|
||||||
|
// Foo.Bar, /Foo/Bar, /package.Foo/Bar, /a.package.Foo/Bar
|
||||||
|
// Output:
|
||||||
|
// [Foo, Bar]
|
||||||
|
func serviceMethod(m string) (string, string, error) {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return "", "", fmt.Errorf("malformed method name: %q", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// grpc method
|
||||||
|
if m[0] == '/' {
|
||||||
|
// [ , Foo, Bar]
|
||||||
|
// [ , package.Foo, Bar]
|
||||||
|
// [ , a.package.Foo, Bar]
|
||||||
|
parts := strings.Split(m, "/")
|
||||||
|
if len(parts) != 3 || len(parts[1]) == 0 || len(parts[2]) == 0 {
|
||||||
|
return "", "", fmt.Errorf("malformed method name: %q", m)
|
||||||
|
}
|
||||||
|
service := strings.Split(parts[1], ".")
|
||||||
|
return service[len(service)-1], parts[2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// non grpc method
|
||||||
|
parts := strings.Split(m, ".")
|
||||||
|
|
||||||
|
// expect [Foo, Bar]
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", "", fmt.Errorf("malformed method name: %q", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts[0], parts[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// ServiceFromMethod returns the service
|
||||||
|
// /service.Foo/Bar => service
|
||||||
|
func serviceFromMethod(m string) string {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
if m[0] != '/' {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
parts := strings.Split(m, "/")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
parts = strings.Split(parts[1], ".")
|
||||||
|
return strings.Join(parts[:len(parts)-1], ".")
|
||||||
|
}
|
||||||
|
*/
|
46
util_test.go
Normal file
46
util_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceMethod(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
input string
|
||||||
|
service string
|
||||||
|
method string
|
||||||
|
err bool
|
||||||
|
}
|
||||||
|
|
||||||
|
methods := []testCase{
|
||||||
|
{"Foo.Bar", "Foo", "Bar", false},
|
||||||
|
{"/Foo/Bar", "Foo", "Bar", false},
|
||||||
|
{"/package.Foo/Bar", "Foo", "Bar", false},
|
||||||
|
{"/a.package.Foo/Bar", "Foo", "Bar", false},
|
||||||
|
{"a.package.Foo/Bar", "", "", true},
|
||||||
|
{"/Foo/Bar/Baz", "", "", true},
|
||||||
|
{"Foo.Bar.Baz", "", "", true},
|
||||||
|
}
|
||||||
|
for _, test := range methods {
|
||||||
|
service, method, err := serviceMethod(test.input)
|
||||||
|
if err != nil && test.err == true {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// unexpected error
|
||||||
|
if err != nil && test.err == false {
|
||||||
|
t.Fatalf("unexpected err %v for %+v", err, test)
|
||||||
|
}
|
||||||
|
// expecter error
|
||||||
|
if test.err == true && err == nil {
|
||||||
|
t.Fatalf("expected error for %+v: got service: %s method: %s", test, service, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
if service != test.service {
|
||||||
|
t.Fatalf("wrong service for %+v: got service: %s method: %s", test, service, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
if method != test.method {
|
||||||
|
t.Fatalf("wrong method for %+v: got service: %s method: %s", test, service, method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user