diff options
Diffstat (limited to 'http-client')
| -rw-r--r-- | http-client/client.go | 106 | ||||
| -rw-r--r-- | http-client/go.mod | 5 | ||||
| -rw-r--r-- | http-client/main.go | 155 |
3 files changed, 266 insertions, 0 deletions
diff --git a/http-client/client.go b/http-client/client.go new file mode 100644 index 0000000..ee3e044 --- /dev/null +++ b/http-client/client.go @@ -0,0 +1,106 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "fyne.io/fyne/v2/data/binding" + "io" + "net/http" + "sync" + "time" +) + +const ( + URL = "http://localhost:8080/api" + Register = "register" + SendMessage = "sendMessage" + PollMessages = "pollMessages" + GetUserKey = "getUserKey" + TryAuth = "tryAuth" +) + +const TimestampFormat = "2006-01-02T15-01-05.999" + +var storage struct { + sync.RWMutex + messages []Message + data []string + binding binding.ExternalStringList +} + +type Request struct { + User string + Data string +} + +type Response struct { + Message string +} + +type Message struct { + User string + Data string + Timestamp string +} + +func (msg *Message) toString() string { + return fmt.Sprintf("%s: %s", msg.User, msg.Data) +} + +func signData(data []byte) string { + data64 := base64.StdEncoding.EncodeToString(data) + h := sha256.Sum256([]byte(data64)) + signature := fmt.Sprintf("%x", h) + return fmt.Sprintf("%s.%s", data64, signature) +} + +func makeRequest(request Request, apiMethod string) (*http.Response, error) { + req, _ := json.Marshal(request) + signedRequest := signData(req) + reader := bytes.NewReader([]byte(signedRequest)) + return http.Post(URL+"/"+apiMethod, "application/json", reader) +} + +func sendMessage(user UserData, message string) { + // TODO(andrew): Добавить отображение ошибки в интерфейс + _, _ = makeRequest(Request{ + User: user.Username, + Data: message, + }, SendMessage) +} + +func runClient(user UserData) { + lastPoll := time.Now().UnixNano() + for { + httpResp, _ := makeRequest(Request{ + User: user.Username, + Data: fmt.Sprint(lastPoll), + }, PollMessages) + lastPoll = time.Now().UnixNano() + + if httpResp.StatusCode == http.StatusOK { + body, _ := io.ReadAll(httpResp.Body) + var resp Response + _ = json.Unmarshal(body, &resp) + var messages []Message + _ = json.Unmarshal([]byte(resp.Message), &messages) + + storage.Lock() + for _, msg := range messages { + fmt.Printf("Polled new message from %s: %s (%s)\n", msg.User, msg.Data, msg.Timestamp) + _ = storage.binding.Append(msg.toString()) + storage.messages = append(storage.messages, msg) + } + storage.Unlock() + } else { + fmt.Println(httpResp.StatusCode) + ae, _ := io.ReadAll(httpResp.Body) + fmt.Println(string(ae)) + } + + time.Sleep(100 * time.Millisecond) + } +} diff --git a/http-client/go.mod b/http-client/go.mod new file mode 100644 index 0000000..a62b9f0 --- /dev/null +++ b/http-client/go.mod @@ -0,0 +1,5 @@ +module vasthecat.ru/coursework/http-client + +go 1.16 + +require fyne.io/fyne/v2 v2.0.2 diff --git a/http-client/main.go b/http-client/main.go new file mode 100644 index 0000000..14419e5 --- /dev/null +++ b/http-client/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + "image/color" +) + +type UserData struct { + Username string +} + +func chatLayout(window fyne.Window, user UserData, c chan string) { + window.SetTitle("Сообщения") + storage.binding = binding.BindStringList(&storage.data) + go runClient(user) + + list := widget.NewListWithData(storage.binding, + func() fyne.CanvasObject { + return widget.NewLabel("template") + }, + func(i binding.DataItem, o fyne.CanvasObject) { + o.(*widget.Label).Bind(i.(binding.String)) + }) + + entry := widget.NewEntry() + submit := widget.NewButton("Отправить", func() { + go sendMessage(user, entry.Text) + entry.SetText("") + + storage.RLock() + lastIdx := storage.binding.Length() - 1 + list.Select(lastIdx) + list.Unselect(lastIdx) + storage.RUnlock() + }) + + window.SetContent(container.NewBorder( + nil, + container.NewBorder(nil, nil, nil, submit, entry), + nil, nil, + list, + )) +} + +func loginLayout(window fyne.Window, c chan string) { + window.SetTitle("Войти в чат") + + label := canvas.NewText("Chat", color.White) + label.TextSize = 40 + + loginLabel := widget.NewLabel("Логин: ") + loginEntry := widget.NewEntry() + + chooseKey := widget.NewButton("Выбрать ключ для подписи", func() { + dialog.ShowFileOpen(func(closer fyne.URIReadCloser, err error) { + if closer == nil { + return + } + bufSize := 4096 + data := make([]byte, 0) + buf := make([]byte, bufSize) + n := bufSize + + for n >= bufSize { + n, _ = closer.Read(buf) + data = append(data, buf...) + } + + _ = closer.Close() + }, window) + }) + + loginButton := widget.NewButton("Войти", func() { + var user = UserData{ + Username: loginEntry.Text, + } + chatLayout(window, user, c) + }) + + back := widget.NewButton("Назад", func() { + startLayout(window, c) + }) + + mainContainer := container.NewCenter(container.NewVBox( + container.NewCenter(label), layout.NewSpacer(), + container.NewBorder(nil, nil, loginLabel, nil, loginEntry), + chooseKey, + loginButton, + back, + )) + window.SetContent(mainContainer) +} + +func registerLayout(window fyne.Window, c chan string) { + window.SetTitle("Регистрация") + + label := canvas.NewText("Chat", color.White) + label.TextSize = 40 + + loginLabel := widget.NewLabel("Логин: ") + loginEntry := widget.NewEntry() + + generateKeys := widget.NewButton("Сгенерировать ключи подписи", func() { + + }) + + registerButton := widget.NewButton("Зарегистрироваться", func() { + + }) + + back := widget.NewButton("Назад", func() { + startLayout(window, c) + }) + + window.SetContent(container.NewCenter(container.NewVBox( + container.NewCenter(label), layout.NewSpacer(), + container.NewBorder(nil, nil, loginLabel, nil, loginEntry), + generateKeys, + registerButton, + back, + ))) +} + +func startLayout(window fyne.Window, c chan string) { + window.SetTitle("Chat") + + label := canvas.NewText("Chat", color.White) + label.TextSize = 40 + login := widget.NewButton("Войти", func() { + loginLayout(window, c) + }) + signup := widget.NewButton("Зарегистрироваться", func() { + registerLayout(window, c) + }) + + mainContainer := container.NewCenter(container.NewVBox( + container.NewCenter(label), layout.NewSpacer(), login, signup)) + window.SetContent(mainContainer) +} + +func main() { + myApp := app.New() + window := myApp.NewWindow("") + channel := make(chan string) + + startLayout(window, channel) + window.ShowAndRun() +} |