Browse Source
* add public func to lookup a ChatClient by its clientId * add facility to send a system message directly to a user * add clientId field to UserEvent * implement simple http endpoint to send a message to a user * let mux handle new directSystemMessageToUser endpoint * add ClientId to UserEvents across the codebase * render body of system-message to client * add clientId to Chat-Message * add tests showing how url-parsing should work * add simple rest endpoint helpers for parameter-parsing and easy routing * use newly added rest-endpoint helper to rout to Client-Messaging controller * use safe "ReadRestUrlParameter" to parse ClientId * remove empty HandleFunc in router * set Header directly to prevent built-in (platform-dependent) canonicalization to kick in * fix typo in "Parameter" message * remove debug-logging of HTTP headers in REST-helpers * convert to uint32 to prevent overruns when converting to wraptype uint later on * resolve linter-ouchies * resolve linter potential nil-deref warning * document the SendSystemMessageToClient endpoint in swaggerdoc * remove clientId assignment causing potential nil dereference in userDisabledEvent-case as the clientId isn't relevant here anyway * make findClientById private, so its not accessible outside of core/chat * remove redundant string type hint * Update PR based on linter requirements Co-authored-by: Raffael Rehberger <raffael@rtrace.io> Co-authored-by: Gabe Kangas <gabek@real-ity.com>pull/1399/head
11 changed files with 211 additions and 0 deletions
@ -0,0 +1,69 @@ |
|||||||
|
package utils |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"net/http" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
const restURLPatternHeaderKey = "Owncast-Resturl-Pattern" |
||||||
|
|
||||||
|
// takes the segment pattern of an Url string and returns the segment before the first dynamic REST parameter.
|
||||||
|
func getPatternForRestEndpoint(pattern string) string { |
||||||
|
firstIndex := strings.Index(pattern, "/{") |
||||||
|
if firstIndex == -1 { |
||||||
|
return pattern |
||||||
|
} |
||||||
|
|
||||||
|
return strings.TrimRight(pattern[:firstIndex], "/") + "/" |
||||||
|
} |
||||||
|
|
||||||
|
func zip2D(iterable1 *[]string, iterable2 *[]string) map[string]string { |
||||||
|
var dict = make(map[string]string) |
||||||
|
for index, key := range *iterable1 { |
||||||
|
dict[key] = (*iterable2)[index] |
||||||
|
} |
||||||
|
return dict |
||||||
|
} |
||||||
|
|
||||||
|
func mapPatternWithRequestURL(pattern string, requestURL string) (map[string]string, error) { |
||||||
|
patternSplit := strings.Split(pattern, "/") |
||||||
|
requestURLSplit := strings.Split(requestURL, "/") |
||||||
|
|
||||||
|
if len(patternSplit) == len(requestURLSplit) { |
||||||
|
return zip2D(&patternSplit, &requestURLSplit), nil |
||||||
|
} |
||||||
|
return nil, errors.New("The length of pattern and request Url does not match") |
||||||
|
} |
||||||
|
|
||||||
|
func readParameter(pattern string, requestURL string, paramName string) (string, error) { |
||||||
|
all, err := mapPatternWithRequestURL(pattern, requestURL) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
if value, exists := all[fmt.Sprintf("{%s}", paramName)]; exists { |
||||||
|
return value, nil |
||||||
|
} |
||||||
|
return "", fmt.Errorf("Parameter with name %s not found", paramName) |
||||||
|
} |
||||||
|
|
||||||
|
// ReadRestURLParameter will return the parameter from the request of the requested name.
|
||||||
|
func ReadRestURLParameter(r *http.Request, parameterName string) (string, error) { |
||||||
|
pattern, found := r.Header[restURLPatternHeaderKey] |
||||||
|
if !found { |
||||||
|
return "", fmt.Errorf("This HandlerFunc is not marked as REST-Endpoint. Cannot read Parameter '%s' from Request", parameterName) |
||||||
|
} |
||||||
|
|
||||||
|
return readParameter(pattern[0], r.URL.Path, parameterName) |
||||||
|
} |
||||||
|
|
||||||
|
// RestEndpoint wraps a handler to use the rest endpoint helper.
|
||||||
|
func RestEndpoint(pattern string, handler http.HandlerFunc) (string, http.HandlerFunc) { |
||||||
|
baseURL := getPatternForRestEndpoint(pattern) |
||||||
|
return baseURL, func(w http.ResponseWriter, r *http.Request) { |
||||||
|
r.Header[restURLPatternHeaderKey] = []string{pattern} |
||||||
|
handler(w, r) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,39 @@ |
|||||||
|
package utils |
||||||
|
|
||||||
|
import ( |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestGetPatternForRestEndpoint(t *testing.T) { |
||||||
|
expected := "/hello/" |
||||||
|
endpoints := [...]string{"/hello/{param1}", "/hello/{param1}/{param2}", "/hello/{param1}/world/{param2}"} |
||||||
|
for _, endpoint := range endpoints { |
||||||
|
if ep := getPatternForRestEndpoint(endpoint); ep != expected { |
||||||
|
t.Errorf("%s p does not match expected %s", ep, expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestReadParameter(t *testing.T) { |
||||||
|
expected := "world" |
||||||
|
endpoints := [...]string{ |
||||||
|
"/hello/{p1}", |
||||||
|
"/hello/cruel/{p1}", |
||||||
|
"/hello/{p1}/my/friend", |
||||||
|
"/hello/{p1}/{p2}/friend", |
||||||
|
"/hello/{p2}/{p3}/{p1}", |
||||||
|
"/{p1}/is/nice", |
||||||
|
"/{p1}/{p1}/{p1}", |
||||||
|
} |
||||||
|
|
||||||
|
for _, ep := range endpoints { |
||||||
|
v, err := readParameter(ep, strings.Replace(ep, "{p1}", expected, -1), "p1") |
||||||
|
if err != nil { |
||||||
|
t.Errorf("Unexpected error when reading parameter: %s", err.Error()) |
||||||
|
} |
||||||
|
if v != expected { |
||||||
|
t.Errorf("'%s' should have returned %s", ep, expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue