Compare commits

...

26 Commits

Author SHA1 Message Date
7365c5f1cd guard import
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-26 22:55:51 +03:00
505c59ba75 fill request with header and cookie data
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-26 22:36:04 +03:00
7a39d86018 remove debug print
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-21 14:28:32 +03:00
06a0a2f336 update for latest micro, drop rutil usage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 03:01:11 +03:00
dependabot[bot]
776ce8591a Bump actions/github-script from 4 to 5 (#73)
Bumps [actions/github-script](https://github.com/actions/github-script) from 4 to 5.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-29 13:42:28 +03:00
48aaef3842 update
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 00:01:19 +03:00
36aa9f6a28 update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-21 21:47:57 +03:00
dependabot[bot]
4e54cd6f7d Bump github.com/unistack-org/micro/v3 from 3.7.0 to 3.7.1 (#71)
Bumps [github.com/unistack-org/micro/v3](https://github.com/unistack-org/micro) from 3.7.0 to 3.7.1.
- [Release notes](https://github.com/unistack-org/micro/releases)
- [Commits](https://github.com/unistack-org/micro/compare/v3.7.0...v3.7.1)

---
updated-dependencies:
- dependency-name: github.com/unistack-org/micro/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-17 08:33:57 +03:00
7886f3160d update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-17 07:49:25 +03:00
0e86aff88c enable automerge
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-16 11:10:47 +03:00
dependabot[bot]
5d23148038 Bump github.com/unistack-org/micro/v3 from 3.4.7 to 3.7.0 (#70) 2021-09-06 15:02:36 +00:00
8c61439bc5 add github stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-06 15:28:03 +03:00
7ad1a9c38e add github stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-06 10:34:26 +03:00
5c829fe029 add GetError method to Error type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-31 23:56:56 +03:00
3e1ffcb989 add all http request metadata
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-11 12:47:44 +03:00
c1e0ce26b5 add ability to register RPC endpoint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 13:43:45 +03:00
26e34124e0 update all
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-06 19:12:46 +03:00
c1526f70ce dont display unneded error message
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-06 19:10:54 +03:00
28d23f388f fixup new codec detection
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-14 14:17:33 +03:00
d3d67eca97 allow to response with different content-type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-13 15:46:47 +03:00
919bf1486f fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-13 12:11:54 +03:00
88ef21ba9e fix broker disconnect
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-13 10:59:38 +03:00
Renovate Bot
254b01f75a Update golang.org/x/net commit hash to 4163338 2021-05-10 13:29:05 +00:00
Renovate Bot
325b8a43ef Update golang.org/x/net commit hash to 81045d8 2021-05-10 12:01:05 +00:00
Renovate Bot
d94860c32d Update golang.org/x/net commit hash to 16afe75 2021-05-08 09:19:14 +00:00
Renovate Bot
778095179d Update golang.org/x/net commit hash to 0714010 2021-05-05 23:20:59 +00:00
17 changed files with 460 additions and 144 deletions

19
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Maintain dependencies for Golang
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

20
.github/renovate.json vendored
View File

@@ -1,20 +0,0 @@
{
"extends": [
"config:base"
],
"postUpdateOptions": ["gomodTidy"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
},
{
"groupName": "all deps",
"separateMajorMinor": true,
"groupSlug": "all",
"packagePatterns": [
"*"
]
}
]
}

View File

@@ -34,9 +34,10 @@ jobs:
uses: actions/checkout@v2
- name: lint
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.
version: v1.39
version: v1.30
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.

75
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,75 @@
# 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 ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
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 repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
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@v1
# 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: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -0,0 +1,66 @@
name: "prautomerge"
on:
workflow_run:
workflows: ["prbuild"]
types:
- completed
permissions:
contents: write
pull-requests: write
jobs:
Dependabot-Automerge:
runs-on: ubuntu-latest
# Contains workaround to execute if dependabot updates the PR by checking for the base branch in the linked PR
# The the github.event.workflow_run.event value is 'push' and not 'pull_request'
# dont work with multiple workflows when last returns success
if: >-
github.event.workflow_run.conclusion == 'success'
&& github.actor == 'dependabot[bot]'
&& github.event.sender.login == 'dependabot[bot]'
&& github.event.sender.type == 'Bot'
&& (github.event.workflow_run.event == 'pull_request'
|| (github.event.workflow_run.event == 'push' && github.event.workflow_run.pull_requests[0].base.ref == github.event.repository.default_branch ))
steps:
- name: Approve Changes and Merge changes if label 'dependencies' is set
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
console.log(context.payload.workflow_run);
var labelNames = await github.paginate(
github.issues.listLabelsOnIssue,
{
repo: context.repo.repo,
owner: context.repo.owner,
issue_number: context.payload.workflow_run.pull_requests[0].number,
},
(response) => response.data.map(
(label) => label.name
)
);
console.log(labelNames);
if (labelNames.includes('dependencies')) {
console.log('Found label');
await github.pulls.createReview({
repo: context.repo.repo,
owner: context.repo.owner,
pull_number: context.payload.workflow_run.pull_requests[0].number,
event: 'APPROVE'
});
console.log('Approved PR');
await github.pulls.merge({
repo: context.repo.repo,
owner: context.repo.owner,
pull_number: context.payload.workflow_run.pull_requests[0].number,
});
console.log('Merged PR');
}

View File

@@ -34,9 +34,10 @@ jobs:
uses: actions/checkout@v2
- name: lint
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.
version: v1.39
version: v1.30
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.

6
go.mod
View File

@@ -1,8 +1,8 @@
module github.com/unistack-org/micro-server-http/v3
module go.unistack.org/micro-server-http/v3
go 1.16
require (
github.com/unistack-org/micro/v3 v3.3.17
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125
go.unistack.org/micro/v3 v3.8.5
golang.org/x/net v0.0.0-20211020060615-d418f374d309
)

27
go.sum
View File

@@ -1,20 +1,29 @@
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
github.com/unistack-org/micro/v3 v3.3.17 h1:WcyS7InP0DlS/JpRQGLh5sG6VstkdHJbgpMp+gmHmwg=
github.com/unistack-org/micro/v3 v3.3.17/go.mod h1:022EOEZZ789hZY3yB5ZSMXU6jLiadBgcNB/cpediV3c=
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
go.unistack.org/micro-proto/v3 v3.1.0 h1:q39FwjFiRZn+Ux/tt+d3bJTmDtsQQWa+3SLYVo1vLfA=
go.unistack.org/micro-proto/v3 v3.1.0/go.mod h1:DpRhYCBXlmSJ/AAXTmntvlh7kQkYU6eFvlmYAx4BQS8=
go.unistack.org/micro/v3 v3.8.5 h1:DIYWRsQF+NPhKZP45sCtNsUhaRw6u2+Ps7U+pKU7i3s=
go.unistack.org/micro/v3 v3.8.5/go.mod h1:KMMmOmbgo/D52/rCAbqeKbBsgEEbSKM69he54J3ZIuA=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -9,13 +9,13 @@ import (
"strings"
"sync"
"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"
"go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/server"
rhttp "go.unistack.org/micro/v3/util/http"
rflutil "go.unistack.org/micro/v3/util/reflect"
)
var (
@@ -31,8 +31,8 @@ var (
type patHandler struct {
mtype *methodType
rcvr reflect.Value
pat *rhttp.Trie
name string
pat rutil.Pattern
}
type httpHandler struct {
@@ -62,15 +62,13 @@ func (h *httpHandler) Options() server.HandlerOptions {
}
func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for exp, ph := range h.pathHandlers {
if exp.MatchString(r.URL.String()) {
ph(w, r)
return
}
if ph, _, ok := h.pathHandlers.Search(r.Method, r.URL.Path); ok {
ph.(http.HandlerFunc)(w, r)
return
}
ct := DefaultContentType
if htype := r.Header.Get("Content-Type"); htype != "" {
if htype := r.Header.Get(metadata.HeaderContentType); htype != "" {
ct = htype
}
@@ -89,13 +87,21 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for k, v := range r.Header {
md.Set(k, strings.Join(v, ", "))
}
md.Set("RemoteAddr", r.RemoteAddr)
md.Set("Method", r.Method)
md.Set("URL", r.URL.String())
md.Set("Proto", r.Proto)
md.Set("ContentLength", fmt.Sprintf("%d", r.ContentLength))
md.Set("TransferEncoding", strings.Join(r.TransferEncoding, ","))
md.Set("Host", r.Host)
md.Set("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 contains /"), http.StatusBadRequest)
h.errorHandler(ctx, nil, w, r, fmt.Errorf("path must starts with /"), http.StatusBadRequest)
return
}
@@ -105,29 +111,17 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
components := strings.Split(path[1:], "/")
l := len(components)
var verb string
idx := strings.LastIndex(components[l-1], ":")
if idx == 0 {
h.errorHandler(ctx, nil, 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 _, hpat := range h.handlers {
handlertmp := hpat.(*httpHandler)
for _, hldrtmp := range handlertmp.handlers[r.Method] {
mp, merr := hldrtmp.pat.Match(components, verb)
if merr == nil {
_, mp, ok := hldrtmp.pat.Search(r.Method, path)
if ok {
match = true
for k, v := range mp {
matches[k] = v
@@ -175,7 +169,6 @@ func (h *httpServer) 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 err = cf.ReadBody(r.Body, argv.Interface()); err != nil && err != io.EOF {
@@ -243,7 +236,22 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
scode := int(200)
if appErr := fn(ctx, hr, replyv.Interface()); appErr != nil {
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().Set(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
}
}
if appErr != nil {
switch verr := appErr.(type) {
case *errors.Error:
scode = int(verr.Code)
@@ -256,19 +264,12 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else {
b, err = cf.Marshal(replyv.Interface())
}
if err != nil && handler.sopts.Logger.V(logger.ErrorLevel) {
handler.sopts.Logger.Errorf(handler.sopts.Context, "handler err: %v", err)
return
}
w.Header().Set("Content-Type", ct)
if md, ok := metadata.FromOutgoingContext(ctx); ok {
for k, v := range md {
w.Header().Set(k, v)
}
}
if nscode := GetRspCode(ctx); nscode != 0 {
scode = nscode
}

133
http.go
View File

@@ -1,5 +1,5 @@
// Package http implements a go-micro.Server
package http
package http // import "go.unistack.org/micro-server-http/v3"
import (
"context"
@@ -8,21 +8,32 @@ import (
"net"
"net/http"
"reflect"
"regexp"
"sort"
"strings"
"sync"
"time"
"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"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/server"
rhttp "go.unistack.org/micro/v3/util/http"
"golang.org/x/net/netutil"
)
var httpAllMethods = []string{
http.MethodConnect,
http.MethodDelete,
http.MethodGet,
http.MethodHead,
http.MethodOptions,
http.MethodPatch,
http.MethodPost,
http.MethodPut,
http.MethodTrace,
}
type httpServer struct {
hd server.Handler
rsvc *register.Service
@@ -30,23 +41,23 @@ type httpServer struct {
exit chan chan error
subscribers map[*httpSubscriber][]broker.Subscriber
errorHandler func(context.Context, server.Handler, http.ResponseWriter, *http.Request, error, int)
pathHandlers map[*regexp.Regexp]http.HandlerFunc
pathHandlers *rhttp.Trie
contentTypeHandlers map[string]http.HandlerFunc
opts server.Options
registerRPC bool
sync.RWMutex
registered bool
init bool
}
func (h *httpServer) newCodec(ct string) (codec.Codec, error) {
h.RLock()
defer h.RUnlock()
if idx := strings.IndexRune(ct, ';'); idx >= 0 {
ct = ct[:idx]
}
if cf, ok := h.opts.Codecs[ct]; ok {
h.RLock()
cf, ok := h.opts.Codecs[ct]
h.RUnlock()
if ok {
return cf, nil
}
return nil, codec.ErrUnknownContentType
@@ -65,7 +76,6 @@ func (h *httpServer) Init(opts ...server.Option) error {
}
h.Lock()
defer h.Unlock()
for _, o := range opts {
o(&h.opts)
@@ -77,18 +87,19 @@ func (h *httpServer) Init(opts ...server.Option) error {
h.handlers = make(map[string]server.Handler)
}
if h.pathHandlers == nil {
h.pathHandlers = make(map[*regexp.Regexp]http.HandlerFunc)
h.pathHandlers = rhttp.NewTrie()
}
if h.contentTypeHandlers == nil {
h.contentTypeHandlers = make(map[string]http.HandlerFunc)
}
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 pp, ph := range phs.h {
exp, err := regexp.Compile(pp)
if err != nil {
return err
}
h.pathHandlers[exp] = ph
h.pathHandlers.Insert(httpAllMethods, pp, ph)
}
}
if phs, ok := h.opts.Context.Value(contentTypeHandlerKey{}).(*contentTypeHandlerVal); ok && phs.h != nil {
@@ -96,52 +107,68 @@ func (h *httpServer) Init(opts ...server.Option) error {
h.contentTypeHandlers[pp] = ph
}
}
h.Unlock()
h.RLock()
if err := h.opts.Register.Init(); err != nil {
h.RUnlock()
return err
}
if err := h.opts.Broker.Init(); err != nil {
h.RUnlock()
return err
}
if err := h.opts.Tracer.Init(); err != nil {
h.RUnlock()
return err
}
if err := h.opts.Auth.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
}
if err := h.opts.Transport.Init(); err != nil {
h.RUnlock()
return err
}
h.RUnlock()
h.Lock()
h.init = true
h.Unlock()
return nil
}
func (h *httpServer) Handle(handler server.Handler) error {
h.Lock()
defer h.Unlock()
hdlr, ok := handler.(*httpHandler)
if !ok {
h.Lock()
h.hd = handler
h.Unlock()
return nil
}
if _, ok := hdlr.hd.(http.Handler); ok {
h.Lock()
h.hd = handler
h.Unlock()
return nil
}
h.Lock()
if h.handlers == nil {
h.handlers = make(map[string]server.Handler)
}
h.handlers[handler.Name()] = handler
h.Unlock()
return nil
}
@@ -165,35 +192,12 @@ func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOptio
}
tp := reflect.TypeOf(handler)
/*
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)
}
}
*/
type nilHandler struct{}
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
}
pat := rhttp.NewTrie()
pat.Insert([]string{md["Method"]}, md["Path"], &nilHandler{})
var method reflect.Method
mname := hn[strings.Index(hn, ".")+1:]
@@ -225,6 +229,16 @@ func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOptio
pth := patHandler{pat: pat, mtype: mtype, name: name, rcvr: rcvr}
hdlr.name = name
hdlr.handlers[md["Method"]] = append(hdlr.handlers[md["Method"]], pth)
if !h.registerRPC {
continue
}
rpat := rhttp.NewTrie()
rpat.Insert([]string{http.MethodPost}, "/"+hn, &nilHandler{})
pth = patHandler{pat: rpat, mtype: mtype, name: name, rcvr: rcvr}
hdlr.handlers[http.MethodPost] = append(hdlr.handlers[http.MethodPost], pth)
}
return hdlr
@@ -247,13 +261,15 @@ func (h *httpServer) Subscribe(sb server.Subscriber) error {
return err
}
h.Lock()
defer h.Unlock()
h.RLock()
_, ok = h.subscribers[sub]
h.RUnlock()
if ok {
return fmt.Errorf("subscriber %v already exists", h)
}
h.Lock()
h.subscribers[sub] = nil
h.Unlock()
return nil
}
@@ -318,8 +334,6 @@ func (h *httpServer) Register() error {
}
h.Lock()
defer h.Unlock()
for sb := range h.subscribers {
handler := h.createSubHandler(sb, config)
var opts []broker.SubscribeOption
@@ -336,6 +350,7 @@ func (h *httpServer) Register() error {
sub, err := config.Broker.Subscribe(subCtx, sb.Topic(), handler, opts...)
if err != nil {
h.Unlock()
return err
}
h.subscribers[sb] = []broker.Subscriber{sub}
@@ -343,6 +358,7 @@ func (h *httpServer) Register() error {
h.registered = true
h.rsvc = service
h.Unlock()
return nil
}
@@ -384,6 +400,7 @@ func (h *httpServer) Deregister() error {
for _, sub := range subs {
config.Logger.Infof(config.Context, "Unsubscribing from topic: %s", sub.Topic())
if err := sub.Unsubscribe(subCtx); err != nil {
h.Unlock()
config.Logger.Errorf(config.Context, "failed to unsubscribe topic: %s, error: %v", sb.Topic(), err)
return err
}
@@ -491,13 +508,13 @@ func (h *httpServer) Start() error {
if srvFunc != nil {
go func() {
if cerr := srvFunc(ts); cerr != nil {
if cerr := srvFunc(ts); cerr != nil && !strings.Contains(cerr.Error(), "use of closed network connection") {
h.opts.Logger.Error(h.opts.Context, cerr)
}
}()
} else {
go func() {
if cerr := http.Serve(ts, fn); cerr != nil {
if cerr := http.Serve(ts, fn); cerr != nil && !strings.Contains(cerr.Error(), "use of closed network connection") {
h.opts.Logger.Error(h.opts.Context, cerr)
}
}()
@@ -556,8 +573,6 @@ func (h *httpServer) Start() error {
}
}
ch <- ts.Close()
// deregister
if err := h.Deregister(); err != nil {
config.Logger.Errorf(config.Context, "Server deregister error: %s", err)
@@ -566,6 +581,8 @@ func (h *httpServer) Start() error {
if err := config.Broker.Disconnect(config.Context); err != nil {
config.Logger.Errorf(config.Context, "Broker disconnect error: %s", err)
}
ch <- ts.Close()
}()
return nil
@@ -592,6 +609,6 @@ func NewServer(opts ...server.Option) server.Server {
exit: make(chan chan error),
subscribers: make(map[*httpSubscriber][]broker.Subscriber),
errorHandler: DefaultErrorHandler,
pathHandlers: make(map[*regexp.Regexp]http.HandlerFunc),
pathHandlers: rhttp.NewTrie(),
}
}

View File

@@ -1,8 +1,8 @@
package http
import (
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/metadata"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
)
type httpMessage struct {

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"net/http"
"github.com/unistack-org/micro/v3/server"
"go.unistack.org/micro/v3/server"
)
// SetError pass error to caller
@@ -13,6 +13,14 @@ 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{}
@@ -110,3 +118,31 @@ func ContentTypeHandler(ct string, h http.HandlerFunc) server.Option {
o.Context = context.WithValue(o.Context, contentTypeHandlerKey{}, v)
}
}
type registerRPCHandlerKey struct{}
// RegisterRPCHandler registers compatibility endpoints with /ServiceName.ServiceEndpoint method POST
func RegisterRPCHandler(b bool) server.Option {
return server.SetOption(registerRPCHandlerKey{}, b)
}
type headerKey struct{}
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...)
}
}

View File

@@ -3,9 +3,9 @@ package http
import (
"io"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/server"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/server"
)
var (

View File

@@ -7,7 +7,7 @@ import (
"unicode"
"unicode/utf8"
"github.com/unistack-org/micro/v3/server"
"go.unistack.org/micro/v3/server"
)
type methodType struct {

View File

@@ -7,11 +7,11 @@ import (
"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"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/server"
)
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()

55
util.go Normal file
View File

@@ -0,0 +1,55 @@
package http
import (
"context"
"net/http"
"strings"
"go.unistack.org/micro/v3/metadata"
rutil "go.unistack.org/micro/v3/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 := strings.Split(md["Cookie"], ";")
cmd := make(map[string]string, len(cookies))
for _, cookie := range cookies {
kv := strings.Split(cookie, "=")
if len(kv) != 2 {
continue
}
cmd[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
}
for idx := 0; idx < len(options.cookies)/2; idx += 2 {
k := http.CanonicalHeaderKey(options.cookies[idx])
v, ok := cmd[k]
if !ok {
continue
}
if err = rutil.SetFieldByPath(req, v, k); err != nil {
return err
}
}
return nil
}

56
util_test.go Normal file
View File

@@ -0,0 +1,56 @@
package http
import (
"bytes"
"context"
"net/http"
"strings"
"testing"
"go.unistack.org/micro/v3/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)
}
}