Хочу сделать загрузку аватарки через вебсокеты. Выбираю картинку, нажимаю загрузить, и вместо загружаемой картинки появляется битая пнг. После перезагрузки страницы, картинка появляется. Буду рад помощи Нетворк таб(вместо username+filename, [object file]),: Спойлер https://ibb.co/h7R56Kp Бэк(golang / gin / gorilla websockets ): func UserAvatarWebsocket(c *gin.Context) { // Check origin error upgrader.CheckOrigin = func(r *http.Request) bool { return true } // Upgrade HTTP connection to WebSocket conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Println("Failed to set up WebSocket connection:", err) return } // Read incoming message from client _, msg, err := conn.ReadMessage() if err != nil { log.Println("Failed to read message from client:", err) return } // Get the authenticated user from context using type assertion user, exists := c.Get("user") if !exists { log.Println("Error: User not found in context") conn.WriteMessage(websocket.TextMessage, []byte("Unauthorized")) return } log.Printf("Type of user in context: %T\n", user) // Type assert the user to models.User currentUser, ok := user.(models.User) if !ok { log.Println("Error: Unexpected user type in context") conn.WriteMessage(websocket.TextMessage, []byte("Internal server error")) return } // Parse the message JSON to get the file name var filename struct { Filename string `json:"filename"` } if err := json.Unmarshal(msg, &filename); err != nil { log.Println("Failed to parse filename from message:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to parse filename")) return } // Receive file from client _, file, err := conn.NextReader() if err != nil { log.Println("Failed to receive file from client:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to receive file")) return } // Save the file to a location filePath := filepath.Join("uploads", currentUser.Username+"_"+filename.Filename) if err := saveFile(filePath, file); err != nil { log.Println("Failed to save file:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to save file")) return } // Update the user's avatar in the database currentUser.Avatar = filePath if err := initializers.DB.Save(¤tUser).Error; err != nil { log.Println("Failed to update user in database:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to update avatar")) return } // Send a response back to the client if err := conn.WriteMessage(websocket.TextMessage, []byte("Avatar updated successfully")); err != nil { log.Println("Failed to write message to client:", err) return } // Set cache headers c.Header("Cache-Control", "no-cache, no-store, must-revalidate") c.Header("Pragma", "no-cache") c.Header("Expires", "0") defer conn.Close() } Код func UserAvatarWebsocket(c *gin.Context) { // Check origin error upgrader.CheckOrigin = func(r *http.Request) bool { return true } // Upgrade HTTP connection to WebSocket conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Println("Failed to set up WebSocket connection:", err) return } // Read incoming message from client _, msg, err := conn.ReadMessage() if err != nil { log.Println("Failed to read message from client:", err) return } // Get the authenticated user from context using type assertion user, exists := c.Get("user") if !exists { log.Println("Error: User not found in context") conn.WriteMessage(websocket.TextMessage, []byte("Unauthorized")) return } log.Printf("Type of user in context: %T\n", user) // Type assert the user to models.User currentUser, ok := user.(models.User) if !ok { log.Println("Error: Unexpected user type in context") conn.WriteMessage(websocket.TextMessage, []byte("Internal server error")) return } // Parse the message JSON to get the file name var filename struct { Filename string `json:"filename"` } if err := json.Unmarshal(msg, &filename); err != nil { log.Println("Failed to parse filename from message:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to parse filename")) return } // Receive file from client _, file, err := conn.NextReader() if err != nil { log.Println("Failed to receive file from client:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to receive file")) return } // Save the file to a location filePath := filepath.Join("uploads", currentUser.Username+"_"+filename.Filename) if err := saveFile(filePath, file); err != nil { log.Println("Failed to save file:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to save file")) return } // Update the user's avatar in the database currentUser.Avatar = filePath if err := initializers.DB.Save(¤tUser).Error; err != nil { log.Println("Failed to update user in database:", err) conn.WriteMessage(websocket.TextMessage, []byte("Failed to update avatar")) return } // Send a response back to the client if err := conn.WriteMessage(websocket.TextMessage, []byte("Avatar updated successfully")); err != nil { log.Println("Failed to write message to client:", err) return } // Set cache headers c.Header("Cache-Control", "no-cache, no-store, must-revalidate") c.Header("Pragma", "no-cache") c.Header("Expires", "0") defer conn.Close() } main.go: package main import ( "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "log" "server/controllers" "server/initializers" "server/middleware" "time" ) func init() { initializers.LoadEnvVariables() initializers.ConnectToDb() initializers.SyncDatabase() } func main() { r := gin.Default() r.Static("/uploads", "./uploads") r.ForwardedByClientIP = true err := r.SetTrustedProxies([]string{"127.0.0.1"}) if err != nil { return } r.Use(cors.New(cors.Config{ AllowOrigins: []string{"http://localhost:8000", "http://localhost:5173"}, AllowMethods: []string{"PUT", "PATCH", "DELETE", "GET", "POST", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 24 * time.Hour, })) r.POST("/signup", controllers.SignUp) r.POST("/login", controllers.Login) r.GET("/validate", middleware.RequireAuth, controllers.Validate) r.GET("/logout", middleware.RequireAuth, controllers.Logout) r.PUT("/update-password", middleware.RequireAuth, controllers.UpdatePassword) r.DELETE("/delete-user", middleware.RequireAuth, controllers.DeleteUser) ws := r.Group("/ws") { ws.Use(middleware.RequireAuth) ws.GET("/update-username", controllers.UpdateUsernameWebsocket) ws.GET("/upload-avatar", controllers.UserAvatarWebsocket) } log.Fatal(r.Run()) } Код package main import ( "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "log" "server/controllers" "server/initializers" "server/middleware" "time" ) func init() { initializers.LoadEnvVariables() initializers.ConnectToDb() initializers.SyncDatabase() } func main() { r := gin.Default() r.Static("/uploads", "./uploads") r.ForwardedByClientIP = true err := r.SetTrustedProxies([]string{"127.0.0.1"}) if err != nil { return } r.Use(cors.New(cors.Config{ AllowOrigins: []string{"http://localhost:8000", "http://localhost:5173"}, AllowMethods: []string{"PUT", "PATCH", "DELETE", "GET", "POST", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 24 * time.Hour, })) r.POST("/signup", controllers.SignUp) r.POST("/login", controllers.Login) r.GET("/validate", middleware.RequireAuth, controllers.Validate) r.GET("/logout", middleware.RequireAuth, controllers.Logout) r.PUT("/update-password", middleware.RequireAuth, controllers.UpdatePassword) r.DELETE("/delete-user", middleware.RequireAuth, controllers.DeleteUser) ws := r.Group("/ws") { ws.Use(middleware.RequireAuth) ws.GET("/update-username", controllers.UpdateUsernameWebsocket) ws.GET("/upload-avatar", controllers.UserAvatarWebsocket) } log.Fatal(r.Run()) } frontend(react ): const handleAvatarUpload = () => { if (!avatar) { toast.error("Please select an image to upload", { theme: "dark", autoClose: 3000, }); return; } const ws = new WebSocket("ws://localhost:8000/ws/upload-avatar"); ws.onopen = () => { try { // Send file metadata and data separately ws.send(JSON.stringify({ filename: avatar.name })); const fileReader = new FileReader(); fileReader.readAsArrayBuffer(avatar); fileReader.onload = () => { const fileData = fileReader.result; if (fileData) { // Only send if fileData is not null ws.send(fileData); } else { // Handle the case where fileData is null toast.error("Failed to read file data", { theme: "dark", autoClose: 3000, }); } }; ws.onmessage = (event) => { const message = event.data.toString(); if (message === "Avatar updated successfully") { onUpdateAvatar(avatar); setAvatar(null); toast.success("Avatar updated successfully", { theme: "dark", autoClose: 3000, }); } else { console.log(message, event); } }; } catch (error) { toast.error("Failed to update avatar", { theme: "dark", autoClose: 3000, }); } }; ws.onerror = function (error) { console.error("WebSocket error:", error); toast.error("WebSocket error", { theme: "dark", autoClose: 3000, }); ws.close(); }; }; JS const handleAvatarUpload = () => { if (!avatar) { toast.error("Please select an image to upload", { theme: "dark", autoClose: 3000, }); return; } const ws = new WebSocket("ws://localhost:8000/ws/upload-avatar"); ws.onopen = () => { try { // Send file metadata and data separately ws.send(JSON.stringify({ filename: avatar.name })); const fileReader = new FileReader(); fileReader.readAsArrayBuffer(avatar); fileReader.onload = () => { const fileData = fileReader.result; if (fileData) { // Only send if fileData is not null ws.send(fileData); } else { // Handle the case where fileData is null toast.error("Failed to read file data", { theme: "dark", autoClose: 3000, }); } }; ws.onmessage = (event) => { const message = event.data.toString(); if (message === "Avatar updated successfully") { onUpdateAvatar(avatar); setAvatar(null); toast.success("Avatar updated successfully", { theme: "dark", autoClose: 3000, }); } else { console.log(message, event); } }; } catch (error) { toast.error("Failed to update avatar", { theme: "dark", autoClose: 3000, }); } }; ws.onerror = function (error) { console.error("WebSocket error:", error); toast.error("WebSocket error", { theme: "dark", autoClose: 3000, }); ws.close(); }; };
Какой смысл загрузки файла через вебсокет если у тебя каждый раз новое соединение устанавливается? Можно для этого использоваться обычный http POST с таким же успехом --- Сообщение объединено с предыдущим 10 май 2024 Да и в целом простые картинки лучше по http загружать, а вебсокет использовать если нужно стримить какие-то данные или большие объемы данных загружать на сервер
azurescens, В целом у меня так и было(загружал через http), просто я хотел написать это с использованием вебсокетов.
UnSad, тогда тебе не нужно создавать каждый раз новое соединение при вызове функции, вебсокеты работают таким образом что соединение создается один раз и дальше остается открытым и уже внутри этого открытого соединения клиент и сервер обмениваются ивентами, в твоем же случае это работает как обычный http запрос. И тебе не нужен отдельный эндпоинт под каждую задачу в вебсокете, это не РЕСТ. Ты один раз подключаешься к https://ws://localhost:8000/ws и потом внутри этого соединения отправляешь ивенты "message", а на сервере своем эти ивенты обрабатываешь. В стандартной реализации 4 ивента доступно open close error и message, если хочешь создавать свои ивенты можешь присмотреться к https://socket.io/ библиотеке. Подробнее про вебсокеты тут почитать можешь https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
И проблема с загрузкой файлов через вебсокет еще в том что с неправильной реализацией можешь заблокировать соединение на время загрузки файла, поэтому все должно происходить асинхронно для каждого клиента. Я бы на твоем месте просто использовал http метод для этого и не усложнял жизнь себе