-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#188398853 Initial commit of Gin framework Moesif middleware (#1)
* Initial commit of Gin framework Moesif middleware * Code cleanup * Readme for Gin SDK
- Loading branch information
Showing
11 changed files
with
2,344 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package moesifgin | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"log" | ||
"sync" | ||
) | ||
|
||
type AppConfig struct { | ||
Mu sync.RWMutex | ||
Updates chan string | ||
eTags [2]string | ||
config AppConfigResponse | ||
} | ||
|
||
func NewAppConfig() AppConfig { | ||
return AppConfig{ | ||
Updates: make(chan string, 1), | ||
config: NewAppConfigResponse(), | ||
} | ||
} | ||
|
||
func (c *AppConfig) Read() AppConfigResponse { | ||
c.Mu.RLock() | ||
defer c.Mu.RUnlock() | ||
return c.config | ||
} | ||
|
||
func (c *AppConfig) Write(config AppConfigResponse) { | ||
c.Mu.Lock() | ||
defer c.Mu.Unlock() | ||
c.config = config | ||
c.eTags[1] = c.eTags[0] | ||
c.eTags[0] = config.eTag | ||
} | ||
|
||
func (c *AppConfig) Go() { | ||
go c.UpdateLoop() | ||
c.Notify("go") | ||
} | ||
|
||
func (c *AppConfig) Notify(eTag string) { | ||
c.Mu.RLock() | ||
e := c.eTags | ||
c.Mu.RUnlock() | ||
if eTag == "" || eTag == e[0] || eTag == e[1] { | ||
return | ||
} | ||
select { | ||
case c.Updates <- eTag: | ||
default: | ||
} | ||
} | ||
|
||
func (c *AppConfig) UpdateLoop() { | ||
for { | ||
eTag, more := <-c.Updates | ||
if !more { | ||
return | ||
} | ||
config, err := getAppConfig() | ||
if err != nil { | ||
log.Printf("Failed to get config: %v", err) | ||
continue | ||
} | ||
log.Printf("AppConfig.Notify ETag=%s got /config response ETag=%s", eTag, config.eTag) | ||
c.Write(config) | ||
} | ||
} | ||
|
||
func (c *AppConfig) GetEntityValues(userId, companyId string) (userValues, companyValues []EntityRuleValues) { | ||
config := c.Read() | ||
return config.UserRules[userId], config.CompanyRules[companyId] | ||
} | ||
|
||
type AppConfigResponse struct { | ||
OrgID string `json:"org_id"` | ||
AppID string `json:"app_id"` | ||
SampleRate int `json:"sample_rate"` | ||
BlockBotTraffic bool `json:"block_bot_traffic"` | ||
UserSampleRate map[string]int `json:"user_sample_rate"` // user id to a sample rate [0, 100] | ||
CompanySampleRate map[string]int `json:"company_sample_rate"` // company id to a sample rate [0, 100] | ||
UserRules map[string][]EntityRuleValues `json:"user_rules"` // user id to a rule id and template values | ||
CompanyRules map[string][]EntityRuleValues `json:"company_rules"` // company id to a rule id and template values | ||
IPAddressesBlockedByName map[string]string `json:"ip_addresses_blocked_by_name"` | ||
RegexConfig []RegexRule `json:"regex_config"` | ||
BillingConfigJsons map[string]string `json:"billing_config_jsons"` | ||
eTag string | ||
} | ||
|
||
func NewAppConfigResponse() AppConfigResponse { | ||
return AppConfigResponse{ | ||
SampleRate: 100, | ||
} | ||
} | ||
|
||
// EntityRule is a user rule or company rule | ||
type EntityRuleValues struct { | ||
Rule string `json:"rules"` | ||
Values map[string]string `json:"values"` | ||
} | ||
|
||
// Regex Rule | ||
type RegexRule struct { | ||
Conditions []RegexCondition `json:"conditions"` | ||
SampleRate int `json:"sample_rate"` | ||
} | ||
|
||
// RegexCondition | ||
type RegexCondition struct { | ||
Path string `json:"path"` | ||
Value string `json:"value"` | ||
} | ||
|
||
func getAppConfig() (config AppConfigResponse, err error) { | ||
config = NewAppConfigResponse() | ||
r, err := apiClient.GetAppConfig() | ||
if err != nil { | ||
log.Printf("Application configuration request error: %v", err) | ||
return | ||
} | ||
body, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | ||
log.Printf("Application configuration response body read error: %v", err) | ||
return | ||
} | ||
err = json.Unmarshal(body, &config) | ||
if err != nil { | ||
log.Printf("Application configuration response body malformed: %v", err) | ||
return | ||
} | ||
config.eTag = r.Header.Get("X-Moesif-Config-Etag") | ||
return | ||
} | ||
|
||
func getSamplingPercentage(userId string, companyId string) int { | ||
c := appConfig.Read() | ||
if userId != "" { | ||
if userRate, ok := c.UserSampleRate[userId]; ok { | ||
return userRate | ||
} | ||
} | ||
|
||
if companyId != "" { | ||
if companyRate, ok := c.CompanySampleRate[companyId]; ok { | ||
return companyRate | ||
} | ||
} | ||
|
||
return c.SampleRate | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package moesifgin | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"strings" | ||
"time" | ||
) | ||
|
||
// Transport implements http.RoundTripper. | ||
type Transport struct { | ||
Transport http.RoundTripper | ||
LogRequest func(req *http.Request) | ||
LogResponse func(resp *http.Response) | ||
} | ||
|
||
// The default logging transport that wraps http.DefaultTransport. | ||
var DefaultTransport = &Transport{ | ||
Transport: http.DefaultTransport, | ||
} | ||
|
||
type contextKey struct { | ||
name string | ||
} | ||
|
||
var ContextKeyRequestStart = &contextKey{"RequestStart"} | ||
|
||
// RoundTrip is the core part of this module and implements http.RoundTripper. | ||
func (t *Transport) RoundTrip(request *http.Request) (*http.Response, error) { | ||
ctx := context.WithValue(request.Context(), ContextKeyRequestStart, time.Now()) | ||
request = request.WithContext(ctx) | ||
|
||
// Outgoing Request Time | ||
outgoingReqTime := time.Now().UTC() | ||
|
||
response, err := t.transport().RoundTrip(request) | ||
if err != nil { | ||
return response, err | ||
} | ||
|
||
// Outgoing Response Time | ||
outgoingRspTime := time.Now().UTC() | ||
|
||
// Skip capture outgoing event | ||
shouldSkipOutgoing := false | ||
if _, found := moesifOption["Should_Skip_Outgoing"]; found { | ||
shouldSkipOutgoing = moesifOption["Should_Skip_Outgoing"].(func(*http.Request, *http.Response) bool)(request, response) | ||
} | ||
|
||
// Skip / Send event to moesif | ||
if shouldSkipOutgoing { | ||
if debug { | ||
log.Printf("Skip sending the outgoing event to Moesif") | ||
} | ||
} else { | ||
|
||
// Check if the event is to Moesif | ||
if !(strings.Contains(request.URL.String(), "moesif.net")) { | ||
|
||
if debug { | ||
log.Printf("Sending the outgoing event to Moesif") | ||
} | ||
|
||
// Get Request Body | ||
var ( | ||
outgoingReqBody interface{} | ||
reqEncoding string | ||
reqContentLength *int64 | ||
) | ||
|
||
if logBodyOutgoing && request.Body != nil { | ||
copyBody, err := request.GetBody() | ||
if err != nil { | ||
if debug { | ||
log.Printf("Error while getting the outgoing request body: %s.\n", err.Error()) | ||
} | ||
} | ||
|
||
// Read the request body | ||
readReqBody, reqBodyErr := ioutil.ReadAll(copyBody) | ||
if reqBodyErr != nil { | ||
if debug { | ||
log.Printf("Error while reading outgoing request body: %s.\n", reqBodyErr.Error()) | ||
} | ||
} | ||
reqContentLength = getContentLength(request.Header, readReqBody) | ||
|
||
// Parse the request Body | ||
outgoingReqBody, reqEncoding = parseBody(readReqBody, "Request_Body_Masks") | ||
|
||
// Return io.ReadCloser while making sure a Close() is available for request body | ||
request.Body = ioutil.NopCloser(bytes.NewBuffer(readReqBody)) | ||
|
||
} | ||
|
||
// Get Response Body | ||
var ( | ||
outgoingRespBody interface{} | ||
respEncoding string | ||
respContentLength *int64 | ||
) | ||
|
||
if logBodyOutgoing && response.Body != nil { | ||
// Read the response body | ||
readRespBody, err := ioutil.ReadAll(response.Body) | ||
if err != nil { | ||
if debug { | ||
log.Printf("Error while reading outgoing response body: %s.\n", err.Error()) | ||
} | ||
} | ||
respContentLength = getContentLength(response.Header, readRespBody) | ||
|
||
// Parse the response Body | ||
outgoingRespBody, respEncoding = parseBody(readRespBody, "Response_Body_Masks") | ||
|
||
// Return io.ReadCloser while making sure a Close() is available for response body | ||
response.Body = ioutil.NopCloser(bytes.NewBuffer(readRespBody)) | ||
} | ||
|
||
// Get Outgoing Event Metadata | ||
var metadataOutgoing map[string]interface{} = nil | ||
if _, found := moesifOption["Get_Metadata_Outgoing"]; found { | ||
metadataOutgoing = moesifOption["Get_Metadata_Outgoing"].(func(*http.Request, *http.Response) map[string]interface{})(request, response) | ||
} | ||
|
||
// Get Outgoing User | ||
userIdOutgoing := getConfigStringValuesForOutgoingEvent("Identify_User_Outgoing", request, response) | ||
|
||
// Get Outgoing Company | ||
companyIdOutgoing := getConfigStringValuesForOutgoingEvent("Identify_Company_Outgoing", request, response) | ||
|
||
// Get Outgoing Session Token | ||
sessionTokenOutgoing := getConfigStringValuesForOutgoingEvent("Get_Session_Token_Outgoing", request, response) | ||
|
||
direction := "Outgoing" | ||
|
||
// Mask Request Header | ||
var requestHeader map[string]interface{} | ||
requestHeader = maskHeaders(HeaderToMap(request.Header), "Request_Header_Masks") | ||
|
||
// Mask Response Header | ||
var responseHeader map[string]interface{} | ||
responseHeader = maskHeaders(HeaderToMap(response.Header), "Response_Header_Masks") | ||
|
||
// Send Event To Moesif | ||
sendMoesifAsync(request, outgoingReqTime, requestHeader, nil, outgoingReqBody, &reqEncoding, reqContentLength, | ||
outgoingRspTime, response.StatusCode, responseHeader, outgoingRespBody, &respEncoding, respContentLength, | ||
userIdOutgoing, companyIdOutgoing, &sessionTokenOutgoing, metadataOutgoing, &direction) | ||
|
||
} else { | ||
if debug { | ||
log.Println("Request Skipped since it is Moesif Event") | ||
} | ||
} | ||
} | ||
|
||
return response, err | ||
} | ||
|
||
func (t *Transport) transport() http.RoundTripper { | ||
if t.Transport != nil { | ||
return t.Transport | ||
} | ||
|
||
return http.DefaultTransport | ||
} |
Oops, something went wrong.