Compare commits
287 Commits
Author | SHA1 | Date | |
---|---|---|---|
12e26d0018 | |||
aab779b406 | |||
3514bb9626 | |||
c7dc998670 | |||
954101f887 | |||
4c3d26c39b | |||
a483e08ffa | |||
28eeca30c0 | |||
2e8462a0f1 | |||
1432587369 | |||
ea8c9dd22e | |||
49608197d0 | |||
41d7d145ce | |||
1d5142d619 | |||
41f7bdf182 | |||
ed907c7076 | |||
5c2cba3ecc | |||
966bc31eb2 | |||
259e1d275e | |||
fdbcff2ba0 | |||
3e814bf229 | |||
13cdeb9397 | |||
0e19afef86 | |||
7e850f75e0 | |||
4c3d3058f6 | |||
bcd27b833a | |||
090b5e3c07 | |||
95109c9dc2 | |||
69dcf71d3f | |||
0a0a986a70 | |||
76fe748e4a | |||
9926d52f78 | |||
2ed04e3e24 | |||
4a9cc0f03f | |||
99af727138 | |||
89fe1dd6bc | |||
8b591f7fd4 | |||
bec1705d09 | |||
316f644090 | |||
3fdff4312c | |||
09657b4b67 | |||
741a6f181b | |||
7f971ee6c3 | |||
647f731f50 | |||
724ffb8c3a | |||
25f306425b | |||
|
dad2d8d5bd | ||
ca148cd8fc | |||
cd8b3e37f2 | |||
c908274c8b | |||
fd3793d50f | |||
|
e67021895e | ||
9f4dbbf296 | |||
eea8ae605b | |||
4341600c01 | |||
d3fc61eada | |||
e8281da860 | |||
9bc1629e77 | |||
c27a8b7cf8 | |||
6b845853e2 | |||
|
dbc3c12eed | ||
7f1a55958e | |||
02f9d6d8f7 | |||
82beddb09b | |||
6cb3a1c4c4 | |||
|
2ff786cb03 | ||
282065f90e | |||
|
b16e6ebd31 | ||
68f4c164d0 | |||
|
d0fee2ca23 | ||
034298b7e0 | |||
f93c35b611 | |||
b5148e90cb | |||
|
a8d47d0c1c | ||
7d4e32298c | |||
|
14ff546fc1 | ||
|
c37d057669 | ||
b925441ea7 | |||
984d09384f | |||
7c15ee784a | |||
f2607d5439 | |||
|
8e607dcc26 | ||
929aae7456 | |||
5ba7742619 | |||
dece47ae0e | |||
|
db5e66e375 | ||
39d3aabf62 | |||
|
25469792df | ||
7f278036f6 | |||
05625d2df4 | |||
82bfa00c3d | |||
|
740b5ee635 | ||
9a096bc471 | |||
|
04b27524ee | ||
|
57758b4f80 | ||
9c809a04eb | |||
|
dd0e7e748d | ||
|
31b048858d | ||
|
bf0f643d78 | ||
|
1bdd010340 | ||
|
30ec3ae03e | ||
a11adb8a1e | |||
|
e8678e2da6 | ||
faf698cfcb | |||
461123dba0 | |||
7d649e8156 | |||
|
d5e0423985 | ||
2e2a9e8f01 | |||
|
685951339e | ||
|
31e882cc1e | ||
|
f5db9193de | ||
9932138d52 | |||
|
a2a83bbae5 | ||
19946951f8 | |||
|
9711b1b4ed | ||
31c53e8172 | |||
|
1c9f81edd4 | ||
9679408349 | |||
|
47786523cc | ||
0d47683c9a | |||
|
ad44db22b9 | ||
2be7d79739 | |||
|
0124e922e5 | ||
fd8c454bae | |||
1cc8b37503 | |||
cd47eab010 | |||
9136addd1e | |||
4ce1ef631b | |||
4618fae641 | |||
b2eba3120e | |||
|
04703cb8fa | ||
6da3bbabed | |||
81016aa16f | |||
|
042998ff76 | ||
97d5936efd | |||
|
d6777af891 | ||
c2f178001c | |||
ffe5f1c9be | |||
416d494e50 | |||
7dd1dfbff6 | |||
|
e11dc06067 | ||
2cc17551ca | |||
d8493a3628 | |||
e9a396d424 | |||
fed13e1293 | |||
|
62fdcf6a1f | ||
2f0e291247 | |||
|
39f7605f91 | ||
e2efc934b3 | |||
2d5fb587a0 | |||
6a30a4f408 | |||
f80e6c1a66 | |||
2aa8768aed | |||
0ab763b505 | |||
68b6f6904b | |||
31e996fc2e | |||
14f3b149e4 | |||
6714e48f4f | |||
547199f0b8 | |||
|
be637e8324 | ||
67e9e7e880 | |||
|
8a6a259404 | ||
2b3e1acd22 | |||
|
2816fd756a | ||
5353c56034 | |||
|
b93dcbb165 | ||
d76f9f20b6 | |||
|
4cb133546d | ||
c0ca60b7c9 | |||
|
7a096d6605 | ||
1c5c86668b | |||
|
e7b5a90b5c | ||
677f00c3c0 | |||
7365c5f1cd | |||
505c59ba75 | |||
7a39d86018 | |||
06a0a2f336 | |||
|
776ce8591a | ||
48aaef3842 | |||
36aa9f6a28 | |||
|
4e54cd6f7d | ||
7886f3160d | |||
0e86aff88c | |||
|
5d23148038 | ||
8c61439bc5 | |||
7ad1a9c38e | |||
5c829fe029 | |||
3e1ffcb989 | |||
c1e0ce26b5 | |||
26e34124e0 | |||
c1526f70ce | |||
28d23f388f | |||
d3d67eca97 | |||
919bf1486f | |||
88ef21ba9e | |||
|
254b01f75a | ||
|
325b8a43ef | ||
|
d94860c32d | ||
|
778095179d | ||
f71ae35f12 | |||
|
4d27c79081 | ||
|
1eebef8dbc | ||
|
cd2c0b65e1 | ||
|
97e0bf75bb | ||
|
9f56d8b6b5 | ||
|
21f608d728 | ||
ba283695dd | |||
b18465083b | |||
|
11020aa6e5 | ||
|
43c0eb4fda | ||
5ab606c2b5 | |||
6c661c9d72 | |||
a215521821 | |||
|
ec5af022b3 | ||
|
bd88f6e485 | ||
|
34a39be017 | ||
|
360703d8bb | ||
2660a0d94d | |||
|
6e9a693b51 | ||
|
06d197e356 | ||
30609135a4 | |||
84167d359e | |||
145a0f4aa6 | |||
|
b2b4d6a5a3 | ||
|
f17afa7950 | ||
bb763c71b5 | |||
622a79bd06 | |||
|
0eb3c0b452 | ||
676634850e | |||
0d442683ee | |||
|
c8587ec93a | ||
7e35316cf2 | |||
|
475452a781 | ||
|
dd0bb7c5d4 | ||
|
b74f3762dd | ||
|
e30590c707 | ||
|
132e95dec6 | ||
48620e6297 | |||
|
e31185b6dc | ||
|
151e45ca30 | ||
|
93c4f219e9 | ||
|
396654b066 | ||
|
a769031246 | ||
|
99915c1633 | ||
|
c7759d29b8 | ||
|
0f97e432a4 | ||
|
713b4f96be | ||
360b15a3b6 | |||
|
4959b0040b | ||
e93c6dc1de | |||
|
9ad01954dc | ||
|
8483b85c94 | ||
|
d1a8f76d77 | ||
|
9d7f4bfa66 | ||
|
656dfd4d05 | ||
|
70e14e6592 | ||
39e6d9f586 | |||
a46c9d395a | |||
|
d3c5a503c6 | ||
|
be224692a4 | ||
|
0da73b62ae | ||
|
a30c2972fd | ||
f28de46379 | |||
af91220a09 | |||
|
14bf4a9f30 | ||
e88f524042 | |||
45670661d0 | |||
|
f7ed037633 | ||
|
8f36e1753a | ||
0a59653c70 | |||
|
cf9d05d4f7 | ||
|
6b6d4eb74b | ||
|
db28bbb977 | ||
|
6819026d7c | ||
4ade934d26 | |||
|
d9adfebaf3 | ||
|
0ff8939a0d | ||
495e98c3c4 | |||
5adaa78c36 | |||
|
7b83fc7007 | ||
|
dcd50c6fa8 | ||
|
9d028175f4 | ||
|
a5d7a69d25 | ||
c0d9c34200 | |||
dc1e05bb18 | |||
1e4d56d059 | |||
6565336c1a |
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.
|
||||||
|
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
bin
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
.vscode
|
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
|
10
README.md
10
README.md
@ -9,8 +9,8 @@ to create a HTTP Server that could potentially be used for REST based API servic
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v4/server"
|
||||||
httpServer "github.com/unistack-org/micro-server-http"
|
httpServer "go.unistack.org/micro-server-http/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -37,9 +37,9 @@ Or as part of a service
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3"
|
"go.unistack.org/micro/v4"
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v4/server"
|
||||||
httpServer "github.com/unistack-org/micro-server-http"
|
httpServer "go.unistack.org/micro-server-http/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
25
go.mod
25
go.mod
@ -1,10 +1,25 @@
|
|||||||
module github.com/unistack-org/micro-server-http/v3
|
module go.unistack.org/micro-server-http/v4
|
||||||
|
|
||||||
go 1.13
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/unistack-org/micro/v3 v3.2.7
|
go.unistack.org/micro-codec-yaml/v4 v4.0.0
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
go.unistack.org/micro-proto/v4 v4.0.1
|
||||||
|
go.unistack.org/micro/v4 v4.0.17
|
||||||
|
go.unistack.org/protoc-gen-go-micro/v4 v4.0.13
|
||||||
|
golang.org/x/net v0.22.0
|
||||||
)
|
)
|
||||||
|
|
||||||
//replace github.com/unistack-org/micro/v3 => ../../micro
|
require (
|
||||||
|
github.com/fatih/structtag v1.2.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/gnostic v0.7.0 // indirect
|
||||||
|
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||||
|
golang.org/x/mod v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
|
golang.org/x/tools v0.19.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
|
)
|
||||||
|
561
handler.go
561
handler.go
@ -3,50 +3,45 @@ package http
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"go.unistack.org/micro/v4/errors"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"go.unistack.org/micro/v4/logger"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"go.unistack.org/micro/v4/metadata"
|
||||||
"github.com/unistack-org/micro/v3/register"
|
"go.unistack.org/micro/v4/register"
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v4/server"
|
||||||
"github.com/unistack-org/micro/v3/util/qson"
|
rhttp "go.unistack.org/micro/v4/util/http"
|
||||||
rflutil "github.com/unistack-org/micro/v3/util/reflect"
|
rflutil "go.unistack.org/micro/v4/util/reflect"
|
||||||
rutil "github.com/unistack-org/micro/v3/util/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultErrorHandler = func(ctx context.Context, s server.Handler, w http.ResponseWriter, r *http.Request, err error, status int) {
|
DefaultErrorHandler = func(ctx context.Context, s interface{}, w http.ResponseWriter, r *http.Request, err error, status int) {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
w.Write([]byte(err.Error()))
|
if _, cerr := w.Write([]byte(err.Error())); cerr != nil {
|
||||||
|
logger.DefaultLogger.Error(ctx, fmt.Sprintf("write failed: %v", cerr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DefaultContentType = "application/json"
|
DefaultContentType = "application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type patHandler struct {
|
type patHandler struct {
|
||||||
pat rutil.Pattern
|
|
||||||
mtype *methodType
|
mtype *methodType
|
||||||
name string
|
|
||||||
rcvr reflect.Value
|
rcvr reflect.Value
|
||||||
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpHandler struct {
|
type httpHandler struct {
|
||||||
name string
|
opts server.HandleOptions
|
||||||
opts server.HandlerOptions
|
|
||||||
sopts server.Options
|
|
||||||
eps []*register.Endpoint
|
|
||||||
hd interface{}
|
hd interface{}
|
||||||
handlers map[string][]patHandler
|
handlers *rhttp.Trie
|
||||||
errorHandler func(context.Context, server.Handler, http.ResponseWriter, *http.Request, error, int)
|
name string
|
||||||
}
|
eps []*register.Endpoint
|
||||||
|
sopts server.Options
|
||||||
func (h *httpHandler) newCodec(ct string) (codec.Codec, error) {
|
sync.RWMutex
|
||||||
if cf, ok := h.sopts.Codecs[ct]; ok {
|
|
||||||
return cf, nil
|
|
||||||
}
|
|
||||||
return nil, codec.ErrUnknownContentType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpHandler) Name() string {
|
func (h *httpHandler) Name() string {
|
||||||
@ -61,81 +56,147 @@ func (h *httpHandler) Endpoints() []*register.Endpoint {
|
|||||||
return h.eps
|
return h.eps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpHandler) Options() server.HandlerOptions {
|
func (h *httpHandler) Options() server.HandleOptions {
|
||||||
return h.opts
|
return h.opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *Server) HTTPHandlerFunc(handler interface{}) (http.HandlerFunc, error) {
|
||||||
ctx := r.Context()
|
if handler == nil {
|
||||||
|
return nil, fmt.Errorf("invalid handler specified: %v", handler)
|
||||||
path := r.URL.Path
|
|
||||||
if !strings.HasPrefix(path, "/") {
|
|
||||||
h.errorHandler(ctx, h, w, r, fmt.Errorf("path must contains /"), http.StatusBadRequest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rtype := reflect.TypeOf(handler)
|
||||||
|
if rtype.NumIn() != 3 {
|
||||||
|
return nil, fmt.Errorf("invalid handler, NumIn != 3: %v", rtype.NumIn())
|
||||||
|
}
|
||||||
|
|
||||||
|
argType := rtype.In(1)
|
||||||
|
replyType := rtype.In(2)
|
||||||
|
|
||||||
|
// First arg need not be a pointer.
|
||||||
|
if !isExportedOrBuiltinType(argType) {
|
||||||
|
return nil, fmt.Errorf("invalid handler, argument type not exported: %v", argType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if replyType.Kind() != reflect.Ptr {
|
||||||
|
return nil, fmt.Errorf("invalid handler, reply type not a pointer: %v", replyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply type must be exported.
|
||||||
|
if !isExportedOrBuiltinType(replyType) {
|
||||||
|
return nil, fmt.Errorf("invalid handler, reply type not exported: %v", replyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rtype.NumOut() != 1 {
|
||||||
|
return nil, fmt.Errorf("invalid handler, has wrong number of outs: %v", rtype.NumOut())
|
||||||
|
}
|
||||||
|
|
||||||
|
// The return type of the method must be error.
|
||||||
|
if returnType := rtype.Out(0); returnType != typeOfError {
|
||||||
|
return nil, fmt.Errorf("invalid handler, returns %v not error", returnType.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
ct := DefaultContentType
|
ct := DefaultContentType
|
||||||
if htype := r.Header.Get("Content-Type"); htype != "" {
|
if htype := r.Header.Get(metadata.HeaderContentType); htype != "" {
|
||||||
ct = htype
|
ct = htype
|
||||||
}
|
}
|
||||||
|
|
||||||
cf, err := h.newCodec(ct)
|
ctx := context.WithValue(r.Context(), rspCodeKey{}, &rspCodeVal{})
|
||||||
if err != nil {
|
ctx = context.WithValue(ctx, rspHeaderKey{}, &rspHeaderVal{})
|
||||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
md = metadata.New(len(r.Header) + 8)
|
||||||
|
}
|
||||||
|
for k, v := range r.Header {
|
||||||
|
md[k] = v[0]
|
||||||
|
}
|
||||||
|
md["RemoteAddr"] = r.RemoteAddr
|
||||||
|
md["Method"] = r.Method
|
||||||
|
md["URL"] = r.URL.String()
|
||||||
|
md["Proto"] = r.Proto
|
||||||
|
md["Content-Length"] = fmt.Sprintf("%d", r.ContentLength)
|
||||||
|
md["Transfer-Encoding"] = r.TransferEncoding[0]
|
||||||
|
md["Host"] = r.Host
|
||||||
|
md["RequestURI"] = r.RequestURI
|
||||||
|
if r.TLS != nil {
|
||||||
|
md["TLS"] = "true"
|
||||||
|
md["TLS-ALPN"] = r.TLS.NegotiatedProtocol
|
||||||
|
md["TLS-ServerName"] = r.TLS.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
components := strings.Split(path[1:], "/")
|
ctx = metadata.NewIncomingContext(ctx, md)
|
||||||
l := len(components)
|
|
||||||
var verb string
|
path := r.URL.Path
|
||||||
idx := strings.LastIndex(components[l-1], ":")
|
|
||||||
if idx == 0 {
|
if r.Body != nil {
|
||||||
h.errorHandler(ctx, h, w, r, fmt.Errorf("not found"), http.StatusNotFound)
|
defer r.Body.Close()
|
||||||
return
|
|
||||||
}
|
|
||||||
if idx > 0 {
|
|
||||||
c := components[l-1]
|
|
||||||
components[l-1], verb = c[:idx], c[idx+1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := make(map[string]interface{})
|
matches := make(map[string]interface{})
|
||||||
var match bool
|
var match bool
|
||||||
var hldr patHandler
|
var hldr *patHandler
|
||||||
for _, hldr = range h.handlers[r.Method] {
|
var handler *httpHandler
|
||||||
mp, err := hldr.pat.Match(components, verb)
|
|
||||||
|
for _, shdlr := range h.handlers {
|
||||||
|
hdlr := shdlr.(*httpHandler)
|
||||||
|
fh, mp, err := hdlr.handlers.Search(r.Method, path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
match = true
|
match = true
|
||||||
for k, v := range mp {
|
for k, v := range mp {
|
||||||
matches[k] = v
|
matches[k] = v
|
||||||
}
|
}
|
||||||
|
hldr = fh.(*patHandler)
|
||||||
|
handler = hdlr
|
||||||
break
|
break
|
||||||
}
|
} else if err == rhttp.ErrMethodNotAllowed && !h.registerRPC {
|
||||||
}
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
_, _ = w.Write([]byte("not matching route found"))
|
||||||
if !match {
|
|
||||||
h.errorHandler(ctx, h, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
md, ok := metadata.FromContext(ctx)
|
|
||||||
if !ok {
|
|
||||||
md = metadata.New(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range r.Header {
|
if !match && h.registerRPC {
|
||||||
md.Set(k, strings.Join(v, ", "))
|
microMethod, mok := md.Get(metadata.HeaderEndpoint)
|
||||||
|
if mok {
|
||||||
|
serviceMethod := strings.Split(microMethod, ".")
|
||||||
|
if len(serviceMethod) == 2 {
|
||||||
|
if shdlr, ok := h.handlers[serviceMethod[0]]; ok {
|
||||||
|
hdlr := shdlr.(*httpHandler)
|
||||||
|
fh, mp, err := hdlr.handlers.Search(http.MethodPost, "/"+microMethod)
|
||||||
|
if err == nil {
|
||||||
|
match = true
|
||||||
|
for k, v := range mp {
|
||||||
|
matches[k] = v
|
||||||
|
}
|
||||||
|
hldr = fh.(*patHandler)
|
||||||
|
handler = hdlr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get fields from url values
|
// get fields from url values
|
||||||
if len(r.URL.RawQuery) > 0 {
|
if len(r.URL.RawQuery) > 0 {
|
||||||
umd := make(map[string]interface{})
|
umd, cerr := rflutil.URLMap(r.URL.RawQuery)
|
||||||
err = qson.Unmarshal(&umd, r.URL.RawQuery)
|
if cerr != nil {
|
||||||
if err != nil {
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
_, _ = w.Write([]byte(cerr.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for k, v := range umd {
|
for k, v := range umd {
|
||||||
matches[k] = v
|
matches[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cf, err := h.newCodec(ct)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var argv, replyv reflect.Value
|
var argv, replyv reflect.Value
|
||||||
|
|
||||||
// Decode the argument value.
|
// Decode the argument value.
|
||||||
@ -155,65 +216,383 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
replyv = reflect.New(hldr.mtype.ReplyType.Elem())
|
replyv = reflect.New(hldr.mtype.ReplyType.Elem())
|
||||||
|
|
||||||
function := hldr.mtype.method.Func
|
function := hldr.mtype.method.Func
|
||||||
//function := hldr.rcvr
|
|
||||||
var returnValues []reflect.Value
|
var returnValues []reflect.Value
|
||||||
|
|
||||||
if err = rflutil.MergeMap(argv.Interface(), matches); err != nil {
|
if r.Body != nil {
|
||||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
var buf []byte
|
||||||
|
buf, err = io.ReadAll(r.Body)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := cf.Marshal(argv.Interface())
|
if err = cf.Unmarshal(buf, argv.Interface()); err != nil {
|
||||||
if err != nil {
|
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = rflutil.FlattenMap(matches)
|
||||||
|
if err = rflutil.Merge(argv.Interface(), matches, rflutil.SliceAppend(true), rflutil.Tags([]string{"protobuf", "json"})); err != nil {
|
||||||
|
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hr := &rpcRequest{
|
hr := &rpcRequest{
|
||||||
codec: cf,
|
codec: cf,
|
||||||
service: h.sopts.Name,
|
service: handler.sopts.Name,
|
||||||
contentType: ct,
|
contentType: ct,
|
||||||
method: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
method: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
||||||
body: b,
|
endpoint: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
||||||
payload: argv.Interface(),
|
payload: argv.Interface(),
|
||||||
|
header: md,
|
||||||
}
|
}
|
||||||
|
|
||||||
var scode int
|
|
||||||
// define the handler func
|
// define the handler func
|
||||||
fn := func(ctx context.Context, req server.Request, rsp interface{}) (err error) {
|
fn := func(fctx context.Context, req server.Request, rsp interface{}) (err error) {
|
||||||
ctx = context.WithValue(ctx, rspCodeKey{}, &rspCodeVal{})
|
returnValues = function.Call([]reflect.Value{hldr.rcvr, hldr.mtype.prepareContext(fctx), argv, reflect.ValueOf(rsp)})
|
||||||
ctx = metadata.NewContext(ctx, md)
|
|
||||||
returnValues = function.Call([]reflect.Value{hldr.rcvr, hldr.mtype.prepareContext(ctx), reflect.ValueOf(argv.Interface()), reflect.ValueOf(rsp)})
|
|
||||||
|
|
||||||
scode = GetRspCode(ctx)
|
|
||||||
// The return value for the method is an error.
|
// The return value for the method is an error.
|
||||||
if rerr := returnValues[0].Interface(); rerr != nil {
|
if rerr := returnValues[0].Interface(); rerr != nil {
|
||||||
err = rerr.(error)
|
err = rerr.(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
md, ok := metadata.FromOutgoingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
md = metadata.New(0)
|
||||||
|
}
|
||||||
|
if nmd, ok := metadata.FromOutgoingContext(fctx); ok {
|
||||||
|
for k, v := range nmd {
|
||||||
|
md[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap the handler func
|
// wrap the handler func
|
||||||
for i := len(h.sopts.HdlrWrappers); i > 0; i-- {
|
// for i := len(handler.sopts.Hooks); i > 0; i-- {
|
||||||
fn = h.sopts.HdlrWrappers[i-1](fn)
|
// fn = handler.sopts.Hooks[i-1](fn)
|
||||||
|
// }
|
||||||
|
|
||||||
|
if ct == "application/x-www-form-urlencoded" {
|
||||||
|
cf, err = h.newCodec(DefaultContentType)
|
||||||
|
if err != nil {
|
||||||
|
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ct = DefaultContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
if appErr := fn(ctx, hr, replyv.Interface()); appErr != nil {
|
scode := int(200)
|
||||||
b, err = cf.Marshal(appErr)
|
appErr := fn(ctx, hr, replyv.Interface())
|
||||||
} else {
|
|
||||||
b, err = cf.Marshal(replyv.Interface())
|
w.Header().Set(metadata.HeaderContentType, ct)
|
||||||
|
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||||
|
for k, v := range md {
|
||||||
|
w.Header()[k] = []string{v}
|
||||||
}
|
}
|
||||||
if err != nil && h.sopts.Logger.V(logger.ErrorLevel) {
|
}
|
||||||
h.sopts.Logger.Errorf(h.sopts.Context, "XXXXX: %v", err)
|
if md := getRspHeader(ctx); md != nil {
|
||||||
|
for k, v := range md {
|
||||||
|
w.Header()[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nct := w.Header().Get(metadata.HeaderContentType); nct != ct {
|
||||||
|
if cf, err = h.newCodec(nct); err != nil {
|
||||||
|
h.errorHandler(ctx, nil, w, r, err, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
|
if appErr != nil {
|
||||||
|
switch verr := appErr.(type) {
|
||||||
|
case *errors.Error:
|
||||||
|
scode = int(verr.Code)
|
||||||
|
buf, err = cf.Marshal(verr)
|
||||||
|
case *Error:
|
||||||
|
buf, err = cf.Marshal(verr.err)
|
||||||
|
default:
|
||||||
|
buf, err = cf.Marshal(appErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf, err = cf.Marshal(replyv.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && handler.sopts.Logger.V(logger.ErrorLevel) {
|
||||||
|
handler.sopts.Logger.Error(handler.sopts.Context, fmt.Sprintf("handler err: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("content-Type", ct)
|
if nscode := GetRspCode(ctx); nscode != 0 {
|
||||||
if scode != 0 {
|
scode = nscode
|
||||||
|
}
|
||||||
w.WriteHeader(scode)
|
w.WriteHeader(scode)
|
||||||
|
|
||||||
|
if _, cerr := w.Write(buf); cerr != nil {
|
||||||
|
handler.sopts.Logger.Error(ctx, fmt.Sprintf("write failed: %v", cerr))
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ct := DefaultContentType
|
||||||
|
if htype := r.Header.Get(metadata.HeaderContentType); htype != "" {
|
||||||
|
ct = htype
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), rspCodeKey{}, &rspCodeVal{})
|
||||||
|
ctx = context.WithValue(ctx, rspHeaderKey{}, &rspHeaderVal{})
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
md = metadata.New(len(r.Header) + 8)
|
||||||
|
}
|
||||||
|
for k, v := range r.Header {
|
||||||
|
md[k] = v[0]
|
||||||
|
}
|
||||||
|
md["RemoteAddr"] = r.RemoteAddr
|
||||||
|
if r.TLS != nil {
|
||||||
|
md["Scheme"] = "https"
|
||||||
} else {
|
} else {
|
||||||
h.sopts.Logger.Warn(h.sopts.Context, "response code not set in handler via SetRspCode(ctx, http.StatusXXX)")
|
md["Scheme"] = "http"
|
||||||
|
}
|
||||||
|
md["Method"] = r.Method
|
||||||
|
md["URL"] = r.URL.String()
|
||||||
|
md["Proto"] = r.Proto
|
||||||
|
md["Content-Length"] = fmt.Sprintf("%d", r.ContentLength)
|
||||||
|
if len(r.TransferEncoding) > 0 {
|
||||||
|
md["Transfer-Encoding"] = r.TransferEncoding[0]
|
||||||
|
}
|
||||||
|
md["Host"] = r.Host
|
||||||
|
md["RequestURI"] = r.RequestURI
|
||||||
|
ctx = metadata.NewIncomingContext(ctx, md)
|
||||||
|
|
||||||
|
path := r.URL.Path
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
h.errorHandler(ctx, nil, w, r, fmt.Errorf("path must starts with /"), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := make(map[string]interface{})
|
||||||
|
|
||||||
|
var match bool
|
||||||
|
var hldr *patHandler
|
||||||
|
var handler *httpHandler
|
||||||
|
|
||||||
|
for _, shdlr := range h.handlers {
|
||||||
|
hdlr := shdlr.(*httpHandler)
|
||||||
|
fh, mp, err := hdlr.handlers.Search(r.Method, path)
|
||||||
|
if err == nil {
|
||||||
|
match = true
|
||||||
|
for k, v := range mp {
|
||||||
|
matches[k] = v
|
||||||
|
}
|
||||||
|
hldr = fh.(*patHandler)
|
||||||
|
handler = hdlr
|
||||||
|
break
|
||||||
|
} else if err == rhttp.ErrMethodNotAllowed && !h.registerRPC {
|
||||||
|
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match && h.registerRPC {
|
||||||
|
microMethod, mok := md.Get(metadata.HeaderEndpoint)
|
||||||
|
if mok {
|
||||||
|
serviceMethod := strings.Split(microMethod, ".")
|
||||||
|
if len(serviceMethod) == 2 {
|
||||||
|
if shdlr, ok := h.handlers[serviceMethod[0]]; ok {
|
||||||
|
hdlr := shdlr.(*httpHandler)
|
||||||
|
fh, mp, err := hdlr.handlers.Search(http.MethodPost, "/"+microMethod)
|
||||||
|
if err == nil {
|
||||||
|
match = true
|
||||||
|
for k, v := range mp {
|
||||||
|
matches[k] = v
|
||||||
|
}
|
||||||
|
hldr = fh.(*patHandler)
|
||||||
|
handler = hdlr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match && h.hd != nil {
|
||||||
|
if hdlr, ok := h.hd.(http.Handler); ok {
|
||||||
|
hdlr.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if !match {
|
||||||
|
// check for http.HandlerFunc handlers
|
||||||
|
if ph, _, err := h.pathHandlers.Search(r.Method, r.URL.Path); err == nil {
|
||||||
|
ph.(http.HandlerFunc)(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get fields from url values
|
||||||
|
if len(r.URL.RawQuery) > 0 {
|
||||||
|
umd, cerr := rflutil.URLMap(r.URL.RawQuery)
|
||||||
|
if cerr != nil {
|
||||||
|
h.errorHandler(ctx, handler, w, r, cerr, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range umd {
|
||||||
|
matches[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Body != nil {
|
||||||
|
defer r.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
cf, err := h.newCodec(ct)
|
||||||
|
if err != nil {
|
||||||
|
h.errorHandler(ctx, nil, w, r, err, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var argv, replyv reflect.Value
|
||||||
|
|
||||||
|
// Decode the argument value.
|
||||||
|
argIsValue := false // if true, need to indirect before calling.
|
||||||
|
if hldr.mtype.ArgType.Kind() == reflect.Ptr {
|
||||||
|
argv = reflect.New(hldr.mtype.ArgType.Elem())
|
||||||
|
} else {
|
||||||
|
argv = reflect.New(hldr.mtype.ArgType)
|
||||||
|
argIsValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if argIsValue {
|
||||||
|
argv = argv.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// reply value
|
||||||
|
replyv = reflect.New(hldr.mtype.ReplyType.Elem())
|
||||||
|
|
||||||
|
function := hldr.mtype.method.Func
|
||||||
|
var returnValues []reflect.Value
|
||||||
|
|
||||||
|
if r.Body != nil {
|
||||||
|
var buf []byte
|
||||||
|
buf, err = io.ReadAll(r.Body)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cf.Unmarshal(buf, argv.Interface()); err != nil {
|
||||||
|
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) > 0 {
|
||||||
|
matches = rflutil.FlattenMap(matches)
|
||||||
|
if err = rflutil.Merge(argv.Interface(), matches, rflutil.SliceAppend(true), rflutil.Tags([]string{"protobuf", "json"})); err != nil {
|
||||||
|
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr := &rpcRequest{
|
||||||
|
codec: cf,
|
||||||
|
service: handler.sopts.Name,
|
||||||
|
contentType: ct,
|
||||||
|
method: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
||||||
|
endpoint: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
||||||
|
payload: argv.Interface(),
|
||||||
|
header: md,
|
||||||
|
}
|
||||||
|
|
||||||
|
// define the handler func
|
||||||
|
fn := func(fctx context.Context, req server.Request, rsp interface{}) (err error) {
|
||||||
|
returnValues = function.Call([]reflect.Value{hldr.rcvr, hldr.mtype.prepareContext(fctx), argv, reflect.ValueOf(rsp)})
|
||||||
|
|
||||||
|
// The return value for the method is an error.
|
||||||
|
if rerr := returnValues[0].Interface(); rerr != nil {
|
||||||
|
err = rerr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
md, ok := metadata.FromOutgoingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
md = metadata.New(0)
|
||||||
|
}
|
||||||
|
if nmd, ok := metadata.FromOutgoingContext(fctx); ok {
|
||||||
|
for k, v := range nmd {
|
||||||
|
md[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap the handler func
|
||||||
|
// for i := len(handler.sopts.HdlrWrappers); i > 0; i-- {
|
||||||
|
// fn = handler.sopts.HdlrWrappers[i-1](fn)
|
||||||
|
// }
|
||||||
|
|
||||||
|
if ct == "application/x-www-form-urlencoded" {
|
||||||
|
cf, err = h.newCodec(DefaultContentType)
|
||||||
|
if err != nil {
|
||||||
|
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ct = DefaultContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
scode := int(200)
|
||||||
|
appErr := fn(ctx, hr, replyv.Interface())
|
||||||
|
|
||||||
|
w.Header().Set(metadata.HeaderContentType, ct)
|
||||||
|
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||||
|
for k, v := range md {
|
||||||
|
w.Header()[k] = []string{v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if md := getRspHeader(ctx); md != nil {
|
||||||
|
for k, v := range md {
|
||||||
|
for _, vv := range v {
|
||||||
|
w.Header().Add(k, vv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nct := w.Header().Get(metadata.HeaderContentType); nct != ct {
|
||||||
|
if cf, err = h.newCodec(nct); err != nil {
|
||||||
|
h.errorHandler(ctx, nil, w, r, err, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
|
if appErr != nil {
|
||||||
|
switch verr := appErr.(type) {
|
||||||
|
case *errors.Error:
|
||||||
|
scode = int(verr.Code)
|
||||||
|
buf, err = cf.Marshal(verr)
|
||||||
|
case *Error:
|
||||||
|
buf, err = cf.Marshal(verr.err)
|
||||||
|
default:
|
||||||
|
buf, err = cf.Marshal(appErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf, err = cf.Marshal(replyv.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && handler.sopts.Logger.V(logger.ErrorLevel) {
|
||||||
|
handler.sopts.Logger.Error(handler.sopts.Context, fmt.Sprintf("handler err: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nscode := GetRspCode(ctx); nscode != 0 {
|
||||||
|
scode = nscode
|
||||||
|
}
|
||||||
|
w.WriteHeader(scode)
|
||||||
|
|
||||||
|
if _, cerr := w.Write(buf); cerr != nil {
|
||||||
|
handler.sopts.Logger.Error(ctx, fmt.Sprintf("write failed: %v", cerr))
|
||||||
}
|
}
|
||||||
w.Write(b)
|
|
||||||
}
|
}
|
||||||
|
12
handler/generate.go
Normal file
12
handler/generate.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
// import required packages
|
||||||
|
_ "go.unistack.org/micro-proto/v4/openapiv3"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate sh -c "curl -L https://github.com/swagger-api/swagger-ui/archive/refs/tags/v4.18.3.zip -o - | bsdtar -C swagger-ui --strip-components=2 -xv swagger-ui-4.18.3/dist && rm swagger-ui/*.map swagger-ui/*-es-*.js swagger-ui/swagger-ui.js swagger-ui/swagger-initializer.js"
|
||||||
|
|
||||||
|
//go:generate sh -c "protoc -I./ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v4) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./ ./meter/meter.proto"
|
||||||
|
|
||||||
|
//go:generate sh -c "protoc -I./ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v4) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./ ./health/health.proto"
|
82
handler/health/health.go
Normal file
82
handler/health/health.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package health // import "go.unistack.org/micro-server-http/v4/handler/health"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
codecpb "go.unistack.org/micro-proto/v4/codec"
|
||||||
|
"go.unistack.org/micro/v4/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ HealthServiceServer = (*Handler)(nil)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
opts Options
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckFunc func(context.Context) error
|
||||||
|
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Version string
|
||||||
|
Name string
|
||||||
|
LiveChecks []CheckFunc
|
||||||
|
ReadyChecks []CheckFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func LiveChecks(fns ...CheckFunc) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.LiveChecks = append(o.LiveChecks, fns...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadyChecks(fns ...CheckFunc) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.ReadyChecks = append(o.ReadyChecks, fns...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name(name string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version(version string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Version = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(opts ...Option) *Handler {
|
||||||
|
options := Options{}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
return &Handler{opts: options}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Live(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||||
|
var err error
|
||||||
|
for _, fn := range h.opts.LiveChecks {
|
||||||
|
if err = fn(ctx); err != nil {
|
||||||
|
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Ready(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||||
|
var err error
|
||||||
|
for _, fn := range h.opts.ReadyChecks {
|
||||||
|
if err = fn(ctx); err != nil {
|
||||||
|
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Version(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||||
|
rsp.Data = []byte(h.opts.Version)
|
||||||
|
return nil
|
||||||
|
}
|
50
handler/health/health.proto
Normal file
50
handler/health/health.proto
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package micro.server.http.v4.handler.health;
|
||||||
|
option go_package = "go.unistack.org/micro-server-http/v4/handler/health;health";
|
||||||
|
|
||||||
|
import "api/annotations.proto";
|
||||||
|
import "openapiv3/annotations.proto";
|
||||||
|
import "codec/frame.proto";
|
||||||
|
|
||||||
|
service HealthService {
|
||||||
|
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
|
option (micro.openapiv3.openapiv3_operation) = {
|
||||||
|
operation_id: "Live";
|
||||||
|
responses: {
|
||||||
|
default: {
|
||||||
|
reference: {
|
||||||
|
_ref: "micro.codec.Frame";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
option (micro.api.http) = { get: "/live"; };
|
||||||
|
};
|
||||||
|
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
|
option (micro.openapiv3.openapiv3_operation) = {
|
||||||
|
operation_id: "Ready";
|
||||||
|
responses: {
|
||||||
|
default: {
|
||||||
|
reference: {
|
||||||
|
_ref: "micro.codec.Frame";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
option (micro.api.http) = { get: "/ready"; };
|
||||||
|
};
|
||||||
|
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
|
option (micro.openapiv3.openapiv3_operation) = {
|
||||||
|
operation_id: "Version";
|
||||||
|
responses: {
|
||||||
|
default: {
|
||||||
|
reference: {
|
||||||
|
_ref: "micro.codec.Frame";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
option (micro.api.http) = { get: "/version"; };
|
||||||
|
};
|
||||||
|
};
|
22
handler/health/health_micro.pb.go
Normal file
22
handler/health/health_micro.pb.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-micro v4.0.2
|
||||||
|
// - protoc v4.23.4
|
||||||
|
// source: health/health.proto
|
||||||
|
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
codec "go.unistack.org/micro-proto/v4/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
HealthServiceName = "HealthService"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthServiceServer interface {
|
||||||
|
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
}
|
70
handler/health/health_micro_http.pb.go
Normal file
70
handler/health/health_micro_http.pb.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// protoc-gen-go-micro version: v4.0.2
|
||||||
|
// source: health/health.proto
|
||||||
|
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
codec "go.unistack.org/micro-proto/v4/codec"
|
||||||
|
v4 "go.unistack.org/micro-server-http/v4"
|
||||||
|
options "go.unistack.org/micro/v4/options"
|
||||||
|
server "go.unistack.org/micro/v4/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
HealthServiceServerEndpoints = []v4.EndpointMetadata{
|
||||||
|
{
|
||||||
|
Name: "HealthService.Live",
|
||||||
|
Path: "/live",
|
||||||
|
Method: "GET",
|
||||||
|
Body: "",
|
||||||
|
Stream: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "HealthService.Ready",
|
||||||
|
Path: "/ready",
|
||||||
|
Method: "GET",
|
||||||
|
Body: "",
|
||||||
|
Stream: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "HealthService.Version",
|
||||||
|
Path: "/version",
|
||||||
|
Method: "GET",
|
||||||
|
Body: "",
|
||||||
|
Stream: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type healthServiceServer struct {
|
||||||
|
HealthServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healthServiceServer) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||||
|
return h.HealthServiceServer.Live(ctx, req, rsp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healthServiceServer) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||||
|
return h.HealthServiceServer.Ready(ctx, req, rsp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healthServiceServer) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||||
|
return h.HealthServiceServer.Version(ctx, req, rsp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterHealthServiceServer(s server.Server, sh HealthServiceServer, opts ...options.Option) error {
|
||||||
|
type healthService interface {
|
||||||
|
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
}
|
||||||
|
type HealthService struct {
|
||||||
|
healthService
|
||||||
|
}
|
||||||
|
h := &healthServiceServer{sh}
|
||||||
|
var nopts []options.Option
|
||||||
|
nopts = append(nopts, v4.HandlerEndpoints(HealthServiceServerEndpoints))
|
||||||
|
return s.Handle(&HealthService{h}, append(nopts, opts...)...)
|
||||||
|
}
|
133
handler/meter/meter.go
Normal file
133
handler/meter/meter.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package meter // import "go.unistack.org/micro-server-http/v4/handler/meter"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
codecpb "go.unistack.org/micro-proto/v4/codec"
|
||||||
|
"go.unistack.org/micro/v4/logger"
|
||||||
|
"go.unistack.org/micro/v4/metadata"
|
||||||
|
"go.unistack.org/micro/v4/meter"
|
||||||
|
"go.unistack.org/micro/v4/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
contentEncodingHeader = "Content-Encoding"
|
||||||
|
acceptEncodingHeader = "Accept-Encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gzipPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return gzip.NewWriter(nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return bytes.NewBuffer(nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// guard to fail early
|
||||||
|
var _ MeterServiceServer = (*Handler)(nil)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
opts Options
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Meter meter.Meter
|
||||||
|
Name string
|
||||||
|
MeterOptions []options.Option
|
||||||
|
DisableCompress bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Meter(m meter.Meter) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Meter = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name(name string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableCompress(g bool) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.DisableCompress = g
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MeterOptions(opts ...options.Option) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.MeterOptions = append(o.MeterOptions, opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOptions(opts ...Option) Options {
|
||||||
|
options := Options{Meter: meter.DefaultMeter, DisableCompress: false}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(opts ...Option) *Handler {
|
||||||
|
options := NewOptions(opts...)
|
||||||
|
return &Handler{opts: options}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Metrics(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||||
|
log, ok := logger.FromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
log = logger.DefaultLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
|
defer bufPool.Put(buf)
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
w := io.Writer(buf)
|
||||||
|
|
||||||
|
if md, ok := metadata.FromIncomingContext(ctx); gzipAccepted(md) && ok && !h.opts.DisableCompress {
|
||||||
|
omd, _ := metadata.FromOutgoingContext(ctx)
|
||||||
|
omd.Set(contentEncodingHeader, "gzip")
|
||||||
|
gz := gzipPool.Get().(*gzip.Writer)
|
||||||
|
defer gzipPool.Put(gz)
|
||||||
|
|
||||||
|
gz.Reset(w)
|
||||||
|
defer gz.Close()
|
||||||
|
|
||||||
|
w = gz
|
||||||
|
gz.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.opts.Meter.Write(w, h.opts.MeterOptions...); err != nil {
|
||||||
|
log.Error(ctx, "http/meter: write failed", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rsp.Data = buf.Bytes()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gzipAccepted returns whether the client will accept gzip-encoded content.
|
||||||
|
func gzipAccepted(md metadata.Metadata) bool {
|
||||||
|
a, ok := md.Get(acceptEncodingHeader)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.Contains(a, "gzip") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
24
handler/meter/meter.proto
Normal file
24
handler/meter/meter.proto
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package micro.server.http.v4.handler.meter;
|
||||||
|
option go_package = "go.unistack.org/micro-server-http/v4/handler/meter;meter";
|
||||||
|
|
||||||
|
import "api/annotations.proto";
|
||||||
|
import "openapiv3/annotations.proto";
|
||||||
|
import "codec/frame.proto";
|
||||||
|
|
||||||
|
service MeterService {
|
||||||
|
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
|
option (micro.openapiv3.openapiv3_operation) = {
|
||||||
|
operation_id: "Metrics";
|
||||||
|
responses: {
|
||||||
|
default: {
|
||||||
|
reference: {
|
||||||
|
_ref: "micro.codec.Frame";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
option (micro.api.http) = { get: "/metrics"; };
|
||||||
|
};
|
||||||
|
};
|
20
handler/meter/meter_micro.pb.go
Normal file
20
handler/meter/meter_micro.pb.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-micro v4.0.2
|
||||||
|
// - protoc v4.23.4
|
||||||
|
// source: meter/meter.proto
|
||||||
|
|
||||||
|
package meter
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
codec "go.unistack.org/micro-proto/v4/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
MeterServiceName = "MeterService"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MeterServiceServer interface {
|
||||||
|
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
}
|
46
handler/meter/meter_micro_http.pb.go
Normal file
46
handler/meter/meter_micro_http.pb.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// protoc-gen-go-micro version: v4.0.2
|
||||||
|
// source: meter/meter.proto
|
||||||
|
|
||||||
|
package meter
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
codec "go.unistack.org/micro-proto/v4/codec"
|
||||||
|
v4 "go.unistack.org/micro-server-http/v4"
|
||||||
|
options "go.unistack.org/micro/v4/options"
|
||||||
|
server "go.unistack.org/micro/v4/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
MeterServiceServerEndpoints = []v4.EndpointMetadata{
|
||||||
|
{
|
||||||
|
Name: "MeterService.Metrics",
|
||||||
|
Path: "/metrics",
|
||||||
|
Method: "GET",
|
||||||
|
Body: "",
|
||||||
|
Stream: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type meterServiceServer struct {
|
||||||
|
MeterServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *meterServiceServer) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||||
|
return h.MeterServiceServer.Metrics(ctx, req, rsp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterMeterServiceServer(s server.Server, sh MeterServiceServer, opts ...options.Option) error {
|
||||||
|
type meterService interface {
|
||||||
|
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||||
|
}
|
||||||
|
type MeterService struct {
|
||||||
|
meterService
|
||||||
|
}
|
||||||
|
h := &meterServiceServer{sh}
|
||||||
|
var nopts []options.Option
|
||||||
|
nopts = append(nopts, v4.HandlerEndpoints(MeterServiceServerEndpoints))
|
||||||
|
return s.Handle(&MeterService{h}, append(nopts, opts...)...)
|
||||||
|
}
|
49
handler/meter/meter_test.go
Normal file
49
handler/meter/meter_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package meter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
codecpb "go.unistack.org/micro-proto/v4/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandler_Metrics(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
opts Options
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
req *codecpb.Frame
|
||||||
|
rsp *codecpb.Frame
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Test #1",
|
||||||
|
fields{
|
||||||
|
opts: NewOptions(),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
context.Background(),
|
||||||
|
&codecpb.Frame{Data: []byte("gzip")},
|
||||||
|
&codecpb.Frame{},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
h := &Handler{
|
||||||
|
opts: tt.fields.opts,
|
||||||
|
}
|
||||||
|
if err := h.Metrics(tt.args.ctx, tt.args.req, tt.args.rsp); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Metrics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
t.Logf("RSP: %v", tt.args.rsp.Data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
19
handler/spa/spa.go
Normal file
19
handler/spa/spa.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package spa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler serve files from dir and redirect to index if file not exists
|
||||||
|
var Handler = func(prefix string, dir fs.FS) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
f := http.StripPrefix(prefix, http.FileServer(http.FS(dir)))
|
||||||
|
if _, err := fs.Stat(dir, strings.TrimPrefix(r.RequestURI, prefix)); err != nil {
|
||||||
|
r.RequestURI = prefix
|
||||||
|
r.URL.Path = prefix
|
||||||
|
}
|
||||||
|
f.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
BIN
handler/swagger-ui/favicon-16x16.png
Normal file
BIN
handler/swagger-ui/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
handler/swagger-ui/favicon-32x32.png
Normal file
BIN
handler/swagger-ui/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
16
handler/swagger-ui/index.css
Normal file
16
handler/swagger-ui/index.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: -moz-scrollbars-vertical;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
19
handler/swagger-ui/index.html
Normal file
19
handler/swagger-ui/index.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!-- HTML for static distribution bundle build -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Swagger UI</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="index.css" />
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||||
|
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||||
|
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
|
||||||
|
</body>
|
||||||
|
</html>
|
79
handler/swagger-ui/oauth2-redirect.html
Normal file
79
handler/swagger-ui/oauth2-redirect.html
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<title>Swagger UI: OAuth2 Redirect</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
function run () {
|
||||||
|
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||||
|
var sentState = oauth2.state;
|
||||||
|
var redirectUrl = oauth2.redirectUrl;
|
||||||
|
var isValid, qp, arr;
|
||||||
|
|
||||||
|
if (/code|token|error/.test(window.location.hash)) {
|
||||||
|
qp = window.location.hash.substring(1).replace('?', '&');
|
||||||
|
} else {
|
||||||
|
qp = location.search.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr = qp.split("&");
|
||||||
|
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||||
|
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||||
|
function (key, value) {
|
||||||
|
return key === "" ? value : decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
) : {};
|
||||||
|
|
||||||
|
isValid = qp.state === sentState;
|
||||||
|
|
||||||
|
if ((
|
||||||
|
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||||
|
) && !oauth2.auth.code) {
|
||||||
|
if (!isValid) {
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "warning",
|
||||||
|
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qp.code) {
|
||||||
|
delete oauth2.state;
|
||||||
|
oauth2.auth.code = qp.code;
|
||||||
|
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||||
|
} else {
|
||||||
|
let oauthErrorMsg;
|
||||||
|
if (qp.error) {
|
||||||
|
oauthErrorMsg = "["+qp.error+"]: " +
|
||||||
|
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||||
|
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "error",
|
||||||
|
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState !== 'loading') {
|
||||||
|
run();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
handler/swagger-ui/swagger-ui-bundle.js
Normal file
3
handler/swagger-ui/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
3
handler/swagger-ui/swagger-ui-standalone-preset.js
Normal file
3
handler/swagger-ui/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
3
handler/swagger-ui/swagger-ui.css
Normal file
3
handler/swagger-ui/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
142
handler/swagger-ui/swaggerui.go
Normal file
142
handler/swagger-ui/swaggerui.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package swaggerui // import "go.unistack.org/micro-server-http/v4/handler/swagger-ui"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.js *.css *.html *.png
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
Handler = func(prefix string) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet || path.Base(r.URL.Path) != "swagger-initializer.js" {
|
||||||
|
http.StripPrefix(prefix, http.FileServer(http.FS(assets))).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tpl := template.New("swagger-initializer.js").Funcs(TemplateFuncs)
|
||||||
|
ptpl, err := tpl.Parse(Template)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ptpl.Execute(w, Config); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TemplateFuncs = template.FuncMap{
|
||||||
|
"isInt": func(i interface{}) bool {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isBool": func(i interface{}) bool {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isString": func(i interface{}) bool {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isSlice": func(i interface{}) bool {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isMap": func(i interface{}) bool {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Template = `
|
||||||
|
window.onload = function() {
|
||||||
|
//<editor-fold desc="Changeable Configuration Block">
|
||||||
|
|
||||||
|
window.ui = SwaggerUIBundle({
|
||||||
|
{{- range $k, $v := . }}
|
||||||
|
{{- if (eq (printf "%s" $v) "") -}}
|
||||||
|
{{- continue -}}
|
||||||
|
{{ end }}
|
||||||
|
{{ $k }}: {{ if isBool $v -}}
|
||||||
|
{{- $v -}},
|
||||||
|
{{- else if isInt $v -}}
|
||||||
|
{{- $v -}},
|
||||||
|
{{- else if isString $v -}}
|
||||||
|
"{{- $v -}}",
|
||||||
|
{{- else if and (isSlice $v) (or (eq (printf "%s" $k) "presets") (eq (printf "%s" $k) "plugins")) -}}
|
||||||
|
[
|
||||||
|
{{- range $v }}
|
||||||
|
{{ . }},
|
||||||
|
{{- end }}
|
||||||
|
],
|
||||||
|
{{- end -}}
|
||||||
|
{{ end }}
|
||||||
|
});
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
};`
|
||||||
|
Config = map[string]interface{}{
|
||||||
|
"configUrl": "",
|
||||||
|
"dom_id": "#swagger-ui",
|
||||||
|
/*
|
||||||
|
"domNode": "",
|
||||||
|
"spec": "",
|
||||||
|
"urls": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"url": "",
|
||||||
|
"name": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
"url": "https://petstore.swagger.io/v2/swagger.json",
|
||||||
|
"deepLinking": true,
|
||||||
|
"displayOperationId": false,
|
||||||
|
"defaultModelsExpandDepth": 1,
|
||||||
|
"defaultModelExpandDepth": 1,
|
||||||
|
"displayRequestDuration": true,
|
||||||
|
"filter": true,
|
||||||
|
"operationsSorter": "alpha",
|
||||||
|
"showExtensions": true,
|
||||||
|
"tryItOutEnabled": true,
|
||||||
|
"presets": []string{
|
||||||
|
"SwaggerUIBundle.presets.apis",
|
||||||
|
"SwaggerUIStandalonePreset",
|
||||||
|
},
|
||||||
|
"plugins": []string{
|
||||||
|
"SwaggerUIBundle.plugins.DownloadUrl",
|
||||||
|
},
|
||||||
|
"layout": "StandaloneLayout",
|
||||||
|
}
|
||||||
|
)
|
15
handler/swagger-ui/swaggerui_test.go
Normal file
15
handler/swagger-ui/swaggerui_test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package swaggerui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTemplate(t *testing.T) {
|
||||||
|
t.Skip()
|
||||||
|
h := http.NewServeMux()
|
||||||
|
h.HandleFunc("/", Handler(""))
|
||||||
|
if err := http.ListenAndServe(":8080", h); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
61
handler/swagger/swagger.go
Normal file
61
handler/swagger/swagger.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package swagger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
yamlcodec "go.unistack.org/micro-codec-yaml/v4"
|
||||||
|
rutil "go.unistack.org/micro/v4/util/reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler append to generated swagger data from dst map[string]interface{}
|
||||||
|
var Handler = func(dst map[string]interface{}, fsys fs.FS) http.HandlerFunc {
|
||||||
|
c := yamlcodec.NewCodec()
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := r.URL.Path
|
||||||
|
if len(path) > 1 && path[0] == '/' {
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := fs.ReadFile(fsys, path)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst == nil {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write(buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var src interface{}
|
||||||
|
|
||||||
|
if err = c.Unmarshal(buf, src); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rutil.Merge(src, dst); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf, err = c.Marshal(src); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write(buf)
|
||||||
|
}
|
||||||
|
}
|
447
http.go
447
http.go
@ -1,121 +1,191 @@
|
|||||||
// Package http implements a go-micro.Server
|
// Package http implements a go-micro.Server
|
||||||
package http
|
package http // import "go.unistack.org/micro-server-http/v4"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/broker"
|
"go.unistack.org/micro/v4/codec"
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"go.unistack.org/micro/v4/logger"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"go.unistack.org/micro/v4/options"
|
||||||
"github.com/unistack-org/micro/v3/register"
|
"go.unistack.org/micro/v4/register"
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v4/server"
|
||||||
rutil "github.com/unistack-org/micro/v3/util/router"
|
rhttp "go.unistack.org/micro/v4/util/http"
|
||||||
"golang.org/x/net/netutil"
|
"golang.org/x/net/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpServer struct {
|
var _ server.Server = (*Server)(nil)
|
||||||
sync.RWMutex
|
|
||||||
opts server.Options
|
type Server struct {
|
||||||
hd server.Handler
|
hd interface{}
|
||||||
exit chan chan error
|
|
||||||
subscribers map[*httpSubscriber][]broker.Subscriber
|
|
||||||
// used for first registration
|
|
||||||
registered bool
|
|
||||||
// register service instance
|
|
||||||
rsvc *register.Service
|
rsvc *register.Service
|
||||||
|
handlers map[string]interface{}
|
||||||
|
exit chan chan error
|
||||||
|
errorHandler func(context.Context, interface{}, http.ResponseWriter, *http.Request, error, int)
|
||||||
|
pathHandlers *rhttp.Trie
|
||||||
|
opts server.Options
|
||||||
|
registerRPC bool
|
||||||
|
sync.RWMutex
|
||||||
|
registered bool
|
||||||
|
init bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) newCodec(ct string) (codec.Codec, error) {
|
func (h *Server) newCodec(ct string) (codec.Codec, error) {
|
||||||
if cf, ok := h.opts.Codecs[ct]; ok {
|
if idx := strings.IndexRune(ct, ';'); idx >= 0 {
|
||||||
|
ct = ct[:idx]
|
||||||
|
}
|
||||||
|
h.RLock()
|
||||||
|
cf, ok := h.opts.Codecs[ct]
|
||||||
|
h.RUnlock()
|
||||||
|
if ok {
|
||||||
return cf, nil
|
return cf, nil
|
||||||
}
|
}
|
||||||
return nil, codec.ErrUnknownContentType
|
return nil, codec.ErrUnknownContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) Options() server.Options {
|
func (h *Server) Options() server.Options {
|
||||||
h.Lock()
|
h.Lock()
|
||||||
opts := h.opts
|
opts := h.opts
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) Init(opts ...server.Option) error {
|
func (h *Server) Init(opts ...options.Option) error {
|
||||||
|
if len(opts) == 0 && h.init {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
h.Lock()
|
h.Lock()
|
||||||
|
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&h.opts)
|
o(&h.opts)
|
||||||
}
|
}
|
||||||
|
if fn, ok := h.opts.Context.Value(errorHandlerKey{}).(func(ctx context.Context, s interface{}, w http.ResponseWriter, r *http.Request, err error, status int)); ok && fn != nil {
|
||||||
|
h.errorHandler = fn
|
||||||
|
}
|
||||||
|
if h.handlers == nil {
|
||||||
|
h.handlers = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
if h.pathHandlers == nil {
|
||||||
|
h.pathHandlers = rhttp.NewTrie()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := h.opts.Context.Value(registerRPCHandlerKey{}).(bool); ok {
|
||||||
|
h.registerRPC = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if phs, ok := h.opts.Context.Value(pathHandlerKey{}).(*pathHandlerVal); ok && phs.h != nil {
|
||||||
|
for pm, ps := range phs.h {
|
||||||
|
for pp, ph := range ps {
|
||||||
|
if err := h.pathHandlers.Insert([]string{pm}, pp, ph); err != nil {
|
||||||
|
h.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.Unlock()
|
||||||
|
|
||||||
|
h.RLock()
|
||||||
|
if err := h.opts.Register.Init(); err != nil {
|
||||||
|
h.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := h.opts.Tracer.Init(); err != nil {
|
||||||
|
h.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := h.opts.Logger.Init(); err != nil {
|
||||||
|
h.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := h.opts.Meter.Init(); err != nil {
|
||||||
|
h.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.RUnlock()
|
||||||
|
|
||||||
|
h.Lock()
|
||||||
|
h.init = true
|
||||||
|
h.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Server) Handle(handler interface{}, opts ...options.Option) error {
|
||||||
|
options := server.NewHandleOptions(opts...)
|
||||||
|
var endpointMetadata []EndpointMetadata
|
||||||
|
|
||||||
|
if v, ok := options.Context.Value(handlerEndpointsKey{}).([]EndpointMetadata); ok {
|
||||||
|
endpointMetadata = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// passed unknown handler
|
||||||
|
hdlr, ok := handler.(*httpHandler)
|
||||||
|
if !ok {
|
||||||
|
h.Lock()
|
||||||
|
if h.handlers == nil {
|
||||||
|
h.handlers = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
for _, v := range endpointMetadata {
|
||||||
|
h.handlers[v.Name] = h.newHTTPHandler(handler, opts...)
|
||||||
|
}
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) Handle(handler server.Handler) error {
|
// passed http.Handler like some muxer
|
||||||
|
if _, ok := hdlr.hd.(http.Handler); ok {
|
||||||
h.Lock()
|
h.Lock()
|
||||||
h.hd = handler
|
h.hd = handler
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
|
return nil
|
||||||
options := server.NewHandlerOptions(opts...)
|
}
|
||||||
|
|
||||||
var eps []*register.Endpoint
|
func (h *Server) newHTTPHandler(handler interface{}, opts ...options.Option) *httpHandler {
|
||||||
|
options := server.NewHandleOptions(opts...)
|
||||||
|
|
||||||
if !options.Internal {
|
eps := make([]*register.Endpoint, 0, len(options.Metadata))
|
||||||
for name, metadata := range options.Metadata {
|
for name, metadata := range options.Metadata {
|
||||||
eps = append(eps, ®ister.Endpoint{
|
eps = append(eps, ®ister.Endpoint{
|
||||||
Name: name,
|
Name: name,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
hdlr := &httpHandler{
|
hdlr := &httpHandler{
|
||||||
eps: eps,
|
eps: eps,
|
||||||
hd: handler,
|
hd: handler,
|
||||||
opts: options,
|
opts: options,
|
||||||
sopts: h.opts,
|
sopts: h.opts,
|
||||||
|
handlers: rhttp.NewTrie(),
|
||||||
}
|
}
|
||||||
|
|
||||||
tp := reflect.TypeOf(handler)
|
tp := reflect.TypeOf(handler)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
for m := 0; m < tp.NumMethod(); m++ {
|
if len(options.Metadata) == 0 {
|
||||||
if e := register.ExtractEndpoint(tp.Method(m)); e != nil {
|
if h.registerRPC {
|
||||||
e.Name = name + "." + e.Name
|
h.opts.Logger.Infof(h.opts.Context, "register rpc handler for http.MethodPost %s /%s", hn, hn)
|
||||||
|
if err := hdlr.handlers.Insert([]string{http.MethodPost}, "/"+hn, pth); err != nil {
|
||||||
for k, v := range options.Metadata[e.Name] {
|
h.opts.Logger.Errorf(h.opts.Context, "cant add rpc handler for http.MethodPost %s /%s", hn, hn)
|
||||||
e.Metadata[k] = v
|
}
|
||||||
}
|
|
||||||
|
|
||||||
eps = append(eps, e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
hdlr.handlers = make(map[string][]patHandler)
|
|
||||||
for hn, md := range options.Metadata {
|
for hn, md := range options.Metadata {
|
||||||
cmp, err := rutil.Parse(md["Path"])
|
|
||||||
if err != nil && h.opts.Logger.V(logger.ErrorLevel) {
|
|
||||||
h.opts.Logger.Errorf(h.opts.Context, "parsing path pattern err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tpl := cmp.Compile()
|
|
||||||
pat, err := rutil.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, tpl.Verb)
|
|
||||||
if err != nil && h.opts.Logger.V(logger.ErrorLevel) {
|
|
||||||
h.opts.Logger.Errorf(h.opts.Context, "creating new pattern err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var method reflect.Method
|
var method reflect.Method
|
||||||
mname := hn[strings.Index(hn, ".")+1:]
|
mname := hn[strings.Index(hn, ".")+1:]
|
||||||
for m := 0; m < tp.NumMethod(); m++ {
|
for m := 0; m < tp.NumMethod(); m++ {
|
||||||
@ -128,59 +198,100 @@ func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) {
|
if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) {
|
||||||
h.opts.Logger.Errorf(h.opts.Context, "nil method for %s", mname)
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil method for %s", mname))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mtype, err := prepareEndpoint(method)
|
mtype, err := prepareEndpoint(method)
|
||||||
if err != nil && h.opts.Logger.V(logger.ErrorLevel) {
|
if err != nil && h.opts.Logger.V(logger.ErrorLevel) {
|
||||||
h.opts.Logger.Errorf(h.opts.Context, "%v", err)
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("%v", err))
|
||||||
continue
|
continue
|
||||||
} else if mtype == nil {
|
} else if mtype == nil {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil mtype for %s", mname))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rcvr := reflect.ValueOf(handler)
|
rcvr := reflect.ValueOf(handler)
|
||||||
name := reflect.Indirect(rcvr).Type().Name()
|
name := reflect.Indirect(rcvr).Type().Name()
|
||||||
|
|
||||||
pth := patHandler{pat: pat, mtype: mtype, name: name, rcvr: rcvr}
|
pth := &patHandler{mtype: mtype, name: name, rcvr: rcvr}
|
||||||
hdlr.name = name
|
hdlr.name = name
|
||||||
hdlr.handlers[md["Method"]] = append(hdlr.handlers[md["Method"]], pth)
|
|
||||||
|
if err := hdlr.handlers.Insert([]string{md["Method"]}, md["Path"], pth); err != nil {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add handler for %s %s", md["Method"][0], md["Path"][0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.registerRPC {
|
||||||
|
h.opts.Logger.Info(h.opts.Context, fmt.Sprintf("register rpc handler for http.MethodPost %s /%s", hn, hn))
|
||||||
|
if err := hdlr.handlers.Insert([]string{http.MethodPost}, "/"+hn, pth); err != nil {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add rpc handler for http.MethodPost %s /%s", hn, hn))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, ok := options.Context.Value(handlerEndpointsKey{}).([]EndpointMetadata)
|
||||||
|
if !ok {
|
||||||
|
return hdlr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, md := range metadata {
|
||||||
|
hn := md.Name
|
||||||
|
var method reflect.Method
|
||||||
|
mname := hn[strings.Index(hn, ".")+1:]
|
||||||
|
for m := 0; m < tp.NumMethod(); m++ {
|
||||||
|
mn := tp.Method(m)
|
||||||
|
if mn.Name != mname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
method = mn
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil method for %s", mname))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mtype, err := prepareEndpoint(method)
|
||||||
|
if err != nil && h.opts.Logger.V(logger.ErrorLevel) {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("%v", err))
|
||||||
|
continue
|
||||||
|
} else if mtype == nil {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil mtype for %s", mname))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rcvr := reflect.ValueOf(handler)
|
||||||
|
name := reflect.Indirect(rcvr).Type().Name()
|
||||||
|
|
||||||
|
pth := &patHandler{mtype: mtype, name: name, rcvr: rcvr}
|
||||||
|
hdlr.name = name
|
||||||
|
|
||||||
|
if err := hdlr.handlers.Insert([]string{md.Method}, md.Path, pth); err != nil {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add handler for %s %s", md.Method, md.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.registerRPC {
|
||||||
|
h.opts.Logger.Info(h.opts.Context, fmt.Sprintf("register rpc handler for http.MethodPost %s /%s", hn, hn))
|
||||||
|
if err := hdlr.handlers.Insert([]string{http.MethodPost}, "/"+hn, pth); err != nil {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add rpc handler for http.MethodPost %s /%s", hn, hn))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hdlr
|
return hdlr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) NewSubscriber(topic string, handler interface{}, opts ...server.SubscriberOption) server.Subscriber {
|
func (h *Server) Register() error {
|
||||||
return newSubscriber(topic, handler, opts...)
|
var eps []*register.Endpoint
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpServer) Subscribe(sb server.Subscriber) error {
|
|
||||||
sub, ok := sb.(*httpSubscriber)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid subscriber: expected *httpSubscriber")
|
|
||||||
}
|
|
||||||
if len(sub.handlers) == 0 {
|
|
||||||
return fmt.Errorf("invalid subscriber: no handler functions")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := server.ValidateSubscriber(sb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
_, ok = h.subscribers[sub]
|
|
||||||
if ok {
|
|
||||||
return fmt.Errorf("subscriber %v already exists", h)
|
|
||||||
}
|
|
||||||
h.subscribers[sub] = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpServer) Register() error {
|
|
||||||
h.RLock()
|
h.RLock()
|
||||||
eps := h.hd.Endpoints()
|
for _, hdlr := range h.handlers {
|
||||||
|
hd, ok := hdlr.(*httpHandler)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eps = append(eps, hd.Endpoints()...)
|
||||||
|
}
|
||||||
rsvc := h.rsvc
|
rsvc := h.rsvc
|
||||||
config := h.opts
|
config := h.opts
|
||||||
h.RUnlock()
|
h.RUnlock()
|
||||||
@ -197,33 +308,16 @@ func (h *httpServer) Register() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
service.Nodes[0].Metadata["protocol"] = "http"
|
service.Nodes[0].Metadata.Set("protocol", "http")
|
||||||
service.Endpoints = eps
|
service.Endpoints = eps
|
||||||
|
|
||||||
h.Lock()
|
|
||||||
var subscriberList []*httpSubscriber
|
|
||||||
for e := range h.subscribers {
|
|
||||||
// Only advertise non internal subscribers
|
|
||||||
if !e.Options().Internal {
|
|
||||||
subscriberList = append(subscriberList, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Slice(subscriberList, func(i, j int) bool {
|
|
||||||
return subscriberList[i].topic > subscriberList[j].topic
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, e := range subscriberList {
|
|
||||||
service.Endpoints = append(service.Endpoints, e.Endpoints()...)
|
|
||||||
}
|
|
||||||
h.Unlock()
|
|
||||||
|
|
||||||
h.RLock()
|
h.RLock()
|
||||||
registered := h.registered
|
registered := h.registered
|
||||||
h.RUnlock()
|
h.RUnlock()
|
||||||
|
|
||||||
if !registered {
|
if !registered {
|
||||||
if config.Logger.V(logger.InfoLevel) {
|
if config.Logger.V(logger.InfoLevel) {
|
||||||
config.Logger.Infof(config.Context, "Register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].Id)
|
config.Logger.Info(config.Context, fmt.Sprintf("Register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,36 +332,14 @@ func (h *httpServer) Register() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
|
||||||
|
|
||||||
for sb := range h.subscribers {
|
|
||||||
handler := h.createSubHandler(sb, config)
|
|
||||||
var opts []broker.SubscribeOption
|
|
||||||
if queue := sb.Options().Queue; len(queue) > 0 {
|
|
||||||
opts = append(opts, broker.SubscribeGroup(queue))
|
|
||||||
}
|
|
||||||
|
|
||||||
subCtx := config.Context
|
|
||||||
if cx := sb.Options().Context; cx != nil {
|
|
||||||
subCtx = cx
|
|
||||||
}
|
|
||||||
opts = append(opts, broker.SubscribeContext(subCtx))
|
|
||||||
opts = append(opts, broker.SubscribeAutoAck(sb.Options().AutoAck))
|
|
||||||
|
|
||||||
sub, err := config.Broker.Subscribe(subCtx, sb.Topic(), handler, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.subscribers[sb] = []broker.Subscriber{sub}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.registered = true
|
h.registered = true
|
||||||
h.rsvc = service
|
h.rsvc = service
|
||||||
|
h.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) Deregister() error {
|
func (h *Server) Deregister() error {
|
||||||
h.RLock()
|
h.RLock()
|
||||||
config := h.opts
|
config := h.opts
|
||||||
h.RUnlock()
|
h.RUnlock()
|
||||||
@ -278,7 +350,7 @@ func (h *httpServer) Deregister() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Logger.V(logger.InfoLevel) {
|
if config.Logger.V(logger.InfoLevel) {
|
||||||
config.Logger.Infof(config.Context, "Deregistering node: %s", service.Nodes[0].Id)
|
config.Logger.Info(config.Context, fmt.Sprintf("Deregistering node: %s", service.Nodes[0].ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := server.DefaultDeregisterFunc(service, config); err != nil {
|
if err := server.DefaultDeregisterFunc(service, config); err != nil {
|
||||||
@ -294,30 +366,13 @@ func (h *httpServer) Deregister() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h.registered = false
|
h.registered = false
|
||||||
|
|
||||||
subCtx := h.opts.Context
|
|
||||||
for sb, subs := range h.subscribers {
|
|
||||||
if cx := sb.Options().Context; cx != nil {
|
|
||||||
subCtx = cx
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sub := range subs {
|
|
||||||
config.Logger.Infof(config.Context, "Unsubscribing from topic: %s", sub.Topic())
|
|
||||||
if err := sub.Unsubscribe(subCtx); err != nil {
|
|
||||||
config.Logger.Errorf(config.Context, "failed to unsubscribe topic: %s, error: %v", sb.Topic(), err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.subscribers[sb] = nil
|
|
||||||
}
|
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) Start() error {
|
func (h *Server) Start() error {
|
||||||
h.RLock()
|
h.RLock()
|
||||||
config := h.opts
|
config := h.opts
|
||||||
hd := h.hd
|
|
||||||
h.RUnlock()
|
h.RUnlock()
|
||||||
|
|
||||||
// micro: config.Transport.Listen(config.Address)
|
// micro: config.Transport.Listen(config.Address)
|
||||||
@ -345,29 +400,47 @@ func (h *httpServer) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Logger.V(logger.InfoLevel) {
|
if config.Logger.V(logger.InfoLevel) {
|
||||||
config.Logger.Infof(config.Context, "Listening on %s", ts.Addr().String())
|
config.Logger.Info(config.Context, fmt.Sprintf("Listening on %s", ts.Addr().String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Lock()
|
h.Lock()
|
||||||
h.opts.Address = ts.Addr().String()
|
h.opts.Address = ts.Addr().String()
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
|
|
||||||
handler, ok := hd.Handler().(http.Handler)
|
var handler http.Handler
|
||||||
if !ok {
|
|
||||||
handler, ok = hd.(http.Handler)
|
// nolint: nestif
|
||||||
|
if h.opts.Context != nil {
|
||||||
|
if hs, ok := h.opts.Context.Value(serverKey{}).(*http.Server); ok && hs != nil {
|
||||||
|
if hs.Handler == nil && h.hd != nil {
|
||||||
|
if hdlr, ok := h.hd.(http.Handler); ok {
|
||||||
|
hs.Handler = hdlr
|
||||||
|
handler = hs.Handler
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handler = hs.Handler
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
switch {
|
||||||
return errors.New("Server required http.Handler")
|
case handler == nil && h.hd == nil:
|
||||||
|
handler = h
|
||||||
|
case len(h.handlers) > 0 && h.hd != nil:
|
||||||
|
handler = h
|
||||||
|
case handler == nil && h.hd != nil:
|
||||||
|
if hdlr, ok := h.hd.(http.Handler); ok {
|
||||||
|
handler = hdlr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.Broker.Connect(h.opts.Context); err != nil {
|
if handler == nil {
|
||||||
return err
|
return fmt.Errorf("cant process with nil handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.RegisterCheck(h.opts.Context); err != nil {
|
if err := config.RegisterCheck(h.opts.Context); err != nil {
|
||||||
if config.Logger.V(logger.ErrorLevel) {
|
if config.Logger.V(logger.ErrorLevel) {
|
||||||
config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s", config.Name, config.Id, err)
|
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error: %s", config.Name, config.ID, err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = h.Register(); err != nil {
|
if err = h.Register(); err != nil {
|
||||||
@ -375,7 +448,29 @@ func (h *httpServer) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go http.Serve(ts, handler)
|
fn := handler
|
||||||
|
|
||||||
|
var hs *http.Server
|
||||||
|
if h.opts.Context != nil {
|
||||||
|
if mwf, ok := h.opts.Context.Value(middlewareKey{}).([]func(http.Handler) http.Handler); ok && len(mwf) > 0 {
|
||||||
|
// wrap the handler func
|
||||||
|
for i := len(mwf); i > 0; i-- {
|
||||||
|
fn = mwf[i-1](fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
if hs, ok = h.opts.Context.Value(serverKey{}).(*http.Server); ok && hs != nil {
|
||||||
|
hs.Handler = fn
|
||||||
|
} else {
|
||||||
|
hs = &http.Server{Handler: fn}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if cerr := hs.Serve(ts); cerr != nil && !errors.Is(cerr, net.ErrClosed) {
|
||||||
|
h.opts.Logger.Error(h.opts.Context, cerr.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
t := new(time.Ticker)
|
t := new(time.Ticker)
|
||||||
@ -398,30 +493,31 @@ func (h *httpServer) Start() error {
|
|||||||
registered := h.registered
|
registered := h.registered
|
||||||
h.RUnlock()
|
h.RUnlock()
|
||||||
rerr := config.RegisterCheck(h.opts.Context)
|
rerr := config.RegisterCheck(h.opts.Context)
|
||||||
|
// nolint: nestif
|
||||||
if rerr != nil && registered {
|
if rerr != nil && registered {
|
||||||
if config.Logger.V(logger.ErrorLevel) {
|
if config.Logger.V(logger.ErrorLevel) {
|
||||||
config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
|
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error: %s, deregister it", config.Name, config.ID, rerr))
|
||||||
}
|
}
|
||||||
// deregister self in case of error
|
// deregister self in case of error
|
||||||
if err := h.Deregister(); err != nil {
|
if err := h.Deregister(); err != nil {
|
||||||
if config.Logger.V(logger.ErrorLevel) {
|
if config.Logger.V(logger.ErrorLevel) {
|
||||||
config.Logger.Errorf(config.Context, "Server %s-%s deregister error: %s", config.Name, config.Id, err)
|
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s deregister error: %s", config.Name, config.ID, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if rerr != nil && !registered {
|
} else if rerr != nil && !registered {
|
||||||
if config.Logger.V(logger.ErrorLevel) {
|
if config.Logger.V(logger.ErrorLevel) {
|
||||||
config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s", config.Name, config.Id, rerr)
|
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error: %s", config.Name, config.ID, rerr))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := h.Register(); err != nil {
|
if err := h.Register(); err != nil {
|
||||||
if config.Logger.V(logger.ErrorLevel) {
|
if config.Logger.V(logger.ErrorLevel) {
|
||||||
config.Logger.Errorf(config.Context, "Server %s-%s register error: %s", config.Name, config.Id, err)
|
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register error: %s", config.Name, config.ID, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.Register(); err != nil {
|
if err := h.Register(); err != nil {
|
||||||
config.Logger.Errorf(config.Context, "Server register error: %s", err)
|
config.Logger.Error(config.Context, fmt.Sprintf("Server register error: %s", err))
|
||||||
}
|
}
|
||||||
// wait for exit
|
// wait for exit
|
||||||
case ch = <-h.exit:
|
case ch = <-h.exit:
|
||||||
@ -429,40 +525,49 @@ func (h *httpServer) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ch <- ts.Close()
|
|
||||||
|
|
||||||
// deregister
|
// deregister
|
||||||
if err := h.Deregister(); err != nil {
|
if err := h.Deregister(); err != nil {
|
||||||
config.Logger.Errorf(config.Context, "Server deregister error: %s", err)
|
config.Logger.Error(config.Context, fmt.Sprintf("Server deregister error: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.Broker.Disconnect(config.Context); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), h.opts.GracefulTimeout)
|
||||||
config.Logger.Errorf(config.Context, "Broker disconnect error: %s", err)
|
defer cancel()
|
||||||
|
|
||||||
|
err := hs.Shutdown(ctx)
|
||||||
|
if err != nil {
|
||||||
|
err = hs.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ch <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) Stop() error {
|
func (h *Server) Stop() error {
|
||||||
ch := make(chan error)
|
ch := make(chan error)
|
||||||
h.exit <- ch
|
h.exit <- ch
|
||||||
return <-ch
|
return <-ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) String() string {
|
func (h *Server) String() string {
|
||||||
return "http"
|
return "http"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpServer) Name() string {
|
func (h *Server) Name() string {
|
||||||
return h.opts.Name
|
return h.opts.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(opts ...server.Option) server.Server {
|
func NewServer(opts ...options.Option) *Server {
|
||||||
options := server.NewOptions(opts...)
|
options := server.NewOptions(opts...)
|
||||||
return &httpServer{
|
eh := DefaultErrorHandler
|
||||||
|
if v, ok := options.Context.Value(errorHandlerKey{}).(errorHandler); ok && v != nil {
|
||||||
|
eh = v
|
||||||
|
}
|
||||||
|
return &Server{
|
||||||
opts: options,
|
opts: options,
|
||||||
exit: make(chan chan error),
|
exit: make(chan chan error),
|
||||||
subscribers: make(map[*httpSubscriber][]broker.Subscriber),
|
errorHandler: eh,
|
||||||
|
pathHandlers: rhttp.NewTrie(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
message.go
20
message.go
@ -1,27 +1,23 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"go.unistack.org/micro/v4/codec"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"go.unistack.org/micro/v4/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpMessage struct {
|
type httpMessage struct {
|
||||||
topic string
|
|
||||||
payload interface{}
|
payload interface{}
|
||||||
contentType string
|
|
||||||
header metadata.Metadata
|
|
||||||
body []byte
|
|
||||||
codec codec.Codec
|
codec codec.Codec
|
||||||
|
header metadata.Metadata
|
||||||
|
topic string
|
||||||
|
contentType string
|
||||||
|
body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *httpMessage) Topic() string {
|
func (r *httpMessage) Topic() string {
|
||||||
return r.topic
|
return r.topic
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *httpMessage) Payload() interface{} {
|
|
||||||
return r.payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpMessage) ContentType() string {
|
func (r *httpMessage) ContentType() string {
|
||||||
return r.contentType
|
return r.contentType
|
||||||
}
|
}
|
||||||
@ -30,8 +26,8 @@ func (r *httpMessage) Header() metadata.Metadata {
|
|||||||
return r.header
|
return r.header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *httpMessage) Body() []byte {
|
func (r *httpMessage) Body() interface{} {
|
||||||
return r.body
|
return r.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *httpMessage) Codec() codec.Codec {
|
func (r *httpMessage) Codec() codec.Codec {
|
||||||
|
156
options.go
156
options.go
@ -1,11 +1,56 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
type rspCodeKey struct{}
|
"go.unistack.org/micro/v4/options"
|
||||||
type rspCodeVal struct {
|
)
|
||||||
|
|
||||||
|
// SetError pass error to caller
|
||||||
|
func SetError(err interface{}) error {
|
||||||
|
return &Error{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetError return underline error
|
||||||
|
func GetError(err interface{}) interface{} {
|
||||||
|
if verr, ok := err.(*Error); ok {
|
||||||
|
return verr.err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error struct holds error
|
||||||
|
type Error struct {
|
||||||
|
err interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error func for error interface
|
||||||
|
func (err *Error) Error() string {
|
||||||
|
return fmt.Sprintf("%v", err.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
rspCodeKey struct{}
|
||||||
|
rspCodeVal struct {
|
||||||
code int
|
code int
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
rspHeaderKey struct{}
|
||||||
|
rspHeaderVal struct {
|
||||||
|
h http.Header
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetRspHeader add response headers
|
||||||
|
func SetRspHeader(ctx context.Context, h http.Header) {
|
||||||
|
if rsp, ok := ctx.Value(rspHeaderKey{}).(*rspHeaderVal); ok {
|
||||||
|
rsp.h = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetRspCode saves response code in context, must be used by handler to specify http code
|
// SetRspCode saves response code in context, must be used by handler to specify http code
|
||||||
func SetRspCode(ctx context.Context, code int) {
|
func SetRspCode(ctx context.Context, code int) {
|
||||||
@ -14,6 +59,14 @@ func SetRspCode(ctx context.Context, code int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRspHeader get http.Header from context
|
||||||
|
func getRspHeader(ctx context.Context) http.Header {
|
||||||
|
if rsp, ok := ctx.Value(rspHeaderKey{}).(*rspHeaderVal); ok {
|
||||||
|
return rsp.h
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetRspCode used internally by generated http server handler
|
// GetRspCode used internally by generated http server handler
|
||||||
func GetRspCode(ctx context.Context) int {
|
func GetRspCode(ctx context.Context) int {
|
||||||
var code int
|
var code int
|
||||||
@ -22,3 +75,100 @@ func GetRspCode(ctx context.Context) int {
|
|||||||
}
|
}
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type middlewareKey struct{}
|
||||||
|
|
||||||
|
// Middleware passes http middlewares
|
||||||
|
func Middleware(mw ...func(http.Handler) http.Handler) options.Option {
|
||||||
|
return options.ContextOption(middlewareKey{}, mw)
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverKey struct{}
|
||||||
|
|
||||||
|
// HTTPServer provide ability to pass *http.Server
|
||||||
|
func HTTPServer(hs *http.Server) options.Option {
|
||||||
|
return options.ContextOption(serverKey{}, hs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorHandler func(ctx context.Context, s interface{}, w http.ResponseWriter, r *http.Request, err error, status int)
|
||||||
|
|
||||||
|
type errorHandlerKey struct{}
|
||||||
|
|
||||||
|
// ErrorHandler specifies handler for errors
|
||||||
|
func ErrorHandler(fn errorHandler) options.Option {
|
||||||
|
return options.ContextOption(errorHandlerKey{}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
pathHandlerKey struct{}
|
||||||
|
pathHandlerVal struct {
|
||||||
|
h map[string]map[string]http.HandlerFunc
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// PathHandler specifies http handler for path regexp
|
||||||
|
func PathHandler(method, path string, handler http.HandlerFunc) options.Option {
|
||||||
|
return func(src interface{}) error {
|
||||||
|
vctx, err := options.Get(src, ".Context")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx, ok := vctx.(context.Context)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid option")
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := ctx.Value(pathHandlerKey{}).(*pathHandlerVal)
|
||||||
|
if !ok {
|
||||||
|
v = &pathHandlerVal{h: make(map[string]map[string]http.HandlerFunc)}
|
||||||
|
}
|
||||||
|
m, ok := v.h[method]
|
||||||
|
if !ok {
|
||||||
|
m = make(map[string]http.HandlerFunc)
|
||||||
|
v.h[method] = m
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, pathHandlerKey{}, v)
|
||||||
|
m[path] = handler
|
||||||
|
return options.Set(src, ctx, ".Context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type registerRPCHandlerKey struct{}
|
||||||
|
|
||||||
|
// RegisterRPCHandler registers compatibility endpoints with /ServiceName.ServiceEndpoint method POST
|
||||||
|
func RegisterRPCHandler(b bool) options.Option {
|
||||||
|
return options.ContextOption(registerRPCHandlerKey{}, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type handlerEndpointsKey struct{}
|
||||||
|
|
||||||
|
type EndpointMetadata struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Method string
|
||||||
|
Body string
|
||||||
|
Stream bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlerEndpoints(md []EndpointMetadata) options.Option {
|
||||||
|
return options.ContextOption(handlerEndpointsKey{}, md)
|
||||||
|
}
|
||||||
|
|
||||||
|
type handlerOptions struct {
|
||||||
|
headers []string
|
||||||
|
cookies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FillRequestOption func(*handlerOptions)
|
||||||
|
|
||||||
|
func Header(headers ...string) FillRequestOption {
|
||||||
|
return func(o *handlerOptions) {
|
||||||
|
o.headers = append(o.headers, headers...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cookie(cookies ...string) FillRequestOption {
|
||||||
|
return func(o *handlerOptions) {
|
||||||
|
o.cookies = append(o.cookies, cookies...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
52
request.go
52
request.go
@ -3,31 +3,23 @@ package http
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"go.unistack.org/micro/v4/codec"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"go.unistack.org/micro/v4/metadata"
|
||||||
|
"go.unistack.org/micro/v4/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ server.Request = &rpcRequest{}
|
||||||
|
|
||||||
type rpcRequest struct {
|
type rpcRequest struct {
|
||||||
rw io.ReadWriter
|
rw io.ReadWriter
|
||||||
service string
|
payload interface{}
|
||||||
|
codec codec.Codec
|
||||||
|
header metadata.Metadata
|
||||||
method string
|
method string
|
||||||
endpoint string
|
endpoint string
|
||||||
target string
|
|
||||||
contentType string
|
contentType string
|
||||||
codec codec.Codec
|
service string
|
||||||
header metadata.Metadata
|
|
||||||
body []byte
|
|
||||||
stream bool
|
stream bool
|
||||||
payload interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type rpcMessage struct {
|
|
||||||
topic string
|
|
||||||
contentType string
|
|
||||||
payload interface{}
|
|
||||||
header metadata.Metadata
|
|
||||||
body []byte
|
|
||||||
codec codec.Codec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcRequest) ContentType() string {
|
func (r *rpcRequest) ContentType() string {
|
||||||
@ -43,7 +35,7 @@ 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.Codec {
|
func (r *rpcRequest) Codec() codec.Codec {
|
||||||
@ -69,27 +61,3 @@ func (r *rpcRequest) Stream() bool {
|
|||||||
func (r *rpcRequest) Body() interface{} {
|
func (r *rpcRequest) Body() interface{} {
|
||||||
return r.payload
|
return r.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcMessage) ContentType() string {
|
|
||||||
return r.contentType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rpcMessage) Topic() string {
|
|
||||||
return r.topic
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rpcMessage) Payload() interface{} {
|
|
||||||
return r.payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rpcMessage) Header() metadata.Metadata {
|
|
||||||
return r.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rpcMessage) Body() []byte {
|
|
||||||
return r.body
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rpcMessage) Codec() codec.Codec {
|
|
||||||
return r.codec
|
|
||||||
}
|
|
||||||
|
13
server.go
13
server.go
@ -7,14 +7,16 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/server"
|
"go.unistack.org/micro/v4/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,15 +64,14 @@ func prepareEndpoint(method reflect.Method) (*methodType, error) {
|
|||||||
return nil, fmt.Errorf("method %v of %v has wrong number of ins: %v", mname, mtype, mtype.NumIn())
|
return nil, fmt.Errorf("method %v of %v has wrong number of ins: %v", mname, mtype, mtype.NumIn())
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
return nil, fmt.Errorf("%v argument does not implement Streamer interface: %v", mname, argType)
|
return nil, fmt.Errorf("%v argument does not implement Streamer interface: %v", mname, argType)
|
||||||
}
|
}
|
||||||
} 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) {
|
||||||
return nil, fmt.Errorf("%v argument type not exported: %v", mname, argType)
|
return nil, fmt.Errorf("%v argument type not exported: %v", mname, argType)
|
||||||
|
213
subscriber.go
213
subscriber.go
@ -1,213 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/broker"
|
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
|
||||||
"github.com/unistack-org/micro/v3/register"
|
|
||||||
"github.com/unistack-org/micro/v3/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
subSig = "func(context.Context, interface{}) error"
|
|
||||||
)
|
|
||||||
|
|
||||||
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
|
||||||
|
|
||||||
type handler struct {
|
|
||||||
method reflect.Value
|
|
||||||
reqType reflect.Type
|
|
||||||
ctxType reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpSubscriber struct {
|
|
||||||
topic string
|
|
||||||
rcvr reflect.Value
|
|
||||||
typ reflect.Type
|
|
||||||
subscriber interface{}
|
|
||||||
handlers []*handler
|
|
||||||
endpoints []*register.Endpoint
|
|
||||||
opts server.SubscriberOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOption) server.Subscriber {
|
|
||||||
options := server.NewSubscriberOptions(opts...)
|
|
||||||
|
|
||||||
var endpoints []*register.Endpoint
|
|
||||||
var handlers []*handler
|
|
||||||
|
|
||||||
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
|
|
||||||
h := &handler{
|
|
||||||
method: reflect.ValueOf(sub),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch typ.NumIn() {
|
|
||||||
case 1:
|
|
||||||
h.reqType = typ.In(0)
|
|
||||||
case 2:
|
|
||||||
h.ctxType = typ.In(0)
|
|
||||||
h.reqType = typ.In(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
handlers = append(handlers, h)
|
|
||||||
ep := ®ister.Endpoint{
|
|
||||||
Name: "Func",
|
|
||||||
Request: register.ExtractSubValue(typ),
|
|
||||||
Metadata: metadata.New(2),
|
|
||||||
}
|
|
||||||
ep.Metadata.Set("topic", topic)
|
|
||||||
ep.Metadata.Set("subscriber", "true")
|
|
||||||
endpoints = append(endpoints, ep)
|
|
||||||
} else {
|
|
||||||
hdlr := reflect.ValueOf(sub)
|
|
||||||
name := reflect.Indirect(hdlr).Type().Name()
|
|
||||||
|
|
||||||
for m := 0; m < typ.NumMethod(); m++ {
|
|
||||||
method := typ.Method(m)
|
|
||||||
h := &handler{
|
|
||||||
method: method.Func,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch method.Type.NumIn() {
|
|
||||||
case 2:
|
|
||||||
h.reqType = method.Type.In(1)
|
|
||||||
case 3:
|
|
||||||
h.ctxType = method.Type.In(1)
|
|
||||||
h.reqType = method.Type.In(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
handlers = append(handlers, h)
|
|
||||||
ep := ®ister.Endpoint{
|
|
||||||
Name: name + "." + method.Name,
|
|
||||||
Request: register.ExtractSubValue(method.Type),
|
|
||||||
Metadata: metadata.New(2),
|
|
||||||
}
|
|
||||||
ep.Metadata.Set("topic", topic)
|
|
||||||
ep.Metadata.Set("subscriber", "true")
|
|
||||||
endpoints = append(endpoints, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &httpSubscriber{
|
|
||||||
rcvr: reflect.ValueOf(sub),
|
|
||||||
typ: reflect.TypeOf(sub),
|
|
||||||
topic: topic,
|
|
||||||
subscriber: sub,
|
|
||||||
handlers: handlers,
|
|
||||||
endpoints: endpoints,
|
|
||||||
opts: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpServer) createSubHandler(sb *httpSubscriber, opts server.Options) broker.Handler {
|
|
||||||
return func(p broker.Event) error {
|
|
||||||
msg := p.Message()
|
|
||||||
ct := msg.Header["Content-Type"]
|
|
||||||
cf, err := s.newCodec(ct)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr := metadata.Copy(msg.Header)
|
|
||||||
delete(hdr, "Content-Type")
|
|
||||||
ctx := metadata.NewContext(context.Background(), hdr)
|
|
||||||
|
|
||||||
results := make(chan error, len(sb.handlers))
|
|
||||||
|
|
||||||
for i := 0; i < len(sb.handlers); i++ {
|
|
||||||
handler := sb.handlers[i]
|
|
||||||
|
|
||||||
var isVal bool
|
|
||||||
var req reflect.Value
|
|
||||||
|
|
||||||
if handler.reqType.Kind() == reflect.Ptr {
|
|
||||||
req = reflect.New(handler.reqType.Elem())
|
|
||||||
} else {
|
|
||||||
req = reflect.New(handler.reqType)
|
|
||||||
isVal = true
|
|
||||||
}
|
|
||||||
if isVal {
|
|
||||||
req = req.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(msg.Body)
|
|
||||||
|
|
||||||
if err := cf.ReadHeader(buf, &codec.Message{}, codec.Event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cf.ReadBody(buf, req.Interface()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(ctx context.Context, msg server.Message) error {
|
|
||||||
var vals []reflect.Value
|
|
||||||
if sb.typ.Kind() != reflect.Func {
|
|
||||||
vals = append(vals, sb.rcvr)
|
|
||||||
}
|
|
||||||
if handler.ctxType != nil {
|
|
||||||
vals = append(vals, reflect.ValueOf(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
vals = append(vals, reflect.ValueOf(msg.Payload()))
|
|
||||||
|
|
||||||
returnValues := handler.method.Call(vals)
|
|
||||||
if err := returnValues[0].Interface(); err != nil {
|
|
||||||
return err.(error)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(opts.SubWrappers); i > 0; i-- {
|
|
||||||
fn = opts.SubWrappers[i-1](fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
results <- fn(ctx, &httpMessage{
|
|
||||||
topic: sb.topic,
|
|
||||||
contentType: ct,
|
|
||||||
payload: req.Interface(),
|
|
||||||
header: msg.Header,
|
|
||||||
body: msg.Body,
|
|
||||||
codec: cf,
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors []string
|
|
||||||
|
|
||||||
for i := 0; i < len(sb.handlers); i++ {
|
|
||||||
if err := <-results; err != nil {
|
|
||||||
errors = append(errors, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
return fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpSubscriber) Topic() string {
|
|
||||||
return s.topic
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpSubscriber) Subscriber() interface{} {
|
|
||||||
return s.subscriber
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpSubscriber) Endpoints() []*register.Endpoint {
|
|
||||||
return s.endpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpSubscriber) Options() server.SubscriberOptions {
|
|
||||||
return s.opts
|
|
||||||
}
|
|
8
tools.go
Normal file
8
tools.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "go.unistack.org/micro-proto/v4"
|
||||||
|
_ "go.unistack.org/protoc-gen-go-micro/v4"
|
||||||
|
)
|
53
util.go
Normal file
53
util.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v4/metadata"
|
||||||
|
rutil "go.unistack.org/micro/v4/util/reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FillRequest(ctx context.Context, req interface{}, opts ...FillRequestOption) error {
|
||||||
|
var err error
|
||||||
|
options := handlerOptions{}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 0; idx < len(options.headers)/2; idx += 2 {
|
||||||
|
k := http.CanonicalHeaderKey(options.headers[idx])
|
||||||
|
v, ok := md[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = rutil.SetFieldByPath(req, v, k); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cookies := md["Cookie"]
|
||||||
|
cmd := make(map[string]string, len(cookies))
|
||||||
|
kv := strings.Split(cookies, "=")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cmd[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
|
||||||
|
for idx := 0; idx < len(options.cookies)/2; idx += 2 {
|
||||||
|
k := http.CanonicalHeaderKey(options.cookies[idx])
|
||||||
|
v, ok := cmd[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = rutil.SetFieldByPath(req, v, k); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
56
util_test.go
Normal file
56
util_test.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v4/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFillrequest(t *testing.T) {
|
||||||
|
md := metadata.New(1)
|
||||||
|
md.Set("ClientID", "xxx")
|
||||||
|
type request struct {
|
||||||
|
Token string
|
||||||
|
ClientID string
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
hreq, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/v1", nil)
|
||||||
|
cookie1 := &http.Cookie{Name: "Token", Value: "zzz"}
|
||||||
|
cookie2 := &http.Cookie{Name: "Token", Value: "zzz"}
|
||||||
|
hreq.AddCookie(cookie1)
|
||||||
|
hreq.AddCookie(cookie2)
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
_ = hreq.Write(buf)
|
||||||
|
var cookie string
|
||||||
|
var line string
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
line, err = buf.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "Cookie") {
|
||||||
|
cookie = strings.TrimSpace(strings.Split(line, ":")[1])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
md.Set("Cookie", cookie)
|
||||||
|
ctx = metadata.NewIncomingContext(ctx, md)
|
||||||
|
req := &request{}
|
||||||
|
|
||||||
|
if err := FillRequest(ctx, req, Cookie("Token", "true"), Header("ClientID", "true")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if req.ClientID != "xxx" {
|
||||||
|
t.Fatalf("FillRequest error: %#+v", req)
|
||||||
|
}
|
||||||
|
if req.Token != "zzz" {
|
||||||
|
t.Fatalf("FillRequest error: %#+v", req)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user