|
@@ -1,39 +1,290 @@
|
|
|
package main
|
|
|
|
|
|
import (
|
|
|
+ "bytes"
|
|
|
"fmt"
|
|
|
- "log"
|
|
|
- "net"
|
|
|
+ native "log"
|
|
|
"net/http"
|
|
|
- "regexp"
|
|
|
+ "os"
|
|
|
"time"
|
|
|
+
|
|
|
+ ws "github.com/gorilla/websocket"
|
|
|
+ "github.com/kelseyhightower/envconfig"
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ version string
|
|
|
+ date string
|
|
|
+ logger = native.New(os.Stdout, "", 0)
|
|
|
+ errors = native.New(os.Stderr, "", 0)
|
|
|
)
|
|
|
|
|
|
+var upgrader = ws.Upgrader{
|
|
|
+ ReadBufferSize: 1024,
|
|
|
+ WriteBufferSize: 1024,
|
|
|
+ CheckOrigin: func(r *http.Request) bool {
|
|
|
+ return true // disable CORS
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+type config struct {
|
|
|
+ Port int64 `default:"8080"`
|
|
|
+ Prefix string `default:"/"`
|
|
|
+}
|
|
|
+
|
|
|
func main() {
|
|
|
+ var c config
|
|
|
+ if err := envconfig.Process("ws", &c); err != nil {
|
|
|
+ errors.Fatal(err)
|
|
|
+ }
|
|
|
+ mngr := manager{
|
|
|
+ clients: make(map[*client]bool),
|
|
|
+ add: make(chan *client),
|
|
|
+ remove: make(chan *client),
|
|
|
+ broadcast: make(chan []byte),
|
|
|
+ }
|
|
|
+ go func() {
|
|
|
+ for {
|
|
|
+ select {
|
|
|
+ case client := <-mngr.add:
|
|
|
+ mngr.clients[client] = true
|
|
|
+ case client := <-mngr.remove:
|
|
|
+ if _, ok := mngr.clients[client]; ok {
|
|
|
+ delete(mngr.clients, client)
|
|
|
+ close(client.send)
|
|
|
+ }
|
|
|
+ case message := <-mngr.broadcast:
|
|
|
+ for client := range mngr.clients {
|
|
|
+ select {
|
|
|
+ case client.send <- message:
|
|
|
+ default:
|
|
|
+ close(client.send)
|
|
|
+ delete(mngr.clients, client)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ http.HandleFunc(fmt.Sprintf("%sws", c.Prefix), func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ conn, err := upgrader.Upgrade(w, r, nil)
|
|
|
+ if err != nil {
|
|
|
+ logger.Println(err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ client := &client{
|
|
|
+ conn: conn,
|
|
|
+ send: make(chan []byte, 256),
|
|
|
+ mngr: mngr,
|
|
|
+ }
|
|
|
+ mngr.add <- client
|
|
|
|
|
|
- http.HandleFunc("/Info", info)
|
|
|
- http.HandleFunc("/Ok", ok)
|
|
|
- http.HandleFunc("/Status", status)
|
|
|
- http.ListenAndServe(":8081", nil) // устанавливаем порт веб-сервера
|
|
|
- err := http.ListenAndServe(":8081", nil)
|
|
|
- if err != nil {
|
|
|
- log.Fatal("ListenAndServe: ", err)
|
|
|
+ go client.write()
|
|
|
+ go client.read()
|
|
|
+ })
|
|
|
+ http.HandleFunc(c.Prefix, index(c.Prefix))
|
|
|
+ if c.Prefix != "/" {
|
|
|
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ w.WriteHeader(http.StatusForbidden)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ http.HandleFunc(fmt.Sprintf("%sversion", c.Prefix), func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if len(version) > 0 && len(date) > 0 {
|
|
|
+ fmt.Fprintf(w, "version: %s (built at %s)", version, date)
|
|
|
+ } else {
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
+ })
|
|
|
+ logger.Printf("[service] listening on port %d", c.Port)
|
|
|
+ if err := http.ListenAndServe(fmt.Sprintf(":%d", c.Port), nil); err != nil {
|
|
|
+ errors.Fatal(err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func info(w http.ResponseWriter, r *http.Request) {
|
|
|
- fmt.Fprint(w, "HTTP-status 200")
|
|
|
+type manager struct {
|
|
|
+ clients map[*client]bool
|
|
|
+ add chan *client
|
|
|
+ remove chan *client
|
|
|
+ broadcast chan []byte
|
|
|
}
|
|
|
|
|
|
-func ok(w http.ResponseWriter, r *http.Request) {
|
|
|
- fmt.Fprint(w, "Kruglov Kirill,792")
|
|
|
+type client struct {
|
|
|
+ conn *ws.Conn
|
|
|
+ send chan []byte
|
|
|
+ mngr manager
|
|
|
}
|
|
|
-func status(w http.ResponseWriter, r *http.Request) {
|
|
|
- ip, _, _ := net.SplitHostPort(r.RemoteAddr) // SHP на выходе дает 3 переменные
|
|
|
- re := regexp.MustCompile(`[.].*[.]`) // MC библиотека для работы с регулярками;
|
|
|
- // [.] - Соответствует одному символу из содержащихся в квадратных скобках
|
|
|
- fmt.Fprintln(w, re.ReplaceAllString(ip, ".*.*.")) // В реплейс загоняем необработанный текст, он на выходе выдает обработанный с помощью регулярки
|
|
|
- fmt.Fprintf(w, "Kruglov Kirill,792"+"\n")
|
|
|
- fmt.Fprintln(w, time.Now().Format("2006-01-02 3:4:5 pm"))
|
|
|
|
|
|
+const (
|
|
|
+ maxMessageSize = 512
|
|
|
+ readDeadline = 60 * time.Second
|
|
|
+ writeDeadline = 10 * time.Second
|
|
|
+ pingPeriod = (writeDeadline * 9) / 10
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ newline = []byte{'\n'}
|
|
|
+ space = []byte{' '}
|
|
|
+)
|
|
|
+
|
|
|
+func (c *client) read() {
|
|
|
+ defer func() {
|
|
|
+ c.mngr.remove <- c
|
|
|
+ c.conn.Close()
|
|
|
+ }()
|
|
|
+ c.conn.SetReadLimit(maxMessageSize)
|
|
|
+ c.conn.SetReadDeadline(time.Now().Add(readDeadline))
|
|
|
+ c.conn.SetPongHandler(func(string) error {
|
|
|
+ c.conn.SetReadDeadline(time.Now().Add(readDeadline))
|
|
|
+ return nil
|
|
|
+ })
|
|
|
+ for {
|
|
|
+ _, message, err := c.conn.ReadMessage()
|
|
|
+ if err != nil {
|
|
|
+ if ws.IsUnexpectedCloseError(err, ws.CloseGoingAway, ws.CloseAbnormalClosure) {
|
|
|
+ logger.Printf("error: %v", err)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
|
|
|
+ c.mngr.broadcast <- message
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (c *client) write() {
|
|
|
+ ticker := time.NewTicker(pingPeriod)
|
|
|
+ defer func() {
|
|
|
+ ticker.Stop()
|
|
|
+ c.conn.Close()
|
|
|
+ }()
|
|
|
+ for {
|
|
|
+ select {
|
|
|
+ case message, ok := <-c.send:
|
|
|
+ c.conn.SetWriteDeadline(time.Now().Add(writeDeadline))
|
|
|
+ if !ok {
|
|
|
+ c.conn.WriteMessage(ws.CloseMessage, []byte{})
|
|
|
+ return
|
|
|
+ }
|
|
|
+ w, err := c.conn.NextWriter(ws.TextMessage)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ w.Write(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(writeDeadline))
|
|
|
+ if err := c.conn.WriteMessage(ws.PingMessage, nil); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+var html = `<!DOCTYPE html>
|
|
|
+<html lang="en">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <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 + "%sws");
|
|
|
+ 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: blue;
|
|
|
+ }
|
|
|
+ #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>
|
|
|
+ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <div id="log"></div>
|
|
|
+ <form id="form">
|
|
|
+ <input type="submit" value="Send" />
|
|
|
+ <input type="text" id="msg" size="64" />
|
|
|
+ </form>
|
|
|
+</body>
|
|
|
+</html>`
|
|
|
+
|
|
|
+func index(prefix string) func(http.ResponseWriter, *http.Request) {
|
|
|
+ return func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ fmt.Fprint(w, fmt.Sprintf(html, prefix))
|
|
|
+ }
|
|
|
}
|