Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: Add Request Info Middleware #4526

Merged
merged 56 commits into from
Jan 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3a45237
close #4494
CabinfeverB Dec 23, 2021
725345f
close #4494: add priority comment
CabinfeverB Dec 27, 2021
f30bb7b
Merge branch 'master' into http_source_ip
CabinfeverB Dec 29, 2021
198fb0b
add request info middleware
CabinfeverB Dec 30, 2021
a80af26
close #4494
CabinfeverB Dec 30, 2021
d9ed82f
Merge remote-tracking branch 'origin/http_source_ip' into http_source_ip
CabinfeverB Dec 30, 2021
5af1d1c
change service label getter and setter
CabinfeverB Dec 30, 2021
3041f1f
Merge branch 'master' into service_middleware
CabinfeverB Dec 30, 2021
232f798
add lock
CabinfeverB Dec 30, 2021
bd9553b
add audit middleware
CabinfeverB Jan 4, 2022
01f88c7
add audit middleware
CabinfeverB Jan 4, 2022
a313450
Merge branch 'http_source_ip' into service_middleware
CabinfeverB Jan 4, 2022
5ba83fe
#4538
CabinfeverB Jan 4, 2022
e3ddcd9
fix
CabinfeverB Jan 4, 2022
edd63ae
fix check
CabinfeverB Jan 4, 2022
271e58f
fix check
CabinfeverB Jan 4, 2022
2540d56
change service label placement
CabinfeverB Jan 5, 2022
ea08f30
merge master
CabinfeverB Jan 5, 2022
4800b61
merge master
CabinfeverB Jan 5, 2022
9a2eaec
Merge branch 'master' into service_middleware
nolouch Jan 12, 2022
894a0fe
add const service label and could be dynamically turned on and off s…
CabinfeverB Jan 13, 2022
befd6d3
Merge branch 'service_middleware' of github.com:CabinfeverB/pd into s…
CabinfeverB Jan 13, 2022
4a2eb02
fix statics check
CabinfeverB Jan 13, 2022
0981c7b
fix statics check
CabinfeverB Jan 13, 2022
4fdb3d4
fix statics check
CabinfeverB Jan 13, 2022
8f9d6a7
fix statics check
CabinfeverB Jan 13, 2022
b68a2ae
fix function name
CabinfeverB Jan 14, 2022
932ba4d
add benchmark test
CabinfeverB Jan 14, 2022
2652c93
fix r.Body nil panic
CabinfeverB Jan 14, 2022
2eff957
Merge branch 'service_middleware' into service_middleware_benchmark
CabinfeverB Jan 14, 2022
be4a3f3
add benchmark test
CabinfeverB Jan 14, 2022
1b6ab1c
change export
CabinfeverB Jan 14, 2022
dbaf902
change export
CabinfeverB Jan 14, 2022
e778858
Merge branch 'service_middleware_benchmark' into service_middleware
CabinfeverB Jan 14, 2022
2e8966f
fix statics check
CabinfeverB Jan 17, 2022
a112ed9
fix typo problem
CabinfeverB Jan 17, 2022
f83681b
change service label method
CabinfeverB Jan 17, 2022
3366bc7
merge master
CabinfeverB Jan 17, 2022
aa11cee
fix statics
CabinfeverB Jan 17, 2022
42d8b60
fix router
CabinfeverB Jan 17, 2022
6852387
fix router
CabinfeverB Jan 17, 2022
e186524
add http proto
CabinfeverB Jan 18, 2022
71ce331
add http proto
CabinfeverB Jan 18, 2022
97025d4
change default value
CabinfeverB Jan 19, 2022
3c40ef8
use middleware func shortname
CabinfeverB Jan 19, 2022
c308901
fix comment
CabinfeverB Jan 19, 2022
8659fc6
change const to iota
CabinfeverB Jan 20, 2022
43beb8b
Merge branch 'master' into service_middleware
CabinfeverB Jan 20, 2022
cc577b3
change register method
CabinfeverB Jan 20, 2022
6a48e2e
fix
CabinfeverB Jan 20, 2022
dc52e3d
fix
CabinfeverB Jan 20, 2022
7b4f6b9
Merge branch 'master' into service_middleware
CabinfeverB Jan 20, 2022
83c4ca9
for test
CabinfeverB Jan 20, 2022
b7ec272
for test
CabinfeverB Jan 20, 2022
3975382
fix race
CabinfeverB Jan 20, 2022
924f302
fix race
CabinfeverB Jan 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pkg/apiutil/apiutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strconv"
"strings"

"github.com/gorilla/mux"
"github.com/pingcap/errcode"
"github.com/pingcap/errors"
"github.com/pingcap/log"
Expand Down Expand Up @@ -190,3 +191,12 @@ func (rt *ComponentSignatureRoundTripper) RoundTrip(req *http.Request) (resp *ht
resp, err = rt.proxied.RoundTrip(req)
return
}

// GetRouteName return mux route name registered
func GetRouteName(req *http.Request) string {
route := mux.CurrentRoute(req)
if route != nil {
return route.GetName()
}
return ""
}
38 changes: 38 additions & 0 deletions pkg/requestutil/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2022 TiKV Project Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package requestutil

import (
"context"
)

// The key type is unexported to prevent collisions
type key int

const (
// requestInfoKey is the context key for the request compoenent.
requestInfoKey key = iota
CabinfeverB marked this conversation as resolved.
Show resolved Hide resolved
)

// WithRequestInfo returns a copy of parent in which the request info value is set
func WithRequestInfo(parent context.Context, requestInfo RequestInfo) context.Context {
return context.WithValue(parent, requestInfoKey, requestInfo)
}

// RequestInfoFrom returns the value of the request info key on the ctx
func RequestInfoFrom(ctx context.Context) (RequestInfo, bool) {
requestInfo, ok := ctx.Value(requestInfoKey).(RequestInfo)
return requestInfo, ok
}
57 changes: 57 additions & 0 deletions pkg/requestutil/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2022 TiKV Project Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package requestutil

import (
"context"
"testing"

. "github.com/pingcap/check"
)

func Test(t *testing.T) {
TestingT(t)
}

var _ = Suite(&testRequestContextSuite{})

type testRequestContextSuite struct {
}

func (s *testRequestContextSuite) TestRequestInfo(c *C) {
ctx := context.Background()
_, ok := RequestInfoFrom(ctx)
c.Assert(ok, Equals, false)
ctx = WithRequestInfo(ctx,
RequestInfo{
ServiceLabel: "test label",
Method: "POST",
Component: "pdctl",
IP: "localhost",
URLParam: "{\"id\"=1}",
BodyParam: "{\"state\"=\"Up\"}",
TimeStamp: "2022",
})
result, ok := RequestInfoFrom(ctx)
c.Assert(result, NotNil)
c.Assert(ok, Equals, true)
c.Assert(result.ServiceLabel, Equals, "test label")
c.Assert(result.Method, Equals, "POST")
c.Assert(result.Component, Equals, "pdctl")
c.Assert(result.IP, Equals, "localhost")
c.Assert(result.URLParam, Equals, "{\"id\"=1}")
c.Assert(result.BodyParam, Equals, "{\"state\"=\"Up\"}")
c.Assert(result.TimeStamp, Equals, "2022")
}
70 changes: 70 additions & 0 deletions pkg/requestutil/request_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2022 TiKV Project Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package requestutil

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"

"github.com/tikv/pd/pkg/apiutil"
)

// RequestInfo holds service information from http.Request
type RequestInfo struct {
ServiceLabel string
Method string
Component string
IP string
TimeStamp string
URLParam string
BodyParam string
}

// GetRequestInfo returns request info needed from http.Request
func GetRequestInfo(r *http.Request) RequestInfo {
return RequestInfo{
ServiceLabel: apiutil.GetRouteName(r),
Method: fmt.Sprintf("%s/%s:%s", r.Proto, r.Method, r.URL.Path),
Component: apiutil.GetComponentNameOnHTTP(r),
IP: apiutil.GetIPAddrFromHTTPRequest(r),
TimeStamp: time.Now().Local().String(),
URLParam: getURLParam(r),
BodyParam: getBodyParam(r),
}
}

func getURLParam(r *http.Request) string {
buf, err := json.Marshal(r.URL.Query())
if err != nil {
return ""
}
return string(buf)
}

func getBodyParam(r *http.Request) string {
if r.Body == nil {
return ""
}
// http request body is a io.Reader between bytes.Reader and strings.Reader, it only has EOF error
buf, _ := io.ReadAll(r.Body)
r.Body.Close()
bodyParam := string(buf)
r.Body = io.NopCloser(bytes.NewBuffer(buf))
return bodyParam
}
18 changes: 18 additions & 0 deletions server/api/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,21 @@ func (h *adminHandler) UpdateWaitAsyncTime(w http.ResponseWriter, r *http.Reques
cluster.GetReplicationMode().UpdateMemberWaitAsyncTime(memberID)
h.rd.JSON(w, http.StatusOK, nil)
}

// @Tags admin
// @Summary switch Service Middlewares including ServiceInfo, Audit and rate limit
// @Param enable query string true "enable" Enums(true, false)
// @Produce json
// @Success 200 {string} string "Switching Service middleware is successful."
// @Failure 400 {string} string "The input is invalid."
// @Router /admin/service-middleware [POST]
func (h *adminHandler) HanldeServiceMiddlewareSwitch(w http.ResponseWriter, r *http.Request) {
enableStr := r.URL.Query().Get("enable")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many switches do we need after supporting the audit and rate limit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

three switches. A total service middleware switches, a rate limit switch, and an audit switch

enable, err := strconv.ParseBool(enableStr)
if err != nil {
h.rd.JSON(w, http.StatusBadRequest, "The input is invalid.")
return
}
h.svr.SetServiceMiddleware(enable)
h.rd.JSON(w, http.StatusOK, "Switching Service middleware is successful.")
}
42 changes: 42 additions & 0 deletions server/api/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,45 @@ func (s *testTSOSuite) TestResetTS(c *C) {
c.Assert(err, NotNil)
c.Assert(err.Error(), Equals, "\"invalid tso value\"\n")
}

var _ = Suite(&testServiceSuite{})

type testServiceSuite struct {
svr *server.Server
cleanup cleanUpFunc
}

func (s *testServiceSuite) SetUpSuite(c *C) {
s.svr, s.cleanup = mustNewServer(c)
mustWaitLeader(c, []*server.Server{s.svr})

mustBootstrapCluster(c, s.svr)
mustPutStore(c, s.svr, 1, metapb.StoreState_Up, nil)
}

func (s *testServiceSuite) TearDownSuite(c *C) {
s.cleanup()
}

func (s *testServiceSuite) TestSwitchServiceMiddleware(c *C) {
urlPrefix := fmt.Sprintf("%s%s/api/v1/admin/service-middleware", s.svr.GetAddr(), apiPrefix)
disableURL := fmt.Sprintf("%s?enable=false", urlPrefix)
err := postJSON(testDialClient, disableURL, nil,
func(res []byte, code int) {
c.Assert(string(res), Equals, "\"Switching Service middleware is successful.\"\n")
c.Assert(code, Equals, http.StatusOK)
})

c.Assert(err, IsNil)
c.Assert(s.svr.IsServiceMiddlewareEnabled(), Equals, false)

enableURL := fmt.Sprintf("%s?enable=true", urlPrefix)
err = postJSON(testDialClient, enableURL, nil,
func(res []byte, code int) {
c.Assert(string(res), Equals, "\"Switching Service middleware is successful.\"\n")
c.Assert(code, Equals, http.StatusOK)
})

c.Assert(err, IsNil)
c.Assert(s.svr.IsServiceMiddlewareEnabled(), Equals, true)
}
33 changes: 33 additions & 0 deletions server/api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,45 @@ import (
"context"
"net/http"

"github.com/pingcap/failpoint"
"github.com/tikv/pd/pkg/errs"
"github.com/tikv/pd/pkg/requestutil"
"github.com/tikv/pd/server"
"github.com/tikv/pd/server/cluster"
"github.com/unrolled/render"
"github.com/urfave/negroni"
)

// requestInfoMiddleware is used to gather info from requsetInfo
CabinfeverB marked this conversation as resolved.
Show resolved Hide resolved
type requestInfoMiddleware struct {
svr *server.Server
}

func newRequestInfoMiddleware(s *server.Server) negroni.Handler {
return &requestInfoMiddleware{svr: s}
}

func (rm *requestInfoMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if !rm.svr.IsServiceMiddlewareEnabled() {
next(w, r)
return
}

requestInfo := requestutil.GetRequestInfo(r)
r = r.WithContext(requestutil.WithRequestInfo(r.Context(), requestInfo))

failpoint.Inject("addRequestInfoMiddleware", func() {
w.Header().Add("service-label", requestInfo.ServiceLabel)
w.Header().Add("body-param", requestInfo.BodyParam)
w.Header().Add("url-param", requestInfo.URLParam)
w.Header().Add("method", requestInfo.Method)
w.Header().Add("component", requestInfo.Component)
w.Header().Add("ip", requestInfo.IP)
})

next(w, r)
}

type clusterMiddleware struct {
s *server.Server
rd *render.Render
Expand Down
Loading