Алексей 2 years ago
parent
commit
6b06de157c
8 changed files with 446 additions and 1 deletions
  1. 13 0
      chat/Dockerfile
  2. 102 0
      chat/README.md
  3. 137 0
      chat/client.go
  4. 98 0
      chat/home.html
  5. 53 0
      chat/hub.go
  6. 40 0
      chat/main.go
  7. 2 1
      main_2.go
  8. 1 0
      websocket

+ 13 - 0
chat/Dockerfile

@@ -0,0 +1,13 @@
+FROM golang:1.19-alpine AS builder
+
+WORKDIR /app
+
+COPY . .
+
+RUN go mod init example
+RUN go mod tidy
+RUN go build -o webchat .
+
+ENTRYPOINT ["./webchat"]
+
+EXPOSE 8080

+ 102 - 0
chat/README.md

@@ -0,0 +1,102 @@
+# Chat Example
+
+This application shows how to use the
+[websocket](https://github.com/gorilla/websocket) package to implement a simple
+web chat application.
+
+## Running the example
+
+The example requires a working Go development environment. The [Getting
+Started](http://golang.org/doc/install) page describes how to install the
+development environment.
+
+Once you have Go up and running, you can download, build and run the example
+using the following commands.
+
+    $ go get github.com/gorilla/websocket
+    $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
+    $ go run *.go
+
+To use the chat example, open http://localhost:8080/ in your browser.
+
+## Server
+
+The server application defines two types, `Client` and `Hub`. The server
+creates an instance of the `Client` type for each websocket connection. A
+`Client` acts as an intermediary between the websocket connection and a single
+instance of the `Hub` type. The `Hub` maintains a set of registered clients and
+broadcasts messages to the clients.
+
+The application runs one goroutine for the `Hub` and two goroutines for each
+`Client`. The goroutines communicate with each other using channels. The `Hub`
+has channels for registering clients, unregistering clients and broadcasting
+messages. A `Client` has a buffered channel of outbound messages. One of the
+client's goroutines reads messages from this channel and writes the messages to
+the websocket. The other client goroutine reads messages from the websocket and
+sends them to the hub.
+
+### Hub 
+
+The code for the `Hub` type is in
+[hub.go](https://github.com/gorilla/websocket/blob/master/examples/chat/hub.go). 
+The application's `main` function starts the hub's `run` method as a goroutine.
+Clients send requests to the hub using the `register`, `unregister` and
+`broadcast` channels.
+
+The hub registers clients by adding the client pointer as a key in the
+`clients` map. The map value is always true.
+
+The unregister code is a little more complicated. In addition to deleting the
+client pointer from the `clients` map, the hub closes the clients's `send`
+channel to signal the client that no more messages will be sent to the client.
+
+The hub handles messages by looping over the registered clients and sending the
+message to the client's `send` channel. If the client's `send` buffer is full,
+then the hub assumes that the client is dead or stuck. In this case, the hub
+unregisters the client and closes the websocket.
+
+### Client
+
+The code for the `Client` type is in [client.go](https://github.com/gorilla/websocket/blob/master/examples/chat/client.go).
+
+The `serveWs` function is registered by the application's `main` function as
+an HTTP handler. The handler upgrades the HTTP connection to the WebSocket
+protocol, creates a client, registers the client with the hub and schedules the
+client to be unregistered using a defer statement.
+
+Next, the HTTP handler starts the client's `writePump` method as a goroutine.
+This method transfers messages from the client's send channel to the websocket
+connection. The writer method exits when the channel is closed by the hub or
+there's an error writing to the websocket connection.
+
+Finally, the HTTP handler calls the client's `readPump` method. This method
+transfers inbound messages from the websocket to the hub.
+
+WebSocket connections [support one concurrent reader and one concurrent
+writer](https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency). The
+application ensures that these concurrency requirements are met by executing
+all reads from the `readPump` goroutine and all writes from the `writePump`
+goroutine.
+
+To improve efficiency under high load, the `writePump` function coalesces
+pending chat messages in the `send` channel to a single WebSocket message. This
+reduces the number of system calls and the amount of data sent over the
+network.
+
+## Frontend
+
+The frontend code is in [home.html](https://github.com/gorilla/websocket/blob/master/examples/chat/home.html).
+
+On document load, the script checks for websocket functionality in the browser.
+If websocket functionality is available, then the script opens a connection to
+the server and registers a callback to handle messages from the server. The
+callback appends the message to the chat log using the appendLog function.
+
+To allow the user to manually scroll through the chat log without interruption
+from new messages, the `appendLog` function checks the scroll position before
+adding new content. If the chat log is scrolled to the bottom, then the
+function scrolls new content into view after adding the content. Otherwise, the
+scroll position is not changed.
+
+The form handler writes the user input to the websocket and clears the input
+field.

+ 137 - 0
chat/client.go

@@ -0,0 +1,137 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bytes"
+	"log"
+	"net/http"
+	"time"
+
+	"github.com/gorilla/websocket"
+)
+
+const (
+	// Time allowed to write a message to the peer.
+	writeWait = 10 * time.Second
+
+	// Time allowed to read the next pong message from the peer.
+	pongWait = 60 * time.Second
+
+	// Send pings to peer with this period. Must be less than pongWait.
+	pingPeriod = (pongWait * 9) / 10
+
+	// Maximum message size allowed from peer.
+	maxMessageSize = 512
+)
+
+var (
+	newline = []byte{'\n'}
+	space   = []byte{' '}
+)
+
+var upgrader = websocket.Upgrader{
+	ReadBufferSize:  1024,
+	WriteBufferSize: 1024,
+}
+
+// Client is a middleman between the websocket connection and the hub.
+type Client struct {
+	hub *Hub
+
+	// The websocket connection.
+	conn *websocket.Conn
+
+	// Buffered channel of outbound messages.
+	send chan []byte
+}
+
+// readPump pumps messages from the websocket connection to the hub.
+//
+// The application runs readPump in a per-connection goroutine. The application
+// ensures that there is at most one reader on a connection by executing all
+// reads from this goroutine.
+func (c *Client) readPump() {
+	defer func() {
+		c.hub.unregister <- c
+		c.conn.Close()
+	}()
+	c.conn.SetReadLimit(maxMessageSize)
+	c.conn.SetReadDeadline(time.Now().Add(pongWait))
+	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+	for {
+		_, message, err := c.conn.ReadMessage()
+		if err != nil {
+			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
+				log.Printf("error: %v", err)
+			}
+			break
+		}
+		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
+		c.hub.broadcast <- message
+	}
+}
+
+// writePump pumps messages from the hub to the websocket connection.
+//
+// A goroutine running writePump is started for each connection. The
+// application ensures that there is at most one writer to a connection by
+// executing all writes from this goroutine.
+func (c *Client) writePump() {
+	ticker := time.NewTicker(pingPeriod)
+	defer func() {
+		ticker.Stop()
+		c.conn.Close()
+	}()
+	for {
+		select {
+		case message, ok := <-c.send:
+			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+			if !ok {
+				// The hub closed the channel.
+				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
+				return
+			}
+
+			w, err := c.conn.NextWriter(websocket.TextMessage)
+			if err != nil {
+				return
+			}
+			w.Write(message)
+
+			// Add queued chat messages to the current websocket message.
+			n := len(c.send)
+			for i := 0; i < n; i++ {
+				w.Write(newline)
+				w.Write(<-c.send)
+			}
+
+			if err := w.Close(); err != nil {
+				return
+			}
+		case <-ticker.C:
+			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
+				return
+			}
+		}
+	}
+}
+
+// serveWs handles websocket requests from the peer.
+func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
+	conn, err := upgrader.Upgrade(w, r, nil)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+	client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
+	client.hub.register <- client
+
+	// Allow collection of memory referenced by the caller by doing all work in
+	// new goroutines.
+	go client.writePump()
+	go client.readPump()
+}

+ 98 - 0
chat/home.html

@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Chat Example</title>
+<script type="text/javascript">
+window.onload = function () {
+    var conn;
+    var msg = document.getElementById("msg");
+    var log = document.getElementById("log");
+
+    function appendLog(item) {
+        var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
+        log.appendChild(item);
+        if (doScroll) {
+            log.scrollTop = log.scrollHeight - log.clientHeight;
+        }
+    }
+
+    document.getElementById("form").onsubmit = function () {
+        if (!conn) {
+            return false;
+        }
+        if (!msg.value) {
+            return false;
+        }
+        conn.send(msg.value);
+        msg.value = "";
+        return false;
+    };
+
+    if (window["WebSocket"]) {
+        conn = new WebSocket("ws://" + document.location.host + "/ws");
+        conn.onclose = function (evt) {
+            var item = document.createElement("div");
+            item.innerHTML = "<b>Connection closed.</b>";
+            appendLog(item);
+        };
+        conn.onmessage = function (evt) {
+            var messages = evt.data.split('\n');
+            for (var i = 0; i < messages.length; i++) {
+                var item = document.createElement("div");
+                item.innerText = messages[i];
+                appendLog(item);
+            }
+        };
+    } else {
+        var item = document.createElement("div");
+        item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
+        appendLog(item);
+    }
+};
+</script>
+<style type="text/css">
+html {
+    overflow: hidden;
+}
+
+body {
+    overflow: hidden;
+    padding: 0;
+    margin: 0;
+    width: 100%;
+    height: 100%;
+    background: gray;
+}
+
+#log {
+    background: white;
+    margin: 0;
+    padding: 0.5em 0.5em 0.5em 0.5em;
+    position: absolute;
+    top: 0.5em;
+    left: 0.5em;
+    right: 0.5em;
+    bottom: 3em;
+    overflow: auto;
+}
+
+#form {
+    padding: 0 0.5em 0 0.5em;
+    margin: 0;
+    position: absolute;
+    bottom: 1em;
+    left: 0px;
+    width: 100%;
+    overflow: hidden;
+}
+
+</style>
+</head>
+<body>
+<div id="log"></div>
+<form id="form">
+    <input type="submit" value="Send" />
+    <input type="text" id="msg" size="64" autofocus />
+</form>
+</body>
+</html>

+ 53 - 0
chat/hub.go

@@ -0,0 +1,53 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+// Hub maintains the set of active clients and broadcasts messages to the
+// clients.
+type Hub struct {
+	// Registered clients.
+	clients map[*Client]bool
+
+	// Inbound messages from the clients.
+	broadcast chan []byte
+
+	// Register requests from the clients.
+	register chan *Client
+
+	// Unregister requests from clients.
+	unregister chan *Client
+}
+
+func newHub() *Hub {
+	return &Hub{
+		broadcast:  make(chan []byte),
+		register:   make(chan *Client),
+		unregister: make(chan *Client),
+		clients:    make(map[*Client]bool),
+	}
+}
+
+func (h *Hub) run() {
+	for {
+		select {
+		case client := <-h.register:
+			h.clients[client] = true
+		case client := <-h.unregister:
+			if _, ok := h.clients[client]; ok {
+				delete(h.clients, client)
+				close(client.send)
+			}
+		case message := <-h.broadcast:
+			for client := range h.clients {
+				select {
+				case client.send <- message:
+				default:
+					close(client.send)
+					delete(h.clients, client)
+				}
+			}
+		}
+	}
+}

+ 40 - 0
chat/main.go

@@ -0,0 +1,40 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"log"
+	"net/http"
+)
+
+var addr = flag.String("addr", ":8080", "http service address")
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+	log.Println(r.URL)
+	if r.URL.Path != "/" {
+		http.Error(w, "Not found", http.StatusNotFound)
+		return
+	}
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+	http.ServeFile(w, r, "home.html")
+}
+
+func main() {
+	flag.Parse()
+	hub := newHub()
+	go hub.run()
+	http.HandleFunc("/", serveHome)
+	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
+		serveWs(hub, w, r)
+	})
+	err := http.ListenAndServe(*addr, nil)
+	if err != nil {
+		log.Fatal("ListenAndServe: ", err)
+	}
+}

+ 2 - 1
main_2.go

@@ -9,8 +9,9 @@ import (
 )
 
 func main() {
+	fmt.Println("starting server")
 	http.HandleFunc("/status", status)
-	http.ListenAndServe(":8080", nil)
+	http.ListenAndServe(":8086", nil)
 
 }
 

+ 1 - 0
websocket

@@ -0,0 +1 @@
+Subproject commit af47554f343b4675b30172ac301638d350db34a5