MM-53023: Add origin client to ObserveAPIEndpointDuration (#23631)
* Add origin device to ObserveAPIEndpointDuration * Fix generation of einterfaces mocks * make einterfaces-mocks * Use request's query and headers to get origin * Add desktop to the origin device identification * Test originDevice function * Rename origin device to origin client
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
1fe2295c70
Коммит
2bc99398f9
@@ -407,11 +407,54 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if r.URL.Path != model.APIURLSuffix+"/websocket" {
|
if r.URL.Path != model.APIURLSuffix+"/websocket" {
|
||||||
elapsed := float64(time.Since(now)) / float64(time.Second)
|
elapsed := float64(time.Since(now)) / float64(time.Second)
|
||||||
c.App.Metrics().ObserveAPIEndpointDuration(h.HandlerName, r.Method, statusCode, elapsed)
|
originClient := string(originClient(r))
|
||||||
|
c.App.Metrics().ObserveAPIEndpointDuration(h.HandlerName, r.Method, statusCode, originClient, elapsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OriginClient string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OriginClientUnknown OriginClient = "unknown"
|
||||||
|
OriginClientWeb OriginClient = "web"
|
||||||
|
OriginClientMobile OriginClient = "mobile"
|
||||||
|
OriginClientDesktop OriginClient = "desktop"
|
||||||
|
)
|
||||||
|
|
||||||
|
// originClient returns the device from which the provided request was issued. The algorithm roughly looks like:
|
||||||
|
// - If the URL contains the query mobilev2=true, then it's mobile
|
||||||
|
// - If the first field of the user agent starts with either "rnbeta" or "Mattermost", then it's mobile
|
||||||
|
// - If the last field of the user agent starts with "Mattermost", then it's desktop
|
||||||
|
// - Otherwise, it's web
|
||||||
|
func originClient(r *http.Request) OriginClient {
|
||||||
|
userAgent := r.Header.Get("User-Agent")
|
||||||
|
fields := strings.Fields(userAgent)
|
||||||
|
if len(fields) < 1 {
|
||||||
|
return OriginClientUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is mobile post v2?
|
||||||
|
queryParam := r.URL.Query().Get("mobilev2")
|
||||||
|
if queryParam == "true" {
|
||||||
|
return OriginClientMobile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is mobile pre v2?
|
||||||
|
clientAgent := fields[0]
|
||||||
|
if strings.HasPrefix(clientAgent, "rnbeta") || strings.HasPrefix(clientAgent, "Mattermost") {
|
||||||
|
return OriginClientMobile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is desktop?
|
||||||
|
if strings.HasPrefix(fields[len(fields)-1], "Mattermost") {
|
||||||
|
return OriginClientDesktop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to web
|
||||||
|
return OriginClientWeb
|
||||||
|
}
|
||||||
|
|
||||||
// checkCSRFToken performs a CSRF check on the provided request with the given CSRF token. Returns whether or not
|
// checkCSRFToken performs a CSRF check on the provided request with the given CSRF token. Returns whether or not
|
||||||
// a CSRF check occurred and whether or not it succeeded.
|
// a CSRF check occurred and whether or not it succeeded.
|
||||||
func (h *Handler) checkCSRFToken(c *Context, r *http.Request, token string, tokenLocation app.TokenLocation, session *model.Session) (checked bool, passed bool) {
|
func (h *Handler) checkCSRFToken(c *Context, r *http.Request, token string, tokenLocation app.TokenLocation, session *model.Session) (checked bool, passed bool) {
|
||||||
|
|||||||
@@ -882,3 +882,71 @@ func TestCheckCSRFToken(t *testing.T) {
|
|||||||
assert.Nil(t, c.Err)
|
assert.Nil(t, c.Err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOriginClient(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
userAgent string
|
||||||
|
mobilev2 bool
|
||||||
|
expectedClient OriginClient
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "No user agent - unknown client",
|
||||||
|
userAgent: "",
|
||||||
|
expectedClient: OriginClientUnknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mozilla user agent",
|
||||||
|
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0",
|
||||||
|
expectedClient: OriginClientWeb,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Chrome user agent",
|
||||||
|
userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
||||||
|
expectedClient: OriginClientWeb,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mobile post v2",
|
||||||
|
userAgent: "someother-agent/3.2.4",
|
||||||
|
mobilev2: true,
|
||||||
|
expectedClient: OriginClientMobile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mobile Android",
|
||||||
|
userAgent: "rnbeta/2.0.0.441 someother-agent/3.2.4",
|
||||||
|
expectedClient: OriginClientMobile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mobile iOS",
|
||||||
|
userAgent: "Mattermost/2.0.0.441 someother-agent/3.2.4",
|
||||||
|
expectedClient: OriginClientMobile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Desktop user agent",
|
||||||
|
userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.177 Electron/23.1.2 Safari/537.36 Mattermost/5.3.1",
|
||||||
|
expectedClient: OriginClientDesktop,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "example.com", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Set User-Agent header, if any
|
||||||
|
if tc.userAgent != "" {
|
||||||
|
req.Header.Set("User-Agent", tc.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set mobilev2 query if needed
|
||||||
|
if tc.mobilev2 {
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("mobilev2", "true")
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute origin client
|
||||||
|
actualClient := originClient(req)
|
||||||
|
|
||||||
|
require.Equal(t, tc.expectedClient, actualClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ type MetricsInterface interface {
|
|||||||
IncrementFilesSearchCounter()
|
IncrementFilesSearchCounter()
|
||||||
ObserveFilesSearchDuration(elapsed float64)
|
ObserveFilesSearchDuration(elapsed float64)
|
||||||
ObserveStoreMethodDuration(method, success string, elapsed float64)
|
ObserveStoreMethodDuration(method, success string, elapsed float64)
|
||||||
ObserveAPIEndpointDuration(endpoint, method, statusCode string, elapsed float64)
|
ObserveAPIEndpointDuration(endpoint, method, statusCode, originClient string, elapsed float64)
|
||||||
IncrementPostIndexCounter()
|
IncrementPostIndexCounter()
|
||||||
IncrementFileIndexCounter()
|
IncrementFileIndexCounter()
|
||||||
IncrementUserIndexCounter()
|
IncrementUserIndexCounter()
|
||||||
|
|||||||
@@ -239,9 +239,9 @@ func (_m *MetricsInterface) IncrementWebsocketReconnectEvent(eventType string) {
|
|||||||
_m.Called(eventType)
|
_m.Called(eventType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObserveAPIEndpointDuration provides a mock function with given fields: endpoint, method, statusCode, elapsed
|
// ObserveAPIEndpointDuration provides a mock function with given fields: endpoint, method, statusCode, originClient, elapsed
|
||||||
func (_m *MetricsInterface) ObserveAPIEndpointDuration(endpoint string, method string, statusCode string, elapsed float64) {
|
func (_m *MetricsInterface) ObserveAPIEndpointDuration(endpoint string, method string, statusCode string, originClient string, elapsed float64) {
|
||||||
_m.Called(endpoint, method, statusCode, elapsed)
|
_m.Called(endpoint, method, statusCode, originClient, elapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObserveClusterRequestDuration provides a mock function with given fields: elapsed
|
// ObserveClusterRequestDuration provides a mock function with given fields: elapsed
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user