-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathchat.go
160 lines (138 loc) · 4.94 KB
/
chat.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Ecca Authentication Proxy
//
// Handles Eccentric Authentication in a web proxy for browsers.
//
// Copyright 2020, Guido Witmond <[email protected]>
// Licensed under AGPL v3 or later. See LICENSE
package main // eccaproxy
import (
"github.com/elazarl/goproxy"
"log"
"bytes"
"net/http"
"net/url"
"io"
"crypto/tls"
"encoding/json"
"time"
"strconv"
)
// received messages are tagged with the token of the connection
type message struct {
Token string
Sender string
Message string
}
var chatChan = make(chan message, 100)
// Chat page polls here for messages
// listen on the chatChan for newly received messages and hand them to the front end.
func handleChatpoll (req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
timeout, err := strconv.Atoi(req.URL.Query().Get("timeout"))
if err != nil || timeout > 180000 || timeout <= 1000 {
timeout = 60000; // milliseconds
}
// Timeout before the client does.
// It compensates for transmission delays
// and prevents races where we send a reply just after the client has timed out.
// That would cause the client to miss an event.
timeout = int(0.95 * float32(timeout))
switch req.Method {
case "GET":
select {
case message := <- chatChan:
log.Printf("pushing message %v", message)
json, err := json.Marshal(message)
check(err)
resp := makeResponse(req, 200, "application/json", bytes.NewBuffer(json))
return nil, resp
case <- time.After(time.Duration(timeout) * time.Millisecond):
resp := makeResponse(req, 404, "text/plain", bytes.NewBuffer([]byte("timeout")))
return nil, resp
}
}
log.Printf("Unexpected method: %#v", req.Method)
time.Sleep(time.Second);
return nil, nil
}
func startWebChatApp(req *http.Request, ctx *goproxy.ProxyCtx, token string, tlsconn *tls.Conn, remoteCN string) (*http.Request, *http.Response) {
// Start a receiver to listen for messages over the tls connection
go chatReceiver(token, tlsconn, remoteCN, chatChan)
// redirect the user to the chat page, on the current host and scheme.
reqUrl := req.URL
redirectURL := url.URL{Scheme: reqUrl.Scheme, Host: reqUrl.Host, Path: "/chat"}
resp := makeRedirect(req, &redirectURL)
return nil, resp
}
// listen on the tlsConnetion and send all incoming messages to the chat-channel
// tag each message with the token.
func chatReceiver(token string, tlsconn *tls.Conn, remoteCN string, channel chan message) {
buffer := make([]byte, 1000)
stop := false
for !stop {
log.Printf("receiver waiting for tlsconn")
size, err := tlsconn.Read(buffer)
log.Printf("receiver received %v bytes", size)
switch err {
case io.EOF:
stop = true
case nil:
channel <- message{token, remoteCN, string(buffer[:size])}
}
}
// tls connection said EOF (or some other error), close it.
tlsconn.Close()
delete(active_calls, token)
}
// Users post their messages here for delivery to the other side
// Upon sending, copy into the chatChan for reflection
func handleChatPostPath (req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
//log.Printf("ChatPost has req: %#v", req)
switch req.Method {
case "POST":
req.ParseForm()
token := req.Form.Get("token")
messageText := req.Form.Get("message")
if token == "" {
log.Printf("Error: No <token> given. Reject request.\n")
return nil, nil // TODO: improve error reporting
}
// test for active chat
caller, exists := active_calls[token]
if !exists {
log.Printf("Error: token not found. Reject request.\n")
return nil, nil // TODO: improve error reporting
}
size, err := caller.Tlsconn.Write([]byte(messageText))
switch err {
case nil: // send ok
log.Printf("send successful")
chatChan <- message{token, "Me", messageText}
resp := makeResponse(req, 200, "application/json", bytes.NewBuffer([]byte("[]")))
return nil, resp
default: // some error happened
log.Printf("ChatPost got error: %v")
log.Printf("%v of %v bytes did get sent", size, len(messageText))
caller.Tlsconn.Close()
delete(active_calls, token)
resp := makeResponse(req, 500, "application/json", bytes.NewBuffer([]byte("[]")))
return nil, resp
}
}
log.Printf("Unexpected method: %#v", req.Method)
time.Sleep(time.Second);
return nil, nil
}
// Create the /chat page.
// Populate it with a section for each active chat.
func handleChat(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
// collect the tokens for accepted chats.
tokens := []string{}
for token, caller := range active_calls {
if caller.App == "chat" {
tokens = append(tokens, token)
}
}
buf := execTemplate(templates, "chat.html", map[string]interface{} { "tokens": tokens })
resp := makeResponse(req, 200, "text/html", buf)
return nil, resp
}