-
Notifications
You must be signed in to change notification settings - Fork 29
/
chat-example.kt
152 lines (131 loc) · 4.67 KB
/
chat-example.kt
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
import com.athaydes.rawhttp.duplex.MessageHandler
import com.athaydes.rawhttp.duplex.MessageSender
import com.athaydes.rawhttp.duplex.RawHttpDuplex
import com.athaydes.rawhttp.duplex.port
import rawhttp.core.EagerHttpResponse
import rawhttp.core.RawHttp
import rawhttp.core.body.StringBody
import rawhttp.core.server.TcpRawHttpServer
import java.io.IOException
import java.net.URLEncoder
import java.util.Optional
import java.util.Scanner
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
val http = RawHttp()
val duplex = RawHttpDuplex()
object ChatServer {
val clientByName = mutableMapOf<String, ChatHandler>()
class ChatHandler(val name: String, val sender: MessageSender) : MessageHandler {
init {
clientByName[name] = this
tellOthers("$name joined...")
}
override fun onTextMessage(message: String) {
println("$name says: $message")
tellOthers("$name: $message")
}
override fun onClose() {
tellOthers("$name left!")
clientByName.remove(name)
}
private fun tellOthers(message: String) {
clientByName.values.forEach {
if (this != it) {
it.sender.sendTextMessage(message)
}
}
}
}
fun runServer() {
val server = TcpRawHttpServer(port)
server.start { request ->
println(request)
if (request.method == "POST") {
val name = request.uri.path.substring(1)
return@start if (name.isEmpty() || name.contains("/")) {
Optional.of(Responses.notFound)
} else {
println("Accepting $name to chatroom")
Optional.of(duplex.accept(request) { sender ->
ChatHandler(name, sender)
})
}
} else {
Optional.of(Responses.methodNotAllowed)
}
}
}
object Responses {
val methodNotAllowed: EagerHttpResponse<Void> = http.parseResponse(
"""
HTTP/1.1 405 Method Not Allowed
Allow: POST
""".trimIndent()).eagerly()
val notFound: EagerHttpResponse<Void> = http.parseResponse(
"""
HTTP/1.1 404 Not Found
""".trimIndent())
.withBody(StringBody("Resource not found.\n" +
"To enter the chat, use 'POST /your-name'")).eagerly()
}
}
object ChatClient {
val scanner = Scanner(System.`in`)
fun runClient() {
print("Welcome! What's your name?\n> ")
val name = URLEncoder.encode(scanner.nextLine(), "UTF-8")
val running = AtomicBoolean(true)
val executor = Executors.newSingleThreadExecutor()
try {
duplex.connect(http.parseRequest("POST http://localhost:$port/$name")) { sender ->
object : MessageHandler, Runnable {
init {
executor.submit(this)
}
override fun run() {
while (running.get()) {
prompt()
val message = scanner.nextLine()
if (message == "quit") {
sender.close()
println("Goodbye!")
running.set(false)
} else {
sender.sendTextMessage(message)
}
}
}
fun prompt() {
print("$name: ")
}
override fun onTextMessage(message: String) {
println()
println(message)
prompt()
}
override fun onClose() {
running.set(false)
executor.shutdown()
}
}
}
} catch (e: IOException) {
e.printStackTrace()
running.set(false)
executor.shutdownNow()
}
}
}
fun main(args: Array<String>) {
val serverMode = args.size == 1 && args[0] == "server"
if (args.size > 1 || args.size == 1 && args[0] !in setOf("client", "server")) {
System.err.println("One argument is acceptable: 'server' or 'client'. 'client' is the default")
System.exit(1)
}
if (serverMode) {
ChatServer.runServer()
} else {
ChatClient.runClient()
}
}