commit 178a583e76852a9c8aa02e146176bb752f3cdcda Author: george Date: Wed May 18 19:51:09 2022 +0100 init diff --git a/cmd/main.go b/cmd/main.go new file mode 100755 index 0000000..83f1350 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "os" + testclient "testclient/internal" +) + +func main() { + if err := testclient.InitHttpClient(); err != nil { + fmt.Fprintln(os.Stderr, "error initialising http client", err.Error()) + os.Exit(1) + } + if err := testclient.Initialise(); err != nil { + fmt.Fprintln(os.Stderr, "Error setting up tester:", err.Error()) + os.Exit(1) + } + testclient.Register() + testclient.WsConnect() + + if testclient.LoadMessages { + testclient.LoadDefaultMessages() + } + + fmt.Println("username:", testclient.Username) + + for { + select { + case <-testclient.Done: + fmt.Println("websocket connection closed") + return + case l := <-testclient.Line: + testclient.SendMessage(l) + } + } +} diff --git a/go.mod b/go.mod new file mode 100755 index 0000000..277472d --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module testclient + +go 1.18 + +require github.com/gorilla/websocket v1.5.0 diff --git a/go.sum b/go.sum new file mode 100755 index 0000000..e5a03d4 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/internal/config.go b/internal/config.go new file mode 100755 index 0000000..19f2b4b --- /dev/null +++ b/internal/config.go @@ -0,0 +1,151 @@ +package testclient + +import ( + "bufio" + "flag" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "os" + "time" +) + +var chatAddress = "localhost:8000" +var loginAddress = "localhost:8001" +var rate = 1000 +var wordPerMessage = 3 +var refresh = 240 +var listen bool +var autoSend bool +var stdinSend bool +var LoadMessages bool +var debug bool +var serverName string +var test bool + +var wordsSource = "https://gist.githubusercontent.com/deekayen/4148741/raw/98d35708fa344717d8eee15d11987de6c8e26d7d/1-1000.txt" +var wordsList []string +var rng *rand.Rand + +var Username string +var token string +var session string + +var Done chan (struct{}) +var Line chan string + +func debugPrint(args ...any) { + if debug { + fmt.Print("DEBUG: ") + fmt.Println(args...) + fmt.Println() // spacer + } +} + +func Initialise() error { + parseArgs() + + if test { + listen, autoSend, LoadMessages, debug = true, true, true, true + } + + if list, err := buildWordsList(wordsSource); err != nil { + return err + } else { + wordsList = list + } + + rng = rand.New(rand.NewSource(time.Now().Unix())) + generateUsername() + + if stdinSend { + go sendFromStdin() + } + + go manageToken() + return nil +} + +func generateUsername() { + Username = wordsList[rng.Intn(len(wordsList))] + "_" + wordsList[rng.Intn(len(wordsList))] +} + +func parseArgs() { + flag.StringVar(&chatAddress, "chataddr", chatAddress, "address for chat server") + flag.StringVar(&loginAddress, "loginaddr", loginAddress, "address for login server") + flag.StringVar(&serverName, "server", serverName, "server name (blank for main)") + flag.IntVar(&rate, "rate", rate, "time in ms between messages") + flag.IntVar(&wordPerMessage, "wpm", wordPerMessage, "words per message") + flag.BoolVar(&listen, "listen", listen, "listen and echo every message received") + flag.BoolVar(&autoSend, "autosend", autoSend, "auto generate and send messages of length and rate specified") + flag.BoolVar(&LoadMessages, "load", LoadMessages, "load most recent messages when connecting to chat") + flag.BoolVar(&debug, "debug", debug, "print additional debug messages e.g. http responses, auth token") + flag.IntVar(&refresh, "refresh", refresh, "auth token refresh rate in seconds") + flag.BoolVar(&stdinSend, "send", stdinSend, "send messages from CLI") + flag.BoolVar(&test, "test", test, "test all functionality (implies -listen, -autosend, -load, -debug)") + + flag.Parse() +} + +func createMessage() (message string) { + for i := 0; i < wordPerMessage; i++ { + message += wordsList[rng.Intn(len(wordsList))] + " " + } + return +} + +func buildWordsList(url string) (wordsList []string, err error) { + res, err := http.Get(url) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + return + } + wordsList = make([]string, 0) + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Fprintln(os.Stderr, "error reading response body:", err) + } + + var word []byte + for _, char := range body { + if char == '\r' { + continue + } + if char == '\n' { + wordsList = append(wordsList, string(word)) + word = make([]byte, 0) + } else { + word = append(word, char) + } + } + return +} + +func sendFromStdin() { + defer func() { + fmt.Println("stopped reading from stdin") + }() + + Line = make(chan string) + + reader := bufio.NewReader(os.Stdin) + + for { + l, _, err := reader.ReadLine() + if err != nil { + fmt.Println(err.Error()) + return + } + Line <- string(l) + } +} + +func manageToken() { + ticker := time.NewTicker(time.Second * time.Duration(refresh)) + + for { + <-ticker.C + refreshToken() + } +} diff --git a/internal/requests.go b/internal/requests.go new file mode 100755 index 0000000..da7e0e2 --- /dev/null +++ b/internal/requests.go @@ -0,0 +1,200 @@ +package testclient + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/url" + "os" + "time" + + "github.com/gorilla/websocket" +) + +type TokenObj struct { + Token string `json:"session"` +} +type SessionObj struct { + Session string `json:"session"` +} + +var client *http.Client +var conn *websocket.Conn + +func InitHttpClient() (err error) { + jar, err := cookiejar.New(nil) + if err != nil { + fmt.Fprintln(os.Stderr, "error initialising cookie jar", err.Error()) + return err + } + + client = &http.Client{ + Jar: jar, + } + return +} + +func Register() error { + postBody := []byte(`{"username":"` + Username + `","password":"Password1!"}`) + req, err := http.NewRequest("POST", "http://"+loginAddress+"/register", bytes.NewBuffer(postBody)) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + return err + } + res, err := client.Do(req) + if err != nil { + fmt.Fprintln(os.Stderr, "could not reach login server", err.Error()) + os.Exit(1) + } + if res.StatusCode != http.StatusCreated { + fmt.Fprintln(os.Stderr, "Status code", res.StatusCode, "received") + os.Exit(1) + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Fprintln(os.Stderr, "error reading register body:", err) + os.Exit(1) + } + + debugPrint("sent ", string(postBody), "received", res.StatusCode, "from", req.URL, "with response body", string(body)) + + type TokenObj struct { + Token string `json:"token"` + } + var tokenObj TokenObj + + if err := json.Unmarshal(body, &tokenObj); err != nil { + fmt.Fprintln(os.Stderr, "error unmarshalling token:", err.Error()) + return err + } + + token = tokenObj.Token + + debugPrint("initial token set to", token) + + return nil +} + +func WsConnect() { + u := url.URL{Scheme: "ws", Host: chatAddress, Path: "/connect", RawQuery: "name=" + serverName} + var err error + + header := http.Header{} + header.Set("Token", token) + + conn, _, err = websocket.DefaultDialer.Dial(u.String(), header) + if err != nil { + fmt.Fprintln(os.Stderr, "error connecting to ws: ", err.Error()) + return + } + Done = make(chan struct{}) + + debugPrint("connected to websocket server", serverName, "(blank for root) at", u.String()) + + go func() { + defer conn.Close() + for { + _, mess, err := conn.ReadMessage() + if err != nil { + fmt.Println(err) + Done <- struct{}{} + return + } + if listen { + fmt.Println(string(mess)) + } + } + }() + + if autoSend { + go func() { + t := time.NewTicker(time.Millisecond * time.Duration(rate)) + for { + <-t.C + SendMessage(createMessage()) + } + }() + } +} + +func SendMessage(message string) { + mess := []byte(`{"text":"` + message + `","user":"` + token + `"}`) + + req, err := http.NewRequest("POST", "http://"+chatAddress+"/send?name="+serverName, bytes.NewBuffer(mess)) + if err != nil { + fmt.Fprintln(os.Stderr, "creating post request error:", err.Error()) + os.Exit(1) + } + req.Header.Set("Token", token) + res, err := client.Do(req) + if err != nil { + fmt.Fprintln(os.Stderr, "sending post request error:", err.Error()) + } + if res.StatusCode != http.StatusOK { + fmt.Fprintln(os.Stderr, "received", res.StatusCode, "when sending message") + } + + debugPrint("sent", message, "received", res.StatusCode) +} + +func LoadDefaultMessages() { + req, err := http.NewRequest("GET", "http://"+chatAddress+"/messages?name="+serverName, nil) + if err != nil { + fmt.Fprintln(os.Stderr, "creating get request error:", err.Error()) + os.Exit(1) + } + req.Header.Set("Token", token) + res, err := client.Do(req) + if err != nil { + fmt.Fprintln(os.Stderr, "sending get request error:", err.Error()) + return + } + if res.StatusCode != http.StatusOK { + fmt.Fprintln(os.Stderr, "received", res.StatusCode, "when loading message") + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Fprintln(os.Stderr, "error reading response body:", err) + } + + debugPrint("load messages: status", res.StatusCode, "body:", string(body)) +} + +func refreshToken() { + req, err := http.NewRequest("GET", "http://"+loginAddress+"/auth", nil) + if err != nil { + fmt.Fprintln(os.Stderr, "creating get request error:", err.Error()) + os.Exit(1) + } + res, err := client.Do(req) + if err != nil { + fmt.Fprintln(os.Stderr, "could not reach login server", err.Error()) + os.Exit(1) + } + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Fprintln(os.Stderr, "error reading auth response body:", err) + } + + if res.StatusCode != http.StatusOK { + fmt.Fprintln(os.Stderr, "/auth received status code", res.StatusCode, "with body", string(body)) + } + + type TokenObj struct { + Token string `json:"token"` + } + var tokenObj TokenObj + + if err := json.Unmarshal(body, &tokenObj); err != nil { + fmt.Fprintln(os.Stderr, "error unmarshalling token:", err.Error()) + return + } + + token = tokenObj.Token + debugPrint("refreshing token to", token) +}