Skip to content

Commit

Permalink
#188398853 Initial commit of Gin framework Moesif middleware (#1)
Browse files Browse the repository at this point in the history
* Initial commit of Gin framework Moesif middleware

* Code cleanup

* Readme for Gin SDK
  • Loading branch information
bakennedy authored Oct 18, 2024
1 parent aa7fc88 commit d4506bb
Show file tree
Hide file tree
Showing 11 changed files with 2,344 additions and 1 deletion.
1,034 changes: 1,033 additions & 1 deletion README.md

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions appconfig.go
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
}
169 changes: 169 additions & 0 deletions captureoutgoing.go
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
}
Loading

0 comments on commit d4506bb

Please sign in to comment.