summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--http-client/client.go106
-rw-r--r--http-client/go.mod5
-rw-r--r--http-client/main.go155
-rw-r--r--http-server/main.go48
4 files changed, 310 insertions, 4 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()
+}
diff --git a/http-server/main.go b/http-server/main.go
index e2402b2..f77c32f 100644
--- a/http-server/main.go
+++ b/http-server/main.go
@@ -14,6 +14,8 @@ import (
"time"
)
+const TimestampFormat = "2006-01-02T15-01-05.999"
+
type Response struct {
Message string
}
@@ -157,8 +159,10 @@ func handleAuthentication(req Request, signature string) (error, error) {
req64 := base64.StdEncoding.EncodeToString(reqBytes)
h := sha256.Sum256([]byte(req64))
generatedSignature := fmt.Sprintf("%x", h)
- fmt.Println(generatedSignature == signature)
- fmt.Println(key)
+ //fmt.Println(generatedSignature)
+ //fmt.Println(signature)
+ _ = fmt.Sprint(generatedSignature == signature)
+ _ = fmt.Sprint(key)
return nil, nil
}
@@ -190,9 +194,11 @@ func sendMessage(w http.ResponseWriter, r *http.Request) {
msg := Message{
User: req.User,
Data: req.Data,
- Timestamp: time.Now().Unix(),
+ Timestamp: time.Now().UnixNano(),
}
+ fmt.Printf("Got message from %s: %s\n", req.User, req.Data)
+
err = db.saveMessage(msg)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
@@ -242,7 +248,11 @@ func pollMessages(w http.ResponseWriter, r *http.Request) {
messages, err := db.getMessagesSince(timestamp)
if err != nil {
- _ = serverError(w)
+ w.WriteHeader(http.StatusBadRequest)
+ _ = jsonResponse(w, Response{
+ Message: fmt.Sprint(err),
+ })
+ //_ = serverError(w)
return
}
@@ -296,6 +306,35 @@ func getUserKey(w http.ResponseWriter, r *http.Request) {
})
}
+func tryAuth(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ _ = methodNotAllowedResponse(w)
+ return
+ }
+
+ req, signature, err := parseRequest(w, r)
+ if err != nil {
+ return
+ }
+
+ authErr, dbErr := handleAuthentication(req, signature)
+ if authErr != nil {
+ w.WriteHeader(http.StatusForbidden)
+ _ = jsonResponse(w, Response{
+ Message: "Запрос не прошёл аутентификацию",
+ })
+ return
+ }
+ if dbErr != nil {
+ _ = serverError(w)
+ return
+ }
+
+ _ = jsonResponse(w, Response{
+ Message: "Запрос прошёл аутентификацию",
+ })
+}
+
var db SQLConnection
func main() {
@@ -309,6 +348,7 @@ func main() {
http.HandleFunc("/api/sendMessage", sendMessage)
http.HandleFunc("/api/pollMessages", pollMessages)
http.HandleFunc("/api/getUserKey", getUserKey)
+ http.HandleFunc("/api/tryAuth", tryAuth)
err = http.ListenAndServe("localhost:8080", nil)
if err != nil {
fmt.Println(err)