-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
584 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package main | ||
|
||
import ( | ||
"embed" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/cmilhench/x/exp/http/socket" | ||
"github.com/cmilhench/x/exp/http/static" | ||
"github.com/cmilhench/x/exp/irc" | ||
) | ||
|
||
//go:embed static | ||
var fs embed.FS | ||
|
||
func main() { | ||
server := socket.NewSocketServer() | ||
server.Handle(socketHandler(server)) | ||
server.Start() | ||
|
||
http.Handle("/", http.FileServer(static.Neutered{Prefix: "static", FileSystem: http.FS(fs)})) | ||
http.HandleFunc("/ws", server.HandleConnections) | ||
|
||
log.Println("Socket server started on :8080") | ||
err := http.ListenAndServe(":8080", nil) | ||
if err != nil { | ||
log.Fatalf("ListenAndServe: %v", err) | ||
} | ||
} | ||
|
||
func socketHandler(server *socket.SocketServer) socket.MessageHandler { | ||
return func(client *socket.Client, messageBytes []byte) { | ||
message := irc.ParseMessage(string(messageBytes)) | ||
log.Printf("message ->: %#v", message) | ||
switch message.Command { | ||
case "INFO": // returns information about the <target> server | ||
client.Send([]byte(fmt.Sprintf("INFO %s", "This is an IRC server."))) | ||
case "MOTD": // returns the message of the day | ||
client.Send([]byte(fmt.Sprintf("MOTD %s", "Welcome to the IRC server!"))) | ||
case "NICK": // allows a client to change their IRC nickname. | ||
client.Name = message.Params | ||
case "PING": // tests the presence of a connection | ||
client.Send([]byte(fmt.Sprintf("PONG %s", message.Params))) | ||
case "NOTICE", "PRIVMSG": // Sends <message> to <target>, which is usually a user or channel. | ||
if message.Params[0] == '#' { | ||
server.Broadcast([]byte(fmt.Sprintf(":%s PRIVMSG %s", client.Name, message.Trailing))) | ||
} else { | ||
server.Send(message.Params, []byte(fmt.Sprintf(":%s PRIVMSG %s", client.Name, message.Trailing))) | ||
} | ||
case "QUIT": // disconnects the user from the server. | ||
server.Part(client) | ||
case "TIME": // returns the current time on the server | ||
client.Send([]byte(time.Now().Format(time.RFC1123Z))) | ||
case "TOPIC": // sets the topic of <channel> to <topic> | ||
default: | ||
log.Printf("Unknown message type: %#v", message) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>WebSocket Client</title> | ||
</head> | ||
<body> | ||
|
||
<h2>WebSocket Client</h2> | ||
|
||
<div> | ||
<label for="message">Message:</label> | ||
<input type="text" id="message" placeholder="message" onchange="enter()"> | ||
</div> | ||
|
||
<h3>Messages:</h3> | ||
<div id="messages"></div> | ||
|
||
<script> | ||
function Client(url) { | ||
if (!(this instanceof Client)) return new Client(url); | ||
this.delay = 0; | ||
this.open(url) | ||
} | ||
Client.prototype.open = function (url) { | ||
var self = this; | ||
var args = Array.prototype.slice.call(arguments); | ||
self._socket = new WebSocket(url); | ||
self._socket.binaryType = "arraybuffer"; | ||
self._socket.onerror = function(event) { | ||
console.error("WebSocket error:", event); | ||
}; | ||
self._socket.onopen = function (event) { | ||
self.delay = 0; | ||
console.debug('WebSocket connected'); | ||
} | ||
self._socket.onclose = function (event) { | ||
console.log('WebSocket disconnected [' + event.code +']!'); | ||
if (event.code !== 1000) { | ||
self.delay = Math.min(Math.max(self.delay *= 2, 0.5), 30); // 0.5, 1, 2, 4, 8, 16, 30, 30 | ||
window.setTimeout(function () { self.open.apply(self, args); }, self.delay * 1000); | ||
} | ||
} | ||
self._socket.onmessage = function (event) { self.onmessage(event); } | ||
return self; | ||
} | ||
Client.prototype.send = function (data, options) { | ||
return this._socket.send(data, options) | ||
} | ||
Client.prototype.onmessage = function (event) {} | ||
</script> | ||
|
||
<script> | ||
var c = new Client("ws://localhost:8080/ws"); | ||
c.onmessage = function(event) { | ||
const parent = document.getElementById("messages"); | ||
const element = document.createElement("div"); | ||
const arrayBuffer = event.data; | ||
message = Parse(new TextDecoder("utf-8").decode(arrayBuffer)) | ||
console.debug(message) | ||
switch (message.Command) { | ||
case "NOTICE": | ||
case "PONG": | ||
case "PRIVMSG": | ||
element.textContent = "Received: " + message.Params; | ||
break; | ||
default: | ||
element.textContent = "Received: " + message.Raw; | ||
break; | ||
} | ||
parent.appendChild(element); | ||
}; | ||
function enter() { | ||
const encoder = new TextEncoder(); | ||
const input = document.getElementById("message"); | ||
let line = input.value; | ||
let prefix = ''; | ||
let command = ''; | ||
let params = ''; | ||
let trailing = ''; | ||
if (line[0] !== '/') { | ||
command = 'PRIVMSG' | ||
params = '#channel' | ||
trailing = line | ||
} else { | ||
let i = line.indexOf(' '); | ||
if (i === -1) { | ||
i = line.length; | ||
} | ||
command = line.substring(1, i); | ||
command = command.toUpperCase(); | ||
switch (command) { | ||
case 'NOTICE': | ||
case 'PRIVMSG': | ||
break; | ||
default: | ||
break; | ||
} | ||
line = line.substring(i); | ||
// Params | ||
i = line.indexOf(' :'); | ||
if (i === -1) { | ||
i = line.length; | ||
} | ||
if (i !== 0) { | ||
params = line.substring(1, i); | ||
} | ||
// Trailing | ||
if (line.length - i > 2) { | ||
trailing = line.substring(i + 2); | ||
} | ||
} | ||
message = Message(prefix, command, params, trailing) | ||
c.send(encoder.encode(message)); | ||
input.value = ""; | ||
}; | ||
</script> | ||
|
||
<script> | ||
function Message(prefix, command, params, trailing) { | ||
let result = ""; | ||
if (prefix) { | ||
result += `:${prefix} `; | ||
} | ||
result += command; | ||
if (params) { | ||
result += ` ${params}`; | ||
} | ||
if (trailing) { | ||
result += ` :${trailing}`; | ||
} | ||
return result; | ||
} | ||
function Parse(line) { | ||
line = line.replace(/\r$/, ""); | ||
line = line.replace(/\r\n$/, ""); | ||
const orig = line; | ||
const message = { | ||
Raw: orig, | ||
Prefix: "", | ||
Command: "", | ||
Params: "", | ||
Trailing: "" | ||
}; | ||
// Prefix | ||
if (line[0] === ':') { | ||
const i = line.indexOf(" "); | ||
message.Prefix = line.substring(1, i); | ||
line = line.substring(i + 1); | ||
} | ||
// Command | ||
let i = line.indexOf(" "); | ||
if (i === -1) { | ||
i = line.length; | ||
} | ||
message.Command = line.substring(0, i); | ||
line = line.substring(i); | ||
// Params | ||
i = line.indexOf(" :"); | ||
if (i === -1) { | ||
i = line.length; | ||
} | ||
if (i !== 0) { | ||
message.Params = line.substring(1, i); | ||
} | ||
// Trailing | ||
if (line.length - i > 2) { | ||
message.Trailing = line.substring(i + 2); | ||
} | ||
return message; | ||
} | ||
</script> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package socket | ||
|
||
import ( | ||
"log" | ||
"time" | ||
|
||
"github.com/cmilhench/x/exp/uuid" | ||
|
||
"github.com/gorilla/websocket" | ||
) | ||
|
||
type Client struct { | ||
conn *websocket.Conn | ||
send chan []byte | ||
id string | ||
Name string | ||
} | ||
|
||
type MessageHandler func(*Client, []byte) | ||
|
||
func NewClient(conn *websocket.Conn) *Client { | ||
id, _ := uuid.New() | ||
return &Client{ | ||
id: id, | ||
conn: conn, | ||
send: make(chan []byte), | ||
} | ||
} | ||
|
||
func (client *Client) ReadMessages(fn MessageHandler) { | ||
for { | ||
_, msg, err := client.conn.ReadMessage() | ||
if err != nil { | ||
log.Printf("Read error: %v", err) | ||
break | ||
} | ||
fn(client, msg) | ||
} | ||
} | ||
|
||
func (client *Client) WriteMessages() { | ||
for msg := range client.send { | ||
err := client.conn.WriteMessage(websocket.BinaryMessage, msg) | ||
if err != nil { | ||
log.Printf("Write error: %v", err) | ||
break | ||
} | ||
} | ||
} | ||
|
||
func (client *Client) Send(data []byte) { | ||
client.send <- data | ||
} | ||
|
||
func (client *Client) Close() { | ||
close(client.send) | ||
deadline := time.Now().Add(5 * time.Second) | ||
data := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") | ||
_ = client.conn.WriteControl(websocket.CloseMessage, data, deadline) | ||
} |
Oops, something went wrong.