good morning!!!!

Skip to content
Snippets Groups Projects
Unverified Commit ba1c24d1 authored by Anmol Sethi's avatar Anmol Sethi
Browse files

Add chat example

Closes #174
parent c7331663
No related branches found
No related tags found
No related merge requests found
......@@ -13,7 +13,7 @@ goimports: gen
goimports -w "-local=$$(go list -m)" .
prettier:
prettier --write --print-width=120 --no-semi --trailing-comma=all --loglevel=warn $$(git ls-files "*.yml" "*.md")
prettier --write --print-width=120 --no-semi --trailing-comma=all --loglevel=warn $$(git ls-files "*.yml" "*.md" "*.js" "*.css" "*.html")
gen:
stringer -type=opcode,MessageType,StatusCode -output=stringer.go
......
package main
import (
"context"
"io"
"io/ioutil"
"log"
"net/http"
"sync"
"time"
"nhooyr.io/websocket"
)
type chatServer struct {
subscribersMu sync.RWMutex
subscribers map[chan []byte]struct{}
}
func (cs *chatServer) subscribeHandler(w http.ResponseWriter, r *http.Request) {
println("HELLO")
c, err := websocket.Accept(w, r, nil)
if err != nil {
log.Print(err)
return
}
cs.subscribe(r.Context(), c)
}
func (cs *chatServer) publishHandler(w http.ResponseWriter, r *http.Request) {
body := io.LimitReader(r.Body, 8192)
msg, err := ioutil.ReadAll(body)
if err != nil {
return
}
cs.publish(msg)
}
func (cs *chatServer) publish(msg []byte) {
cs.subscribersMu.RLock()
defer cs.subscribersMu.RUnlock()
for c := range cs.subscribers {
select {
case c <- msg:
default:
}
}
}
func (cs *chatServer) addSubscriber(msgs chan []byte) {
cs.subscribersMu.Lock()
if cs.subscribers == nil {
cs.subscribers = make(map[chan []byte]struct{})
}
cs.subscribers[msgs] = struct{}{}
cs.subscribersMu.Unlock()
}
func (cs *chatServer) deleteSubscriber(msgs chan []byte) {
cs.subscribersMu.Lock()
delete(cs.subscribers, msgs)
cs.subscribersMu.Unlock()
}
func (cs *chatServer) subscribe(ctx context.Context, c *websocket.Conn) error {
ctx = c.CloseRead(ctx)
msgs := make(chan []byte, 16)
cs.addSubscriber(msgs)
defer cs.deleteSubscriber(msgs)
for {
select {
case msg := <-msgs:
err := writeTimeout(ctx, time.Second*5, c, msg)
if err != nil {
return err
}
case <-ctx.Done():
return ctx.Err()
}
}
}
func writeTimeout(ctx context.Context, timeout time.Duration, c *websocket.Conn, msg []byte) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return c.Write(ctx, websocket.MessageText, msg)
}
module nhooyr.io/websocket/example-chat
go 1.13
require nhooyr.io/websocket v1.8.2
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/compress v1.10.0 h1:92XGj1AcYzA6UrVdd4qIIBrT8OroryvRvdmg/IfmC7Y=
github.com/klauspost/compress v1.10.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
nhooyr.io/websocket v1.8.2 h1:LwdzfyyOZKtVFoXay6A39Acu03KmidSZ3YUUvPa13PA=
nhooyr.io/websocket v1.8.2/go.mod h1:LiqdCg1Cu7TPWxEvPjPa0TGYxCsy4pHNTN9gGluwBpQ=
body {
width: 100vw;
height: 100vh;
min-width: 320px;
}
#root {
padding: 20px;
max-width: 500px;
margin: auto;
max-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#root > * + * {
margin: 20px 0 0 0;
}
#message-log {
width: 100%;
height: 100vh;
flex-grow: 1;
overflow: auto;
}
#message-log p:first-child {
margin-top: 0;
margin-bottom: 0;
}
#message-log > * + * {
margin: 10px 0 0 0;
}
#publish-form {
appearance: none;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
#publish-form input[type="text"] {
flex-grow: 1;
word-break: normal;
border-radius: 5px;
}
#publish-form input[type="submit"] {
color: white;
background-color: black;
border-radius: 5px;
margin-left: 10px;
}
#publish-form input[type="submit"]:hover {
background-color: red;
}
#publish-form input[type="submit"]:active {
background-color: red;
}
<!DOCTYPE html>
<html lang="en-CA">
<head>
<meta charset="UTF-8" />
<title>nhooyr.io/websocket - Chat Example</title>
<meta name="viewport" content="width=device-width" />
<link href="/index.css" rel="stylesheet" />
<link href="https://unpkg.com/sanitize.css" rel="stylesheet" />
<link href="https://unpkg.com/sanitize.css/typography.css" rel="stylesheet" />
<link href="https://unpkg.com/sanitize.css/forms.css" rel="stylesheet" />
</head>
<body>
<div id="root">
<div id="message-log"></div>
<form id="publish-form">
<input name="message" id="message-input" type="text" />
<input type="submit" />
</form>
</div>
<script type="text/javascript" src="/index.js"></script>
</body>
</html>
;(() => {
let conn
let submitted = false
function dial() {
conn = new WebSocket(`ws://${location.host}/subscribe`)
conn.addEventListener("close", () => {
conn = undefined
setTimeout(dial, 1000)
})
conn.addEventListener("message", ev => {
if (typeof ev.data !== "string") {
return
}
appendLog(ev.data)
if (submitted) {
messageLog.scrollTo(0, messageLog.scrollHeight)
submitted = false
}
})
return conn
}
dial()
const messageLog = document.getElementById("message-log")
const publishForm = document.getElementById("publish-form")
const messageInput = document.getElementById("message-input")
function appendLog(text) {
const p = document.createElement("p")
p.innerText = `${new Date().toLocaleTimeString()}: ${text}`
messageLog.append(p)
}
appendLog("Submit a message to get started!")
publishForm.onsubmit = ev => {
ev.preventDefault()
const msg = messageInput.value
if (msg === "") {
return
}
messageInput.value = ""
submitted = true
fetch("/publish", {
method: "POST",
body: msg,
})
}
})()
package main
import (
"fmt"
"log"
"net"
"net/http"
"time"
)
func main() {
err := run()
if err != nil {
log.Fatal(err)
}
}
func run() error {
l, err := net.Listen("tcp", "localhost:0")
if err != nil {
return err
}
fmt.Printf("listening on http://%v\n", l.Addr())
var ws chatServer
m := http.NewServeMux()
m.Handle("/", http.FileServer(http.Dir(".")))
m.HandleFunc("/subscribe", ws.subscribeHandler)
m.HandleFunc("/publish", ws.publishHandler)
s := http.Server{
Handler: m,
ReadTimeout: time.Second * 10,
WriteTimeout: time.Second * 10,
}
return s.Serve(l)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment