Compare commits

..

10 Commits

Author SHA1 Message Date
bel
6ca9686bee Trying to classify 2020-06-04 16:24:50 -06:00
Bel LaPointe
fab2ab97d7 Working on making server and client uuid in sync but a challenjour has appeared is good 2020-05-13 16:42:12 -06:00
Bel LaPointe
d6ea697f57 bcast join 2020-05-13 16:36:20 -06:00
Bel LaPointe
d2cebde4dc locking in prog 2020-05-13 16:29:23 -06:00
Bel LaPointe
c9872c7725 multi rooms with some race for multi write to a ws 2020-05-13 16:24:33 -06:00
Bel LaPointe
4145bea3ba remove destructor given unerliable 2020-05-13 16:09:26 -06:00
Bel LaPointe
816c793c5a removing unused 2020-05-13 16:05:49 -06:00
Bel LaPointe
d610aebc8f remove unused 2020-05-13 16:04:48 -06:00
Bel LaPointe
24eb2bc3de Brain dump and minor rename 2020-05-13 16:03:07 -06:00
Bel LaPointe
5c924ad154 eventually deletes elements 2020-05-13 15:46:26 -06:00
13 changed files with 210 additions and 433 deletions

54
BRAINDUMP.md Executable file
View File

@@ -0,0 +1,54 @@
# Classes
## Config - helpers
### Static
* [gs]etCookie
* createUUID
* getUUID
## Log - logger
### Static
* info
* warn
* error
* log
## Preview - self-video feed element
* element
## WS - websocket wrapper
### Static
* ws
## RTC - RTC connection to TURN
* conn (rtc conection)
* streams {id: RTCMediaStream}
## Remote - local display of stream
* element
## Local - local stream out
### Text, Video, Audio
* stream (MediaStream)
## Entropy - monolith
### Static
* pageReady - init Local preview video
* start - new RTC conn, sends offer message
* gotMessageFromServer - listens for events from server, starts stream if called, responds to offer messages, responds to TURN connect
* createdDescription
* local - Local
* rtc - RTC

0
config.go Normal file → Executable file
View File

0
cookie.go Normal file → Executable file
View File

19
errors.log Normal file
View File

@@ -0,0 +1,19 @@
address wss://dev2.home.blapointe.com/ws/
got description
uuid
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
got description
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
InvalidStateError: Cannot set remote answer in state stable
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
OperationError: Unknown ufrag (fa5a1736)
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
OperationError: Unknown ufrag (fa5a1736)
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
https://stackoverflow.com/questions/30109011/webrtc-failed-to-add-a-third-peer-cannot-set-remote-answer-in-state-stable

0
main.go Normal file → Executable file
View File

29
pool.go Normal file → Executable file
View File

@@ -3,22 +3,39 @@ package main
import ( import (
"io" "io"
"io/ioutil" "io/ioutil"
"strings"
"sync" "sync"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
type Conn struct {
ws websocket.Conn
lock sync.Mutex
}
type Pool struct { type Pool struct {
conns *sync.Map //map[string]*websocket.Conn lock *sync.RWMutex
conns *sync.Map //map[string]*Conn
} }
func NewPool() *Pool { func NewPool() *Pool {
return &Pool{ return &Pool{
conns: &sync.Map{}, //map[string]*websocket.Conn{}, conns: &sync.Map{},
lock: &sync.RWMutex{},
} }
} }
func (p *Pool) Push(id string, conn *websocket.Conn) {
p.lock.Lock()
p.conns.Store(id, &Conn{ws: *conn})
p.lock.Unlock()
p.Broadcast(websocket.TextMessage, strings.NewReader(`{"joined":"`+id+`", "uuid":"`+id+`"}`))
}
func (p *Pool) Broadcast(mt int, r io.Reader) error { func (p *Pool) Broadcast(mt int, r io.Reader) error {
p.lock.RLock()
defer p.lock.RUnlock()
// io.MultiWriter exists but I like this // io.MultiWriter exists but I like this
b, err := ioutil.ReadAll(r) b, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
@@ -28,9 +45,13 @@ func (p *Pool) Broadcast(mt int, r io.Reader) error {
cnt := 0 cnt := 0
p.conns.Range(func(k, v interface{}) bool { p.conns.Range(func(k, v interface{}) bool {
k = k.(string) k = k.(string)
conn := v.(*websocket.Conn) conn := v.(*Conn)
lock := &conn.lock
lock.Lock()
defer lock.Unlock()
ws := &conn.ws
cnt += 1 cnt += 1
w, err := conn.NextWriter(mt) w, err := ws.NextWriter(mt)
if err != nil { if err != nil {
p.conns.Delete(k) p.conns.Delete(k)
return true return true

View File

@@ -1,25 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!--
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
-->
<script src="webrtc.js"></script> <script src="webrtc.js"></script>
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
<style>
video {
width: 100%;
width: 100%;
max-height: 90vh;
/*
max-width: 400px;
min-width: 400px;
*/
border: 1px solid black;
display: inline-block;
}
#preview { display: block; max-width: 150px; position: absolute; top: 0; right: 0; z-index: 1; }
</style>
</head> </head>
<body> <body>
@@ -27,18 +10,16 @@
<div>Audio<label class="switch"><input onclick="toggle('audio', this)" type="checkbox"><span class="slider round"></span></label></div> <div>Audio<label class="switch"><input onclick="toggle('audio', this)" type="checkbox"><span class="slider round"></span></label></div>
<video id="preview" autoplay muted ></video> <video id="preview" autoplay muted ></video>
<div id="remote"> <div id="remote"></div>
</div>
<br /> <br />
<input type="button" id="start" onclick="start(true)" value="Start Video"></input> <input type="button" id="start" onclick="start(true)" value="Start Video"></input>
<div id="log"></div>
</body>
<footer>
<script type="text/javascript"> <script type="text/javascript">
pageReady(); pageReady();
</script> </script>
</footer>
<div id="log">
</div>
</body>
</html> </html>

21
public/style.css Normal file → Executable file
View File

@@ -60,3 +60,24 @@ input:checked + .slider:before {
.slider.round:before { .slider.round:before {
border-radius: 50%; border-radius: 50%;
} }
video {
width: 100%;
width: 100%;
max-height: 90vh;
/*
max-width: 400px;
min-width: 400px;
*/
border: 1px solid black;
display: inline-block;
}
#preview {
display: block;
max-width: 150px;
position: absolute;
top: 0;
right: 0;
z-index: 1;
}

View File

@@ -25,14 +25,18 @@ class Config {
static getUUID() { static getUUID() {
var uuid = Config.getCookie('uuid'); var uuid = Config.getCookie('uuid');
if (!uuid) { if (!uuid) {
uuid = Config.createUUID(); Config.newUUID();
Config.setCookie('uuid', uuid);
} }
Log.info("uuid", uuid);
return uuid; return uuid;
} }
static newUUID() {
var uuid = Config.createUUID();
Config.setCookie('uuid', uuid);
}
static createUUID() { static createUUID() {
return "" + new Date();
function s4() { function s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
} }
@@ -52,7 +56,7 @@ Config.iceConfig = {
'iceTransportPolicy': 'relay', 'iceTransportPolicy': 'relay',
}; };
class View { class Log {
static write(foo, color) { static write(foo, color) {
var msg = [].slice.call(arguments[2]).join(" "); var msg = [].slice.call(arguments[2]).join(" ");
foo(msg); foo(msg);
@@ -61,32 +65,19 @@ class View {
} }
static info() { static info() {
View.write(rconsole.info, "gray", arguments); Log.write(rconsole.info, "gray", arguments);
} }
static warn() { static warn() {
View.write(rconsole.warn, "orange", arguments); Log.write(rconsole.warn, "orange", arguments);
} }
static log() { static log() {
View.write(rconsole.log, "black", arguments); Log.write(rconsole.log, "black", arguments);
} }
static error() { static error() {
View.write(rconsole.error, "red", arguments); Log.write(rconsole.error, "red", arguments);
}
}
class Remote {
element = null;
constructor(id, stream) {
document.getElementById("remote").innerHTML += `
<video id="${id}" autoplay ></video>
`;
this.element = document.getElementById(id);
this.element.srcObject = stream;
View.log(this.element);
} }
} }
@@ -97,62 +88,61 @@ class Preview {
this.element = document.getElementById(id); this.element = document.getElementById(id);
this.element.srcObject = stream; this.element.srcObject = stream;
} }
}
class WS {
constructor(address, cb) {
Log.warn("address", address);
WS.ws = new WebSocket(address);
WS.ws.onmessage = cb;
}
}
WS.ws = null;
class Remote {
element = null;
constructor(id, stream) {
document.getElementById("remote").innerHTML += `
<video id="${id}" autoplay ></video>
`;
this.element = document.getElementById(id);
this.element.srcObject = stream;
}
destructor() { destructor() {
Log.log("destructor called");
this.element.remove(); this.element.remove();
} }
} }
class Server { class RTC {
server = null; conn = null;
constructor(address, cb) {
Server.server = new WebSocket(address);
Server.server.onmessage = cb;
}
}
class Peer {
peer = null;
streams = {}; streams = {};
// TODO issue: singleton for all feeds ever;; 1 peer = 1 server conn
constructor(stream) { constructor(stream) {
this.peer = new RTCPeerConnection(Config.iceConfig); this.conn = new RTCPeerConnection(Config.iceConfig);
this.peer.onicecandidate = (event) => { this.conn.onicecandidate = (event) => {
if(event.candidate != null) { if (event.candidate != null) {
Server.server.send(JSON.stringify({'ice': event.candidate, 'uuid': Config.getUUID()})); WS.ws.send(JSON.stringify({'ice': event.candidate, 'uuid': Config.getUUID()}));
} }
}; };
this.peer.ontrack = (event) => { this.conn.ontrack = (event) => {
event.streams.forEach((stream) => { event.streams.forEach((stream) => {
this.streams[stream.id] = new Remote(stream.id, stream); this.streams[stream.id] = new Remote(stream.id, stream);
}); });
View.log("/ontrack:", this.streams);
}; };
this.peer.onremovestream = (event) => { this.conn.addStream(stream);
event.streams.forEach((stream) => {
this.streams[stream.id].destructor();
delete this.streams[stream.id];
});
View.log("/onremovestream:", this.streams);
};
this.peer.onconnectionstatechange = (event) => {
View.log("/onconnectionstatechange:", this.streams, this.peer.connectionState);
};
this.peer.addStream(stream);
} }
offer(cb) { offer(cb) {
this.peer this.conn
.createOffer({ .createOffer({
'iceRestart': true, 'iceRestart': true,
'voiceActivityDetection': true, 'voiceActivityDetection': true,
}) })
.then(cb) .then(cb)
.catch(View.error); .catch(Log.error);
}
destructor() {
this.peer.close();
} }
} }
@@ -162,48 +152,51 @@ class Entropy {
} }
static start(isCaller) { static start(isCaller) {
Entropy.peer = new Peer(Entropy.local.stream); Entropy.rtc = new RTC(Entropy.local.stream);
if(isCaller) { if (isCaller) {
Entropy.peer.offer(Entropy.createdDescription); Entropy.rtc.offer(Entropy.createdDescription);
} }
} }
static gotMessageFromServer(message) { static gotMessageFromServer(message) {
if(!Entropy.peer || !Entropy.peer.peer) Entropy.start(false); if (!Entropy.rtc || !Entropy.rtc.conn) Entropy.start(false);
var signal = JSON.parse(message.data); var signal = JSON.parse(message.data);
// Ignore messages from ourself // Ignore messages from ourself
if(signal.uuid == Config.getUUID()) return; if (signal.uuid == Config.getUUID()) return;
if(signal.sdp) { if (signal.joined)
Entropy.peer.peer.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() { Log.info("a challenjour has appeared", JSON.stringify(signal));
if (signal.sdp) {
Entropy.rtc.conn.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
// Only create answers in response to offers // Only create answers in response to offers
if(signal.sdp.type == 'offer') { if (signal.sdp.type == 'offer') {
Entropy.peer.peer.createAnswer().then(Entropy.createdDescription).catch(View.error); Entropy.rtc.conn.createAnswer().then(Entropy.createdDescription).catch(Log.error);
} }
}).catch(View.error); }).catch(Log.error);
} else if(signal.ice) { } else if (signal.ice) {
Entropy.peer.peer.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(View.error); Entropy.rtc.conn.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(Log.error);
} }
} }
static createdDescription(description) { static createdDescription(description) {
View.log('got description'); Log.log('got description');
Entropy.peer.peer Entropy.rtc.conn
.setLocalDescription(description) .setLocalDescription(description)
.then(function() { .then(function() {
Server.server.send(JSON.stringify({ WS.ws.send(JSON.stringify({
'sdp': Entropy.peer.peer.localDescription, 'sdp': Entropy.rtc.conn.localDescription,
'uuid': Config.getUUID(), 'uuid': Config.getUUID(),
})); }));
}) })
.catch(View.error); .catch(Log.error);
} }
} }
Entropy.local = null; Entropy.local = null;
Entropy.peer = null; Entropy.rtc = null;
class Local { class Local {
stream = null; stream = null;
@@ -213,7 +206,7 @@ class Local {
case "audio": return new Audio(); case "audio": return new Audio();
case "video": return new Video(); case "video": return new Video();
case "text": return new Text(); case "text": return new Text();
default: View.error("unknown build", type); default: Log.error("unknown build", type);
} }
} }
@@ -222,8 +215,8 @@ class Local {
video: this.gettype() == "video", video: this.gettype() == "video",
audio: this.gettype() == "audio", audio: this.gettype() == "audio",
}; };
if(!navigator.mediaDevices.getUserMedia) { if (!navigator.mediaDevices.getUserMedia) {
View.error('Your browser does not support getUserMedia API'); Log.error('Your browser does not support getUserMedia API');
} }
navigator.mediaDevices.getUserMedia(constraints) navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => { .then((stream) => {
@@ -232,11 +225,11 @@ class Local {
} }
this.stream = stream; this.stream = stream;
}) })
.catch(View.error); .catch(Log.error);
} }
gettype() { gettype() {
View.error("not impl"); Log.error("not impl");
} }
start() {} start() {}
@@ -263,17 +256,17 @@ var streams = {}
var rconsole = console; var rconsole = console;
var console = {} var console = {}
for (var i in ["log", "info", "warn", "error"]) { for (var i in ["log", "info", "warn", "error"]) {
console[i] = View[i]; console[i] = Log[i];
} }
window.console = console; window.console = console;
function pageReady() { function pageReady() {
Config.getUUID(); //Config.newUUID();
var host = window.location.hostname; var host = window.location.hostname;
if (window.location.port) { if (window.location.port) {
host += ":" + window.location.port; host += ":" + window.location.port;
} }
new Server('wss://' + host + '/abc', Entropy.gotMessageFromServer); new WS('wss://' + host + '/ws/' + window.location.pathname.split("/").slice(-1).pop(), Entropy.gotMessageFromServer);
Entropy.pageReady(); Entropy.pageReady();
} }

View File

@@ -1,142 +0,0 @@
var localVideo;
var localStream;
var remoteVideo;
var peerConnection;
var uuid;
var serverConnection;
var peerConnectionConfig = {
'iceServers': [
{'urls': 'stun:stun.stunprotocol.org:3478'},
{'urls': 'stun:stun.l.google.com:19302'},
]
};
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (1*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
function pageReady() {
uuid = getCookie("uuid");
if (!uuid) {
uuid = createUUID();
setCookie("uuid", uuid);
}
localVideo = document.getElementById('localVideo');
remoteVideo = document.getElementById('remoteVideo');
serverConnection = new WebSocket('wss://' + window.location.hostname + '/abc?uuid=' + uuid);
serverConnection.onmessage = gotMessageFromServer;
var constraints = {
video: true,
audio: true,
};
if(navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia(constraints).then(getUserMediaSuccess).catch(errorHandler);
} else {
alert('Your browser does not support getUserMedia API');
}
}
function getUserMediaSuccess(stream) {
localStream = stream;
localVideo.srcObject = stream;
}
function start(isCaller) {
peerConnection = new RTCPeerConnection(peerConnectionConfig);
peerConnection.onicecandidate = gotIceCandidate;
peerConnection.ontrack = gotRemoteStream;
peerConnection.addStream(localStream);
if(isCaller) {
peerConnection
.createOffer({
'iceRestart': true,
'voiceActivityDetection': true,
})
.then(createdDescription)
.catch(errorHandler);
}
}
function gotMessageFromServer(message) {
if(!peerConnection) start(false);
var signal = JSON.parse(message.data);
// Ignore messages from ourself
if(signal.uuid == uuid) return;
if(signal.sdp) {
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
// Only create answers in response to offers
if(signal.sdp.type == 'offer') {
peerConnection.createAnswer().then(createdDescription).catch(errorHandler);
}
}).catch(errorHandler);
} else if(signal.ice) {
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
}
}
function gotIceCandidate(event) {
if(event.candidate != null) {
serverConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': uuid}));
}
}
function createdDescription(description) {
console.log('got description');
peerConnection
.setLocalDescription(description)
.then(function() {
serverConnection.send(JSON.stringify({
'sdp': peerConnection.localDescription,
'uuid': uuid,
}));
})
.catch(errorHandler);
}
function gotRemoteStream(event) {
console.log('got remote stream');
remoteVideo.srcObject = event.streams[0];
}
function errorHandler(error) {
console.log(error);
}
// Taken from http://stackoverflow.com/a/105074/515584
// Strictly speaking, it's not a real UUID, but it gets the job done here
function createUUID() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

View File

@@ -1,175 +0,0 @@
class Self {
uuid = null
connections = []
constructor() {
this.connections.push(new Stream(this.id(), {audio: true, video: true}))
}
start(isCaller) {
for(var i of this.connections)
i.start(isCaller)
}
id() {
this.uuid = this.get("uuid")
if (!this.uuid) {
s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
this.uuid = s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
this.set("uuid", this.uuid)
}
return this.uuid
}
get(k) {
var name = k + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
set(k, v) {
var d = new Date();
d.setTime(d.getTime() + (1*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = k + "=" + v + ";" + expires + ";path=/";
}
}
class Stream {
serverConnection = null
localVideo = null
remoteVideo = null
localStream = null
id = null
constructor(id, config) {
if (config.audio) {
id += "a"
}
if (config.video) {
id += "v"
}
this.id = id
this.serverConnection = new WebSocket('wss://' + window.location.hostname + '/abc?uuid=' + this.id)
this.serverConnection.onmessage = (m) => this.gotMessageFromServer(m)
if(navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
.getUserMedia(config)
.then((s) => this.getUserMediaSuccess(s))
.catch((e) => this.errorHandler(e))
} else {
throw new Exception('Your browser does not support getUserMedia API')
}
}
start(isCaller) {
this.peerConnection = new RTCPeerConnection({
'iceServers': [
{'urls': 'stun:stun.stunprotocol.org:3478'},
{'urls': 'stun:stun.l.google.com:19302'},
]
});
this.peerConnection.onicecandidate = (e) => this.gotIceCandidate(e);
this.peerConnection.ontrack = (e) => this.gotRemoteStream(e);
this.peerConnection.addStream(this.localStream);
if(isCaller) {
this.peerConnection
.createOffer({
'iceRestart': true,
'voiceActivityDetection': true,
})
.then((d) => this.createdDescription(d))
.catch((m) => this.errorHandler(m));
}
}
getUserMediaSuccess(stream) {
this.localVideo = document.getElementById('localVideo');
this.remoteVideo = document.getElementById('remoteVideo');
this.localStream = stream;
this.localVideo.srcObject = stream;
}
gotMessageFromServer(message) {
if(!this.peerConnection) start(false);
var signal = JSON.parse(message.data);
// Ignore messages from ourself
if(signal.uuid == this.id) return;
if(signal.sdp) {
this.peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
// Only create answers in response to offers
if(signal.sdp.type == 'offer') {
this.peerConnection
.createAnswer()
.then((d) => this.createdDescription(d))
.catch((e) => this.errorHandler(e));
}
}).catch((e) => this.errorHandler(e));
} else if(signal.ice) {
this.peerConnection
.addIceCandidate(new RTCIceCandidate(signal.ice))
.catch((e) => this.errorHandler(e));
}
}
gotIceCandidate(event) {
if(event.candidate != null) {
this.serverConnection
.send(JSON.stringify({
'ice': event.candidate,
'uuid': this.id,
}));
}
}
createdDescription(description) {
console.log('got description');
this.peerConnection
.setLocalDescription(description)
.then(() => {
this.serverConnection.send(JSON.stringify({
'sdp': this.peerConnection.localDescription,
'uuid': this.id,
}));
})
.catch((e) => this.errorHandler(e));
}
gotRemoteStream(event) {
console.log('got remote stream');
this.remoteVideo.srcObject = event.streams[0];
}
errorHandler(error) {
console.log(error);
}
}
var self
function pageReady() {
self = new Self()
}
function start(isCaller) {
self.start(isCaller)
}

13
server.go Normal file → Executable file
View File

@@ -31,15 +31,20 @@ func New() *Server {
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("base", path.Base(r.URL.Path)) log.Println("base", path.Base(r.URL.Path))
log.Println("dir", path.Dir(r.URL.Path))
if !s.Authorize(w, r) { if !s.Authorize(w, r) {
return return
} }
if path.Ext(r.URL.Path) != "" { if path.Dir(r.URL.Path) == "/ws" {
s.fs.ServeHTTP(w, r)
} else if _, err := os.Stat(path.Join(config().GetString("d"), r.URL.Path[1:])); os.IsNotExist(err) {
s.ws.ServeHTTP(w, r) s.ws.ServeHTTP(w, r)
} else { } else {
s.fs.ServeHTTP(w, r) r.URL.Path = "/" + path.Base(r.URL.Path)
if _, err := os.Stat(path.Join(config().GetString("d"), path.Base(r.URL.Path))); os.IsNotExist(err) {
r.URL.Path = "/"
s.fs.ServeHTTP(w, r)
} else {
s.fs.ServeHTTP(w, r)
}
} }
} }

2
ws.go Normal file → Executable file
View File

@@ -49,7 +49,7 @@ func (ws *WS) serveHTTP(w http.ResponseWriter, r *http.Request) error {
if err != nil { if err != nil {
return err return err
} }
pool.conns.Store(id, conn) pool.Push(id, conn) // conns.Store(id, conn)
for { for {
mt, reader, err := conn.NextReader() mt, reader, err := conn.NextReader()
if err != nil { if err != nil {