Skip to content

Commit d4506bb

Browse files
authored
#188398853 Initial commit of Gin framework Moesif middleware (#1)
* Initial commit of Gin framework Moesif middleware * Code cleanup * Readme for Gin SDK
1 parent aa7fc88 commit d4506bb

11 files changed

+2344
-1
lines changed

README.md

+1,033-1
Large diffs are not rendered by default.

appconfig.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package moesifgin
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"log"
7+
"sync"
8+
)
9+
10+
type AppConfig struct {
11+
Mu sync.RWMutex
12+
Updates chan string
13+
eTags [2]string
14+
config AppConfigResponse
15+
}
16+
17+
func NewAppConfig() AppConfig {
18+
return AppConfig{
19+
Updates: make(chan string, 1),
20+
config: NewAppConfigResponse(),
21+
}
22+
}
23+
24+
func (c *AppConfig) Read() AppConfigResponse {
25+
c.Mu.RLock()
26+
defer c.Mu.RUnlock()
27+
return c.config
28+
}
29+
30+
func (c *AppConfig) Write(config AppConfigResponse) {
31+
c.Mu.Lock()
32+
defer c.Mu.Unlock()
33+
c.config = config
34+
c.eTags[1] = c.eTags[0]
35+
c.eTags[0] = config.eTag
36+
}
37+
38+
func (c *AppConfig) Go() {
39+
go c.UpdateLoop()
40+
c.Notify("go")
41+
}
42+
43+
func (c *AppConfig) Notify(eTag string) {
44+
c.Mu.RLock()
45+
e := c.eTags
46+
c.Mu.RUnlock()
47+
if eTag == "" || eTag == e[0] || eTag == e[1] {
48+
return
49+
}
50+
select {
51+
case c.Updates <- eTag:
52+
default:
53+
}
54+
}
55+
56+
func (c *AppConfig) UpdateLoop() {
57+
for {
58+
eTag, more := <-c.Updates
59+
if !more {
60+
return
61+
}
62+
config, err := getAppConfig()
63+
if err != nil {
64+
log.Printf("Failed to get config: %v", err)
65+
continue
66+
}
67+
log.Printf("AppConfig.Notify ETag=%s got /config response ETag=%s", eTag, config.eTag)
68+
c.Write(config)
69+
}
70+
}
71+
72+
func (c *AppConfig) GetEntityValues(userId, companyId string) (userValues, companyValues []EntityRuleValues) {
73+
config := c.Read()
74+
return config.UserRules[userId], config.CompanyRules[companyId]
75+
}
76+
77+
type AppConfigResponse struct {
78+
OrgID string `json:"org_id"`
79+
AppID string `json:"app_id"`
80+
SampleRate int `json:"sample_rate"`
81+
BlockBotTraffic bool `json:"block_bot_traffic"`
82+
UserSampleRate map[string]int `json:"user_sample_rate"` // user id to a sample rate [0, 100]
83+
CompanySampleRate map[string]int `json:"company_sample_rate"` // company id to a sample rate [0, 100]
84+
UserRules map[string][]EntityRuleValues `json:"user_rules"` // user id to a rule id and template values
85+
CompanyRules map[string][]EntityRuleValues `json:"company_rules"` // company id to a rule id and template values
86+
IPAddressesBlockedByName map[string]string `json:"ip_addresses_blocked_by_name"`
87+
RegexConfig []RegexRule `json:"regex_config"`
88+
BillingConfigJsons map[string]string `json:"billing_config_jsons"`
89+
eTag string
90+
}
91+
92+
func NewAppConfigResponse() AppConfigResponse {
93+
return AppConfigResponse{
94+
SampleRate: 100,
95+
}
96+
}
97+
98+
// EntityRule is a user rule or company rule
99+
type EntityRuleValues struct {
100+
Rule string `json:"rules"`
101+
Values map[string]string `json:"values"`
102+
}
103+
104+
// Regex Rule
105+
type RegexRule struct {
106+
Conditions []RegexCondition `json:"conditions"`
107+
SampleRate int `json:"sample_rate"`
108+
}
109+
110+
// RegexCondition
111+
type RegexCondition struct {
112+
Path string `json:"path"`
113+
Value string `json:"value"`
114+
}
115+
116+
func getAppConfig() (config AppConfigResponse, err error) {
117+
config = NewAppConfigResponse()
118+
r, err := apiClient.GetAppConfig()
119+
if err != nil {
120+
log.Printf("Application configuration request error: %v", err)
121+
return
122+
}
123+
body, err := ioutil.ReadAll(r.Body)
124+
if err != nil {
125+
log.Printf("Application configuration response body read error: %v", err)
126+
return
127+
}
128+
err = json.Unmarshal(body, &config)
129+
if err != nil {
130+
log.Printf("Application configuration response body malformed: %v", err)
131+
return
132+
}
133+
config.eTag = r.Header.Get("X-Moesif-Config-Etag")
134+
return
135+
}
136+
137+
func getSamplingPercentage(userId string, companyId string) int {
138+
c := appConfig.Read()
139+
if userId != "" {
140+
if userRate, ok := c.UserSampleRate[userId]; ok {
141+
return userRate
142+
}
143+
}
144+
145+
if companyId != "" {
146+
if companyRate, ok := c.CompanySampleRate[companyId]; ok {
147+
return companyRate
148+
}
149+
}
150+
151+
return c.SampleRate
152+
}

captureoutgoing.go

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package moesifgin
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"io/ioutil"
7+
"log"
8+
"net/http"
9+
"strings"
10+
"time"
11+
)
12+
13+
// Transport implements http.RoundTripper.
14+
type Transport struct {
15+
Transport http.RoundTripper
16+
LogRequest func(req *http.Request)
17+
LogResponse func(resp *http.Response)
18+
}
19+
20+
// The default logging transport that wraps http.DefaultTransport.
21+
var DefaultTransport = &Transport{
22+
Transport: http.DefaultTransport,
23+
}
24+
25+
type contextKey struct {
26+
name string
27+
}
28+
29+
var ContextKeyRequestStart = &contextKey{"RequestStart"}
30+
31+
// RoundTrip is the core part of this module and implements http.RoundTripper.
32+
func (t *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
33+
ctx := context.WithValue(request.Context(), ContextKeyRequestStart, time.Now())
34+
request = request.WithContext(ctx)
35+
36+
// Outgoing Request Time
37+
outgoingReqTime := time.Now().UTC()
38+
39+
response, err := t.transport().RoundTrip(request)
40+
if err != nil {
41+
return response, err
42+
}
43+
44+
// Outgoing Response Time
45+
outgoingRspTime := time.Now().UTC()
46+
47+
// Skip capture outgoing event
48+
shouldSkipOutgoing := false
49+
if _, found := moesifOption["Should_Skip_Outgoing"]; found {
50+
shouldSkipOutgoing = moesifOption["Should_Skip_Outgoing"].(func(*http.Request, *http.Response) bool)(request, response)
51+
}
52+
53+
// Skip / Send event to moesif
54+
if shouldSkipOutgoing {
55+
if debug {
56+
log.Printf("Skip sending the outgoing event to Moesif")
57+
}
58+
} else {
59+
60+
// Check if the event is to Moesif
61+
if !(strings.Contains(request.URL.String(), "moesif.net")) {
62+
63+
if debug {
64+
log.Printf("Sending the outgoing event to Moesif")
65+
}
66+
67+
// Get Request Body
68+
var (
69+
outgoingReqBody interface{}
70+
reqEncoding string
71+
reqContentLength *int64
72+
)
73+
74+
if logBodyOutgoing && request.Body != nil {
75+
copyBody, err := request.GetBody()
76+
if err != nil {
77+
if debug {
78+
log.Printf("Error while getting the outgoing request body: %s.\n", err.Error())
79+
}
80+
}
81+
82+
// Read the request body
83+
readReqBody, reqBodyErr := ioutil.ReadAll(copyBody)
84+
if reqBodyErr != nil {
85+
if debug {
86+
log.Printf("Error while reading outgoing request body: %s.\n", reqBodyErr.Error())
87+
}
88+
}
89+
reqContentLength = getContentLength(request.Header, readReqBody)
90+
91+
// Parse the request Body
92+
outgoingReqBody, reqEncoding = parseBody(readReqBody, "Request_Body_Masks")
93+
94+
// Return io.ReadCloser while making sure a Close() is available for request body
95+
request.Body = ioutil.NopCloser(bytes.NewBuffer(readReqBody))
96+
97+
}
98+
99+
// Get Response Body
100+
var (
101+
outgoingRespBody interface{}
102+
respEncoding string
103+
respContentLength *int64
104+
)
105+
106+
if logBodyOutgoing && response.Body != nil {
107+
// Read the response body
108+
readRespBody, err := ioutil.ReadAll(response.Body)
109+
if err != nil {
110+
if debug {
111+
log.Printf("Error while reading outgoing response body: %s.\n", err.Error())
112+
}
113+
}
114+
respContentLength = getContentLength(response.Header, readRespBody)
115+
116+
// Parse the response Body
117+
outgoingRespBody, respEncoding = parseBody(readRespBody, "Response_Body_Masks")
118+
119+
// Return io.ReadCloser while making sure a Close() is available for response body
120+
response.Body = ioutil.NopCloser(bytes.NewBuffer(readRespBody))
121+
}
122+
123+
// Get Outgoing Event Metadata
124+
var metadataOutgoing map[string]interface{} = nil
125+
if _, found := moesifOption["Get_Metadata_Outgoing"]; found {
126+
metadataOutgoing = moesifOption["Get_Metadata_Outgoing"].(func(*http.Request, *http.Response) map[string]interface{})(request, response)
127+
}
128+
129+
// Get Outgoing User
130+
userIdOutgoing := getConfigStringValuesForOutgoingEvent("Identify_User_Outgoing", request, response)
131+
132+
// Get Outgoing Company
133+
companyIdOutgoing := getConfigStringValuesForOutgoingEvent("Identify_Company_Outgoing", request, response)
134+
135+
// Get Outgoing Session Token
136+
sessionTokenOutgoing := getConfigStringValuesForOutgoingEvent("Get_Session_Token_Outgoing", request, response)
137+
138+
direction := "Outgoing"
139+
140+
// Mask Request Header
141+
var requestHeader map[string]interface{}
142+
requestHeader = maskHeaders(HeaderToMap(request.Header), "Request_Header_Masks")
143+
144+
// Mask Response Header
145+
var responseHeader map[string]interface{}
146+
responseHeader = maskHeaders(HeaderToMap(response.Header), "Response_Header_Masks")
147+
148+
// Send Event To Moesif
149+
sendMoesifAsync(request, outgoingReqTime, requestHeader, nil, outgoingReqBody, &reqEncoding, reqContentLength,
150+
outgoingRspTime, response.StatusCode, responseHeader, outgoingRespBody, &respEncoding, respContentLength,
151+
userIdOutgoing, companyIdOutgoing, &sessionTokenOutgoing, metadataOutgoing, &direction)
152+
153+
} else {
154+
if debug {
155+
log.Println("Request Skipped since it is Moesif Event")
156+
}
157+
}
158+
}
159+
160+
return response, err
161+
}
162+
163+
func (t *Transport) transport() http.RoundTripper {
164+
if t.Transport != nil {
165+
return t.Transport
166+
}
167+
168+
return http.DefaultTransport
169+
}

0 commit comments

Comments
 (0)