Compare commits
No commits in common. "master" and "v3.2.8" have entirely different histories.
19
.github/dependabot.yml
vendored
19
.github/dependabot.yml
vendored
@ -1,19 +0,0 @@
|
||||
# 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"
|
19
.github/renovate.json
vendored
Normal file
19
.github/renovate.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"groupName": "all deps",
|
||||
"separateMajorMinor": true,
|
||||
"groupSlug": "all",
|
||||
"packagePatterns": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
13
.github/stale.sh
vendored
Executable file
13
.github/stale.sh
vendored
Executable file
@ -0,0 +1,13 @@
|
||||
#!/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
20
.github/workflows/autoapprove.yml
vendored
@ -1,20 +0,0 @@
|
||||
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
21
.github/workflows/automerge.yml
vendored
@ -1,21 +0,0 @@
|
||||
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,20 +3,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.16
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
@ -32,9 +31,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: lint
|
||||
uses: golangci/golangci-lint-action@v3.4.0
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
# 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
78
.github/workflows/codeql-analysis.yml
vendored
@ -1,78 +0,0 @@
|
||||
# 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
27
.github/workflows/dependabot-automerge.yml
vendored
@ -1,27 +0,0 @@
|
||||
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,20 +3,19 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.16
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
@ -32,9 +31,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: lint
|
||||
uses: golangci/golangci-lint-action@v3.4.0
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
# 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
24
.gitignore
vendored
@ -1,24 +0,0 @@
|
||||
# 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
|
@ -1,44 +0,0 @@
|
||||
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 (
|
||||
"net/http"
|
||||
|
||||
"go.unistack.org/micro/v4/server"
|
||||
httpServer "go.unistack.org/micro-server-http/v4"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
httpServer "github.com/unistack-org/micro-server-http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -37,9 +37,9 @@ Or as part of a service
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.unistack.org/micro/v4"
|
||||
"go.unistack.org/micro/v4/server"
|
||||
httpServer "go.unistack.org/micro-server-http/v4"
|
||||
"github.com/unistack-org/micro/v3"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
httpServer "github.com/unistack-org/micro-server-http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
25
go.mod
25
go.mod
@ -1,25 +1,10 @@
|
||||
module go.unistack.org/micro-server-http/v4
|
||||
module github.com/unistack-org/micro-server-http/v3
|
||||
|
||||
go 1.19
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
go.unistack.org/micro-codec-yaml/v4 v4.0.0
|
||||
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
|
||||
github.com/unistack-org/micro/v3 v3.2.14
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
//replace github.com/unistack-org/micro/v3 => ../../micro
|
||||
|
576
handler.go
576
handler.go
@ -7,41 +7,47 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.unistack.org/micro/v4/errors"
|
||||
"go.unistack.org/micro/v4/logger"
|
||||
"go.unistack.org/micro/v4/metadata"
|
||||
"go.unistack.org/micro/v4/register"
|
||||
"go.unistack.org/micro/v4/server"
|
||||
rhttp "go.unistack.org/micro/v4/util/http"
|
||||
rflutil "go.unistack.org/micro/v4/util/reflect"
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/errors"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
rflutil "github.com/unistack-org/micro/v3/util/reflect"
|
||||
rutil "github.com/unistack-org/micro/v3/util/router"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultErrorHandler = func(ctx context.Context, s interface{}, w http.ResponseWriter, r *http.Request, err error, status int) {
|
||||
DefaultErrorHandler = func(ctx context.Context, s server.Handler, w http.ResponseWriter, r *http.Request, err error, status int) {
|
||||
w.WriteHeader(status)
|
||||
if _, cerr := w.Write([]byte(err.Error())); cerr != nil {
|
||||
logger.DefaultLogger.Error(ctx, fmt.Sprintf("write failed: %v", cerr))
|
||||
}
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
DefaultContentType = "application/json"
|
||||
)
|
||||
|
||||
type patHandler struct {
|
||||
pat rutil.Pattern
|
||||
mtype *methodType
|
||||
rcvr reflect.Value
|
||||
name string
|
||||
rcvr reflect.Value
|
||||
}
|
||||
|
||||
type httpHandler struct {
|
||||
opts server.HandleOptions
|
||||
hd interface{}
|
||||
handlers *rhttp.Trie
|
||||
name string
|
||||
eps []*register.Endpoint
|
||||
sopts server.Options
|
||||
sync.RWMutex
|
||||
name string
|
||||
opts server.HandlerOptions
|
||||
sopts server.Options
|
||||
eps []*register.Endpoint
|
||||
hd interface{}
|
||||
handlers map[string][]patHandler
|
||||
errorHandler func(context.Context, server.Handler, http.ResponseWriter, *http.Request, error, int)
|
||||
}
|
||||
|
||||
func (h *httpHandler) newCodec(ct string) (codec.Codec, error) {
|
||||
if cf, ok := h.sopts.Codecs[ct]; ok {
|
||||
return cf, nil
|
||||
}
|
||||
return nil, codec.ErrUnknownContentType
|
||||
}
|
||||
|
||||
func (h *httpHandler) Name() string {
|
||||
@ -56,405 +62,82 @@ func (h *httpHandler) Endpoints() []*register.Endpoint {
|
||||
return h.eps
|
||||
}
|
||||
|
||||
func (h *httpHandler) Options() server.HandleOptions {
|
||||
func (h *httpHandler) Options() server.HandlerOptions {
|
||||
return h.opts
|
||||
}
|
||||
|
||||
func (h *Server) HTTPHandlerFunc(handler interface{}) (http.HandlerFunc, error) {
|
||||
if handler == nil {
|
||||
return nil, fmt.Errorf("invalid handler specified: %v", handler)
|
||||
}
|
||||
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := metadata.NewContext(r.Context(), nil)
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
ctx = metadata.NewIncomingContext(ctx, md)
|
||||
|
||||
path := r.URL.Path
|
||||
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
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 {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
_, _ = w.Write([]byte("not matching route found"))
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get fields from url values
|
||||
if len(r.URL.RawQuery) > 0 {
|
||||
umd, cerr := rflutil.URLMap(r.URL.RawQuery)
|
||||
if cerr != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(cerr.Error()))
|
||||
return
|
||||
}
|
||||
for k, v := range umd {
|
||||
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
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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.Hooks); i > 0; i-- {
|
||||
// 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
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}, 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 {
|
||||
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)
|
||||
defer r.Body.Close()
|
||||
|
||||
path := r.URL.Path
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
h.errorHandler(ctx, nil, w, r, fmt.Errorf("path must starts with /"), http.StatusBadRequest)
|
||||
h.errorHandler(ctx, h, w, r, fmt.Errorf("path must contains /"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
ct := DefaultContentType
|
||||
if htype := r.Header.Get("Content-Type"); htype != "" {
|
||||
ct = htype
|
||||
}
|
||||
|
||||
cf, err := h.newCodec(ct)
|
||||
if err != nil {
|
||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
components := strings.Split(path[1:], "/")
|
||||
l := len(components)
|
||||
var verb string
|
||||
idx := strings.LastIndex(components[l-1], ":")
|
||||
if idx == 0 {
|
||||
h.errorHandler(ctx, h, w, r, fmt.Errorf("not found"), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if idx > 0 {
|
||||
c := components[l-1]
|
||||
components[l-1], verb = c[:idx], c[idx+1:]
|
||||
}
|
||||
|
||||
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)
|
||||
var hldr patHandler
|
||||
for _, hldr = range h.handlers[r.Method] {
|
||||
mp, err := hldr.pat.Match(components, verb)
|
||||
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)
|
||||
if !match {
|
||||
h.errorHandler(ctx, h, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(0)
|
||||
}
|
||||
|
||||
for k, v := range r.Header {
|
||||
md.Set(k, strings.Join(v, ", "))
|
||||
}
|
||||
|
||||
// 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
|
||||
umd, err := rflutil.URLMap(r.URL.RawQuery)
|
||||
if err != nil {
|
||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
||||
}
|
||||
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.
|
||||
@ -474,125 +157,88 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
replyv = reflect.New(hldr.mtype.ReplyType.Elem())
|
||||
|
||||
function := hldr.mtype.method.Func
|
||||
//function := hldr.rcvr
|
||||
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 err = cf.ReadBody(r.Body, argv.Interface()); err != nil && err != io.EOF {
|
||||
h.errorHandler(ctx, h, w, r, err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
matches = rflutil.FlattenMap(matches)
|
||||
if err = rflutil.MergeMap(argv.Interface(), matches); err != nil {
|
||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := cf.Marshal(argv.Interface())
|
||||
if err != nil {
|
||||
h.errorHandler(ctx, h, w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
hr := &rpcRequest{
|
||||
codec: cf,
|
||||
service: handler.sopts.Name,
|
||||
service: h.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),
|
||||
body: b,
|
||||
payload: argv.Interface(),
|
||||
header: md,
|
||||
}
|
||||
|
||||
var scode int
|
||||
// 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)})
|
||||
fctx = context.WithValue(fctx, rspCodeKey{}, &rspCodeVal{})
|
||||
fctx = metadata.NewIncomingContext(fctx, md)
|
||||
returnValues = function.Call([]reflect.Value{hldr.rcvr, hldr.mtype.prepareContext(fctx), reflect.ValueOf(argv.Interface()), reflect.ValueOf(rsp)})
|
||||
|
||||
scode = GetRspCode(fctx)
|
||||
// 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
|
||||
}
|
||||
if md, ok := metadata.FromOutgoingContext(fctx); ok {
|
||||
metadata.SetOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
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
|
||||
for i := len(h.sopts.HdlrWrappers); i > 0; i-- {
|
||||
fn = h.sopts.HdlrWrappers[i-1](fn)
|
||||
}
|
||||
|
||||
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 {
|
||||
if appErr := fn(ctx, hr, replyv.Interface()); appErr != nil {
|
||||
switch verr := appErr.(type) {
|
||||
case *errors.Error:
|
||||
scode = int(verr.Code)
|
||||
buf, err = cf.Marshal(verr)
|
||||
b, err = cf.Marshal(verr)
|
||||
case *Error:
|
||||
buf, err = cf.Marshal(verr.err)
|
||||
b, err = cf.Marshal(verr.err)
|
||||
default:
|
||||
buf, err = cf.Marshal(appErr)
|
||||
b, err = cf.Marshal(appErr)
|
||||
}
|
||||
} else {
|
||||
buf, err = cf.Marshal(replyv.Interface())
|
||||
b, 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))
|
||||
if err != nil && h.sopts.Logger.V(logger.ErrorLevel) {
|
||||
h.sopts.Logger.Errorf(h.sopts.Context, "handler err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if nscode := GetRspCode(ctx); nscode != 0 {
|
||||
scode = nscode
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
for k, v := range md {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(scode)
|
||||
|
||||
if _, cerr := w.Write(buf); cerr != nil {
|
||||
handler.sopts.Logger.Error(ctx, fmt.Sprintf("write failed: %v", cerr))
|
||||
w.Header().Set("content-Type", ct)
|
||||
if scode != 0 {
|
||||
w.WriteHeader(scode)
|
||||
} else {
|
||||
h.sopts.Logger.Warn(h.sopts.Context, "response code not set in handler via SetRspCode(ctx, http.StatusXXX)")
|
||||
}
|
||||
w.Write(b)
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
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"
|
@ -1,82 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
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"; };
|
||||
};
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
// 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...)...)
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
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"; };
|
||||
};
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// 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...)...)
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 665 B |
Binary file not shown.
Before Width: | Height: | Size: 628 B |
@ -1,16 +0,0 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<!-- 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>
|
@ -1,79 +0,0 @@
|
||||
<!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>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,142 +0,0 @@
|
||||
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",
|
||||
}
|
||||
)
|
@ -1,15 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
469
http.go
469
http.go
@ -1,191 +1,121 @@
|
||||
// Package http implements a go-micro.Server
|
||||
package http // import "go.unistack.org/micro-server-http/v4"
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v4/codec"
|
||||
"go.unistack.org/micro/v4/logger"
|
||||
"go.unistack.org/micro/v4/options"
|
||||
"go.unistack.org/micro/v4/register"
|
||||
"go.unistack.org/micro/v4/server"
|
||||
rhttp "go.unistack.org/micro/v4/util/http"
|
||||
"github.com/unistack-org/micro/v3/broker"
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
rutil "github.com/unistack-org/micro/v3/util/router"
|
||||
"golang.org/x/net/netutil"
|
||||
)
|
||||
|
||||
var _ server.Server = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
hd interface{}
|
||||
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
|
||||
type httpServer struct {
|
||||
sync.RWMutex
|
||||
opts server.Options
|
||||
hd server.Handler
|
||||
exit chan chan error
|
||||
subscribers map[*httpSubscriber][]broker.Subscriber
|
||||
// used for first registration
|
||||
registered bool
|
||||
init bool
|
||||
// register service instance
|
||||
rsvc *register.Service
|
||||
}
|
||||
|
||||
func (h *Server) newCodec(ct string) (codec.Codec, error) {
|
||||
if idx := strings.IndexRune(ct, ';'); idx >= 0 {
|
||||
ct = ct[:idx]
|
||||
}
|
||||
h.RLock()
|
||||
cf, ok := h.opts.Codecs[ct]
|
||||
h.RUnlock()
|
||||
if ok {
|
||||
func (h *httpServer) newCodec(ct string) (codec.Codec, error) {
|
||||
if cf, ok := h.opts.Codecs[ct]; ok {
|
||||
return cf, nil
|
||||
}
|
||||
return nil, codec.ErrUnknownContentType
|
||||
}
|
||||
|
||||
func (h *Server) Options() server.Options {
|
||||
func (h *httpServer) Options() server.Options {
|
||||
h.Lock()
|
||||
opts := h.opts
|
||||
h.Unlock()
|
||||
return opts
|
||||
}
|
||||
|
||||
func (h *Server) Init(opts ...options.Option) error {
|
||||
if len(opts) == 0 && h.init {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpServer) Init(opts ...server.Option) error {
|
||||
h.Lock()
|
||||
|
||||
for _, o := range 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()
|
||||
return nil
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
func (h *httpServer) Handle(handler server.Handler) error {
|
||||
h.Lock()
|
||||
h.init = true
|
||||
h.hd = handler
|
||||
h.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Server) Handle(handler interface{}, opts ...options.Option) error {
|
||||
options := server.NewHandleOptions(opts...)
|
||||
var endpointMetadata []EndpointMetadata
|
||||
func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
|
||||
options := server.NewHandlerOptions(opts...)
|
||||
|
||||
if v, ok := options.Context.Value(handlerEndpointsKey{}).([]EndpointMetadata); ok {
|
||||
endpointMetadata = v
|
||||
}
|
||||
var eps []*register.Endpoint
|
||||
|
||||
// passed unknown handler
|
||||
hdlr, ok := handler.(*httpHandler)
|
||||
if !ok {
|
||||
h.Lock()
|
||||
if h.handlers == nil {
|
||||
h.handlers = make(map[string]interface{})
|
||||
if !options.Internal {
|
||||
for name, metadata := range options.Metadata {
|
||||
eps = append(eps, ®ister.Endpoint{
|
||||
Name: name,
|
||||
Metadata: metadata,
|
||||
})
|
||||
}
|
||||
for _, v := range endpointMetadata {
|
||||
h.handlers[v.Name] = h.newHTTPHandler(handler, opts...)
|
||||
}
|
||||
h.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// passed http.Handler like some muxer
|
||||
if _, ok := hdlr.hd.(http.Handler); ok {
|
||||
h.Lock()
|
||||
h.hd = handler
|
||||
h.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Server) newHTTPHandler(handler interface{}, opts ...options.Option) *httpHandler {
|
||||
options := server.NewHandleOptions(opts...)
|
||||
|
||||
eps := make([]*register.Endpoint, 0, len(options.Metadata))
|
||||
for name, metadata := range options.Metadata {
|
||||
eps = append(eps, ®ister.Endpoint{
|
||||
Name: name,
|
||||
Metadata: metadata,
|
||||
})
|
||||
}
|
||||
|
||||
hdlr := &httpHandler{
|
||||
eps: eps,
|
||||
hd: handler,
|
||||
opts: options,
|
||||
sopts: h.opts,
|
||||
handlers: rhttp.NewTrie(),
|
||||
eps: eps,
|
||||
hd: handler,
|
||||
opts: options,
|
||||
sopts: h.opts,
|
||||
}
|
||||
|
||||
tp := reflect.TypeOf(handler)
|
||||
|
||||
/*
|
||||
if len(options.Metadata) == 0 {
|
||||
if h.registerRPC {
|
||||
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 {
|
||||
h.opts.Logger.Errorf(h.opts.Context, "cant add rpc handler for http.MethodPost %s /%s", hn, hn)
|
||||
}
|
||||
}
|
||||
}
|
||||
for m := 0; m < tp.NumMethod(); m++ {
|
||||
if e := register.ExtractEndpoint(tp.Method(m)); e != nil {
|
||||
e.Name = name + "." + e.Name
|
||||
|
||||
for k, v := range options.Metadata[e.Name] {
|
||||
e.Metadata[k] = v
|
||||
}
|
||||
|
||||
eps = append(eps, e)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
hdlr.handlers = make(map[string][]patHandler)
|
||||
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
|
||||
mname := hn[strings.Index(hn, ".")+1:]
|
||||
for m := 0; m < tp.NumMethod(); m++ {
|
||||
@ -198,100 +128,59 @@ func (h *Server) newHTTPHandler(handler interface{}, opts ...options.Option) *ht
|
||||
}
|
||||
|
||||
if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) {
|
||||
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil method for %s", mname))
|
||||
h.opts.Logger.Errorf(h.opts.Context, "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))
|
||||
h.opts.Logger.Errorf(h.opts.Context, "%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}
|
||||
pth := patHandler{pat: pat, 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"][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))
|
||||
}
|
||||
}
|
||||
hdlr.handlers[md["Method"]] = append(hdlr.handlers[md["Method"]], pth)
|
||||
}
|
||||
|
||||
return hdlr
|
||||
}
|
||||
|
||||
func (h *Server) Register() error {
|
||||
var eps []*register.Endpoint
|
||||
h.RLock()
|
||||
for _, hdlr := range h.handlers {
|
||||
hd, ok := hdlr.(*httpHandler)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
eps = append(eps, hd.Endpoints()...)
|
||||
func (h *httpServer) NewSubscriber(topic string, handler interface{}, opts ...server.SubscriberOption) server.Subscriber {
|
||||
return newSubscriber(topic, handler, opts...)
|
||||
}
|
||||
|
||||
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()
|
||||
eps := h.hd.Endpoints()
|
||||
rsvc := h.rsvc
|
||||
config := h.opts
|
||||
h.RUnlock()
|
||||
@ -308,16 +197,33 @@ func (h *Server) Register() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.Nodes[0].Metadata.Set("protocol", "http")
|
||||
service.Nodes[0].Metadata["protocol"] = "http"
|
||||
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()
|
||||
registered := h.registered
|
||||
h.RUnlock()
|
||||
|
||||
if !registered {
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Info(config.Context, fmt.Sprintf("Register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID))
|
||||
config.Logger.Infof(config.Context, "Register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].Id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,14 +238,36 @@ func (h *Server) Register() error {
|
||||
}
|
||||
|
||||
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.rsvc = service
|
||||
h.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Server) Deregister() error {
|
||||
func (h *httpServer) Deregister() error {
|
||||
h.RLock()
|
||||
config := h.opts
|
||||
h.RUnlock()
|
||||
@ -350,7 +278,7 @@ func (h *Server) Deregister() error {
|
||||
}
|
||||
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Info(config.Context, fmt.Sprintf("Deregistering node: %s", service.Nodes[0].ID))
|
||||
config.Logger.Infof(config.Context, "Deregistering node: %s", service.Nodes[0].Id)
|
||||
}
|
||||
|
||||
if err := server.DefaultDeregisterFunc(service, config); err != nil {
|
||||
@ -366,13 +294,30 @@ func (h *Server) Deregister() error {
|
||||
}
|
||||
|
||||
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()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Server) Start() error {
|
||||
func (h *httpServer) Start() error {
|
||||
h.RLock()
|
||||
config := h.opts
|
||||
hd := h.hd
|
||||
h.RUnlock()
|
||||
|
||||
// micro: config.Transport.Listen(config.Address)
|
||||
@ -400,47 +345,29 @@ func (h *Server) Start() error {
|
||||
}
|
||||
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Info(config.Context, fmt.Sprintf("Listening on %s", ts.Addr().String()))
|
||||
config.Logger.Infof(config.Context, "Listening on %s", ts.Addr().String())
|
||||
}
|
||||
|
||||
h.Lock()
|
||||
h.opts.Address = ts.Addr().String()
|
||||
h.Unlock()
|
||||
|
||||
var handler 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
|
||||
}
|
||||
}
|
||||
handler, ok := hd.Handler().(http.Handler)
|
||||
if !ok {
|
||||
handler, ok = hd.(http.Handler)
|
||||
}
|
||||
|
||||
switch {
|
||||
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 !ok {
|
||||
return errors.New("Server required http.Handler")
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
return fmt.Errorf("cant process with nil handler")
|
||||
if err := config.Broker.Connect(h.opts.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := config.RegisterCheck(h.opts.Context); err != nil {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error: %s", config.Name, config.ID, err))
|
||||
config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s", config.Name, config.Id, err)
|
||||
}
|
||||
} else {
|
||||
if err = h.Register(); err != nil {
|
||||
@ -449,8 +376,8 @@ func (h *Server) Start() error {
|
||||
}
|
||||
|
||||
fn := handler
|
||||
var srvFunc func(net.Listener) error
|
||||
|
||||
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
|
||||
@ -458,19 +385,17 @@ func (h *Server) Start() error {
|
||||
fn = mwf[i-1](fn)
|
||||
}
|
||||
}
|
||||
var ok bool
|
||||
if hs, ok = h.opts.Context.Value(serverKey{}).(*http.Server); ok && hs != nil {
|
||||
if hs, ok := h.opts.Context.Value(serverKey{}).(*http.Server); ok && hs != nil {
|
||||
hs.Handler = fn
|
||||
} else {
|
||||
hs = &http.Server{Handler: fn}
|
||||
srvFunc = hs.Serve
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
if cerr := hs.Serve(ts); cerr != nil && !errors.Is(cerr, net.ErrClosed) {
|
||||
h.opts.Logger.Error(h.opts.Context, cerr.Error())
|
||||
}
|
||||
}()
|
||||
if srvFunc != nil {
|
||||
go srvFunc(ts)
|
||||
} else {
|
||||
go http.Serve(ts, fn)
|
||||
}
|
||||
|
||||
go func() {
|
||||
t := new(time.Ticker)
|
||||
@ -493,31 +418,30 @@ func (h *Server) Start() error {
|
||||
registered := h.registered
|
||||
h.RUnlock()
|
||||
rerr := config.RegisterCheck(h.opts.Context)
|
||||
// nolint: nestif
|
||||
if rerr != nil && registered {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error: %s, deregister it", config.Name, config.ID, rerr))
|
||||
config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
|
||||
}
|
||||
// deregister self in case of error
|
||||
if err := h.Deregister(); err != nil {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s deregister error: %s", config.Name, config.ID, err))
|
||||
config.Logger.Errorf(config.Context, "Server %s-%s deregister error: %s", config.Name, config.Id, err)
|
||||
}
|
||||
}
|
||||
} else if rerr != nil && !registered {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error: %s", config.Name, config.ID, rerr))
|
||||
config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s", config.Name, config.Id, rerr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := h.Register(); err != nil {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register error: %s", config.Name, config.ID, err))
|
||||
config.Logger.Errorf(config.Context, "Server %s-%s register error: %s", config.Name, config.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.Register(); err != nil {
|
||||
config.Logger.Error(config.Context, fmt.Sprintf("Server register error: %s", err))
|
||||
config.Logger.Errorf(config.Context, "Server register error: %s", err)
|
||||
}
|
||||
// wait for exit
|
||||
case ch = <-h.exit:
|
||||
@ -525,49 +449,40 @@ func (h *Server) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
ch <- ts.Close()
|
||||
|
||||
// deregister
|
||||
if err := h.Deregister(); err != nil {
|
||||
config.Logger.Error(config.Context, fmt.Sprintf("Server deregister error: %s", err))
|
||||
config.Logger.Errorf(config.Context, "Server deregister error: %s", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), h.opts.GracefulTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := hs.Shutdown(ctx)
|
||||
if err != nil {
|
||||
err = hs.Close()
|
||||
if err := config.Broker.Disconnect(config.Context); err != nil {
|
||||
config.Logger.Errorf(config.Context, "Broker disconnect error: %s", err)
|
||||
}
|
||||
|
||||
ch <- err
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Server) Stop() error {
|
||||
func (h *httpServer) Stop() error {
|
||||
ch := make(chan error)
|
||||
h.exit <- ch
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func (h *Server) String() string {
|
||||
func (h *httpServer) String() string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (h *Server) Name() string {
|
||||
func (h *httpServer) Name() string {
|
||||
return h.opts.Name
|
||||
}
|
||||
|
||||
func NewServer(opts ...options.Option) *Server {
|
||||
func NewServer(opts ...server.Option) server.Server {
|
||||
options := server.NewOptions(opts...)
|
||||
eh := DefaultErrorHandler
|
||||
if v, ok := options.Context.Value(errorHandlerKey{}).(errorHandler); ok && v != nil {
|
||||
eh = v
|
||||
}
|
||||
return &Server{
|
||||
opts: options,
|
||||
exit: make(chan chan error),
|
||||
errorHandler: eh,
|
||||
pathHandlers: rhttp.NewTrie(),
|
||||
return &httpServer{
|
||||
opts: options,
|
||||
exit: make(chan chan error),
|
||||
subscribers: make(map[*httpSubscriber][]broker.Subscriber),
|
||||
}
|
||||
}
|
||||
|
18
message.go
18
message.go
@ -1,23 +1,27 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"go.unistack.org/micro/v4/codec"
|
||||
"go.unistack.org/micro/v4/metadata"
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
type httpMessage struct {
|
||||
payload interface{}
|
||||
codec codec.Codec
|
||||
header metadata.Metadata
|
||||
topic string
|
||||
payload interface{}
|
||||
contentType string
|
||||
header metadata.Metadata
|
||||
body []byte
|
||||
codec codec.Codec
|
||||
}
|
||||
|
||||
func (r *httpMessage) Topic() string {
|
||||
return r.topic
|
||||
}
|
||||
|
||||
func (r *httpMessage) Payload() interface{} {
|
||||
return r.payload
|
||||
}
|
||||
|
||||
func (r *httpMessage) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
@ -26,8 +30,8 @@ func (r *httpMessage) Header() metadata.Metadata {
|
||||
return r.header
|
||||
}
|
||||
|
||||
func (r *httpMessage) Body() interface{} {
|
||||
return r.payload
|
||||
func (r *httpMessage) Body() []byte {
|
||||
return r.body
|
||||
}
|
||||
|
||||
func (r *httpMessage) Codec() codec.Codec {
|
||||
|
140
options.go
140
options.go
@ -5,53 +5,26 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.unistack.org/micro/v4/options"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
// SetError pass error to caller
|
||||
type rspCodeKey struct{}
|
||||
type rspCodeVal struct {
|
||||
code int
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
func SetRspCode(ctx context.Context, code int) {
|
||||
if rsp, ok := ctx.Value(rspCodeKey{}).(*rspCodeVal); ok {
|
||||
@ -59,14 +32,6 @@ 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
|
||||
func GetRspCode(ctx context.Context) int {
|
||||
var code int
|
||||
@ -78,97 +43,12 @@ func GetRspCode(ctx context.Context) int {
|
||||
|
||||
type middlewareKey struct{}
|
||||
|
||||
// Middleware passes http middlewares
|
||||
func Middleware(mw ...func(http.Handler) http.Handler) options.Option {
|
||||
return options.ContextOption(middlewareKey{}, mw)
|
||||
func Middleware(mw ...func(http.Handler) http.Handler) server.Option {
|
||||
return server.SetOption(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...)
|
||||
}
|
||||
func Server(hs *http.Server) server.Option {
|
||||
return server.SetOption(serverKey{}, hs)
|
||||
}
|
||||
|
52
request.go
52
request.go
@ -3,23 +3,31 @@ package http
|
||||
import (
|
||||
"io"
|
||||
|
||||
"go.unistack.org/micro/v4/codec"
|
||||
"go.unistack.org/micro/v4/metadata"
|
||||
"go.unistack.org/micro/v4/server"
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var _ server.Request = &rpcRequest{}
|
||||
|
||||
type rpcRequest struct {
|
||||
rw io.ReadWriter
|
||||
payload interface{}
|
||||
codec codec.Codec
|
||||
header metadata.Metadata
|
||||
service string
|
||||
method string
|
||||
endpoint string
|
||||
target string
|
||||
contentType string
|
||||
service string
|
||||
codec codec.Codec
|
||||
header metadata.Metadata
|
||||
body []byte
|
||||
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 {
|
||||
@ -35,7 +43,7 @@ func (r *rpcRequest) Method() string {
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Endpoint() string {
|
||||
return r.endpoint
|
||||
return r.method
|
||||
}
|
||||
|
||||
func (r *rpcRequest) Codec() codec.Codec {
|
||||
@ -61,3 +69,27 @@ func (r *rpcRequest) Stream() bool {
|
||||
func (r *rpcRequest) Body() interface{} {
|
||||
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,16 +7,14 @@ import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.unistack.org/micro/v4/server"
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
type methodType struct {
|
||||
method reflect.Method
|
||||
ArgType reflect.Type
|
||||
ReplyType reflect.Type
|
||||
ContextType reflect.Type
|
||||
method reflect.Method
|
||||
stream bool
|
||||
}
|
||||
|
||||
@ -64,14 +62,15 @@ 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())
|
||||
}
|
||||
|
||||
switch stream {
|
||||
case true:
|
||||
if stream {
|
||||
// check stream type
|
||||
streamType := reflect.TypeOf((*server.Stream)(nil)).Elem()
|
||||
if !argType.Implements(streamType) {
|
||||
return nil, fmt.Errorf("%v argument does not implement Streamer interface: %v", mname, argType)
|
||||
}
|
||||
default:
|
||||
} else {
|
||||
// if not stream check the replyType
|
||||
|
||||
// First arg need not be a pointer.
|
||||
if !isExportedOrBuiltinType(argType) {
|
||||
return nil, fmt.Errorf("%v argument type not exported: %v", mname, argType)
|
||||
|
213
subscriber.go
Normal file
213
subscriber.go
Normal file
@ -0,0 +1,213 @@
|
||||
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
8
tools.go
@ -1,8 +0,0 @@
|
||||
//go:build tools
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
_ "go.unistack.org/micro-proto/v4"
|
||||
_ "go.unistack.org/protoc-gen-go-micro/v4"
|
||||
)
|
53
util.go
53
util.go
@ -1,53 +0,0 @@
|
||||
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
56
util_test.go
@ -1,56 +0,0 @@
|
||||
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