summaryrefslogtreecommitdiff
path: root/game_server
diff options
context:
space:
mode:
authorDennis Kobert <d-kobert@web.de>2019-06-11 23:38:13 +0200
committerDennis Kobert <d-kobert@web.de>2019-06-11 23:38:13 +0200
commit2fa4a0e50ebfc97059c8b84dbd17e79f9afc8a8d (patch)
treec3b34ccb2737e347a73768536895cbbaab13cc01 /game_server
parentec991104f56e90d7bb2878da2fe6ed4e585dfc46 (diff)
parentaf74efccf8d21e6151022b71f3cacd3fa83024ee (diff)
Merge branch 'rework-backend'
Diffstat (limited to 'game_server')
-rw-r--r--game_server/Cargo.toml13
-rwxr-xr-xgame_server/build.sh21
-rw-r--r--game_server/err73
-rw-r--r--game_server/rbuild.sh2
-rw-r--r--game_server/src/backend_connection.rs31
-rw-r--r--game_server/src/gameserver.rs147
-rw-r--r--game_server/src/group.rs8
-rw-r--r--game_server/src/lobby.rs35
-rw-r--r--game_server/src/main.rs20
-rw-r--r--game_server/src/test_group.rs28
-rw-r--r--game_server/src/ws_test.html66
11 files changed, 444 insertions, 0 deletions
diff --git a/game_server/Cargo.toml b/game_server/Cargo.toml
new file mode 100644
index 0000000..97c0e77
--- /dev/null
+++ b/game_server/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "game-server"
+version = "0.1.0"
+authors = ["natrixaeria", "truedoctor"]
+edition = "2018"
+description = "A general game server for connections to web clients. Currently (on the way to) deploying a skribbl.io like game."
+
+[dependencies]
+log = "0.4"
+pretty_env_logger = "0.3"
+reqwest = "0.9"
+websocket = "0.22"
+hyper = "0.10"
diff --git a/game_server/build.sh b/game_server/build.sh
new file mode 100755
index 0000000..1eb61a1
--- /dev/null
+++ b/game_server/build.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env sh
+
+case $1 in
+ ("")
+ if rustup run stable cargo --color always build; then
+ echo build success!
+ RUST_LOG=debug target/debug/game-server
+ else
+ echo build failed!
+ fi
+ ;;
+ -r)
+ sh build.sh &> err && cat err | tac
+ ;;
+ -c)
+ rustup run stable cargo clean
+ ;;
+ *)
+ echo invalid argument
+ ;;
+esac
diff --git a/game_server/err b/game_server/err
new file mode 100644
index 0000000..6ca8a6f
--- /dev/null
+++ b/game_server/err
@@ -0,0 +1,73 @@
+ Compiling game-server v0.1.0 (/home/jan/projects/DiscoBot/game_server)
+error[E0277]: the trait bound `(): futures::future::Future` is not satisfied
+ --> src/backend_connection.rs:32:24
+ |
+32 |  hyper::rt::run(hyper::rt::lazy(|| {
+ |  ^^^^^^^^^^^^^^^ the trait `futures::future::Future` is not implemented for `()`
+ |
+ = note: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`
+ = note: required by `futures::future::lazy::lazy`
+
+error[E0599]: no method named `wait` found for type `std::result::Result<futures::future::map_err::MapErr<futures::future::map::Map<hyper::client::ResponseFuture, [closure@src/backend_connection.rs:54:34: 54:68]>, [closure@src/backend_connection.rs:55:38: 55:73]>, http::uri::InvalidUri>` in the current scope
+ --> src/backend_connection.rs:56:24
+ |
+56 |  }).wait();
+ |  ^^^^
+ |
+ = note: the method `wait` exists but the following trait bounds were not satisfied:
+ `&mut std::result::Result<futures::future::map_err::MapErr<futures::future::map::Map<hyper::client::ResponseFuture, [closure@src/backend_connection.rs:54:34: 54:68]>, [closure@src/backend_connection.rs:55:38: 55:73]>, http::uri::InvalidUri> : futures::future::Future`
+
+error[E0308]: mismatched types
+ --> src/backend_connection.rs:58:17
+ |
+58 |  res
+ |  ^^^ expected (), found enum `std::result::Result`
+ |
+ = note: expected type `()`
+ found type `std::result::Result<http::response::Response<hyper::body::body::Body>, http::uri::InvalidUri>`
+
+error[E0277]: the trait bound `(): futures::future::Future` is not satisfied
+ --> src/backend_connection.rs:32:24
+ |
+32 |   hyper::rt::run(hyper::rt::lazy(|| {
+ |  ________________________^
+33 | |  let client = hyper::Client::builder()
+34 | |  .build::<_, hyper::Body>(
+35 | |  HttpsConnector::new(4).unwrap()
+... |
+59 | |  }
+60 | |  }));
+ | |__________^ the trait `futures::future::Future` is not implemented for `()`
+ |
+ = note: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`
+ = note: required by `futures::future::lazy::Lazy`
+
+error[E0277]: the trait bound `(): futures::future::Future` is not satisfied
+ --> src/backend_connection.rs:32:9
+ |
+32 |  hyper::rt::run(hyper::rt::lazy(|| {
+ |  ^^^^^^^^^^^^^^ the trait `futures::future::Future` is not implemented for `()`
+ |
+ = note: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`
+ = note: required by `hyper::rt::run`
+
+error[E0063]: missing field `res_receiver` in initializer of `backend_connection::BackendConnection`
+ --> src/backend_connection.rs:62:9
+ |
+62 |  BackendConnection {
+ |  ^^^^^^^^^^^^^^^^^ missing `res_receiver`
+
+error[E0609]: no field `request_sender` on type `&backend_connection::BackendConnection`
+ --> src/backend_connection.rs:69:14
+ |
+69 |  self.request_sender.send(
+ |  ^^^^^^^^^^^^^^
+
+error: aborting due to 7 previous errors
+
+Some errors have detailed explanations: E0063, E0277, E0308, E0599, E0609.
+For more information about an error, try `rustc --explain E0063`.
+error: Could not compile `game-server`.
+
+To learn more, run the command again with --verbose.
+build failed!
diff --git a/game_server/rbuild.sh b/game_server/rbuild.sh
new file mode 100644
index 0000000..22b10b5
--- /dev/null
+++ b/game_server/rbuild.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+sh build.sh &> err && cat err | tac
diff --git a/game_server/src/backend_connection.rs b/game_server/src/backend_connection.rs
new file mode 100644
index 0000000..9307c4a
--- /dev/null
+++ b/game_server/src/backend_connection.rs
@@ -0,0 +1,31 @@
+use reqwest::{Response, Client, Url, UrlError, Error as ReqError};
+
+pub struct BackendConnection {
+ host: String,
+ client: Client,
+ last_response: Option<Result<Response, ReqError>>
+}
+
+impl BackendConnection {
+ pub fn new(host: &str) -> Self {
+ BackendConnection {
+ host: host.to_string(),
+ client: Client::new(),
+ last_response: None
+ }
+ }
+
+ pub fn request(&mut self, location: &str) -> Result<(), UrlError> {
+ Ok(self.last_response =
+ Some(self.client.get(Url::parse(&format!("{}{}", self.host, location))?)
+ .send()))
+ }
+
+ pub fn get_response(&self) -> &Option<Result<Response, ReqError>> {
+ &self.last_response
+ }
+
+ pub fn host_name<'a>(&'a self) -> &'a str {
+ &self.host
+ }
+}
diff --git a/game_server/src/gameserver.rs b/game_server/src/gameserver.rs
new file mode 100644
index 0000000..9334a27
--- /dev/null
+++ b/game_server/src/gameserver.rs
@@ -0,0 +1,147 @@
+use websocket::{OwnedMessage,
+ sync::Server,
+ client::sync::Client,
+ server::{NoTlsAcceptor, InvalidConnection,
+ sync::AcceptResult}};
+use std::net::{SocketAddr, ToSocketAddrs, TcpStream};
+use std::sync::mpsc;
+use std::sync::mpsc::{Sender, Receiver};
+use super::lobby::Lobby;
+use super::backend_connection::BackendConnection;
+
+const PROTOCOL: &str = "tuesday";
+
+type Token = u32;
+
+#[derive(Debug)]
+pub enum GameServerError {
+ BindError(std::io::Error),
+ HandshakeRequestError,
+ InvalidProtocolError,
+ AcceptError(std::io::Error)
+}
+
+pub struct GameServer {
+ addr: SocketAddr,
+ lobby: Lobby,
+ backend: BackendConnection,
+}
+
+pub struct GameClient {
+ addr: SocketAddr,
+ client: Client<TcpStream>,
+}
+
+impl GameClient {
+ fn from_raw(client: Client<TcpStream>) -> Result<Self, ()> {
+ let addr = client.peer_addr().map_err(|_| ())?;
+ info!("got a client connection from: {}", addr);
+ Ok(GameClient {
+ addr,
+ client,
+ })
+ }
+
+ fn require_token(&mut self) -> Option<Token> {
+ let message = self.client
+ .recv_message()
+ .ok()?;
+ if let OwnedMessage::Text(text) = message {
+ text.parse::<Token>().ok()
+ } else {
+ None
+ }
+ }
+}
+
+type ClientConnection = Result<GameClient, GameServerError>;
+
+impl GameServer {
+ pub fn new<T: ToSocketAddrs>(addr: T) -> Self {
+ let addr = addr.to_socket_addrs().unwrap().next().unwrap();
+ debug!("ws address: {}", addr);
+ info!("create lobby");
+ let lobby = Lobby::new();
+ let backend = BackendConnection::new("https://kobert.dev");
+ info!("got a C# backend connection");
+ GameServer {
+ addr,
+ lobby,
+ backend,
+ }
+ }
+
+ pub fn run(&self) -> Result<(), GameServerError> {
+ let reader = self.read_clients();
+ loop {
+ let mut connection = reader.recv().unwrap()?;
+ self.add_client(connection);
+ }
+ Ok(())
+ }
+
+ fn add_client(&self, mut client: GameClient) {
+ std::thread::spawn(move || {
+ println!("Token: {:?}", client.require_token());
+ loop { std::thread::sleep(std::time::Duration::from_millis(100)); }
+ });
+ }
+
+ fn read_clients(&self) -> Receiver<ClientConnection> {
+ let (s, r): (Sender<ClientConnection>, Receiver<ClientConnection>)
+ = mpsc::channel();
+ let addr = self.addr;
+ std::thread::spawn(move || {
+ let result = Self::handle_requests(addr, &s).or_else(|e| s.send(Err(e)));
+ });
+ r
+ }
+
+ fn handle_requests(addr: SocketAddr, s: &Sender<ClientConnection>) -> Result<(), GameServerError> {
+ let server = match Server::<NoTlsAcceptor>::bind(addr) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("websocket binding error");
+ Err(GameServerError::BindError(e))?
+ },
+ };
+ info!("webserver is being launched");
+ for req in server {
+ s.send(Ok(Self::handle_request(req)?)).unwrap();
+ }
+ info!("webserver is being shut down");
+ Ok(())
+ }
+
+ fn handle_request(req: AcceptResult<TcpStream>) -> ClientConnection {
+ match req {
+ Ok(req) => {
+ if !req.protocols().contains(&PROTOCOL.to_string()) {
+ warn!("a client tried to connect without {} protocol", PROTOCOL);
+ req.reject().unwrap();
+ Err(GameServerError::InvalidProtocolError)
+ } else {
+ match req.use_protocol(PROTOCOL).accept() {
+ Ok(client) => {
+ match GameClient::from_raw(client) {
+ Ok(client) => Ok(client),
+ Err(_) => {
+ error!("could not create a client");
+ Err(GameServerError::HandshakeRequestError)
+ }
+ }
+ },
+ Err((_, e)) => {
+ warn!("client handshake failed");
+ Err(GameServerError::AcceptError(e))
+ }
+ }
+ }
+ },
+ Err(e) => {
+ warn!("invalid client request");
+ Err(GameServerError::HandshakeRequestError)
+ }
+ }
+ }
+}
diff --git a/game_server/src/group.rs b/game_server/src/group.rs
new file mode 100644
index 0000000..55e4fbf
--- /dev/null
+++ b/game_server/src/group.rs
@@ -0,0 +1,8 @@
+pub type GroupId = u32;
+
+pub trait Group {
+ fn id(&self) -> GroupId;
+ fn name(&self) -> String;
+
+ fn run(&self);
+}
diff --git a/game_server/src/lobby.rs b/game_server/src/lobby.rs
new file mode 100644
index 0000000..fe3bdee
--- /dev/null
+++ b/game_server/src/lobby.rs
@@ -0,0 +1,35 @@
+use std::collections::HashMap;
+
+use super::group::{Group, GroupId};
+
+pub struct Lobby {
+ groups: HashMap<GroupId, Box<Group>>,
+}
+
+impl Lobby {
+ pub fn new() -> Lobby {
+ Self {
+ groups: HashMap::new(),
+ }
+ }
+
+ pub fn add_group(&mut self, group: Box<Group>) {
+ self.groups.insert(group.id(), group);
+ }
+
+ pub fn iter<'a>(&'a self) -> GroupIterator<'a> {
+ GroupIterator { groups: self.groups.values() }
+ }
+}
+
+pub struct GroupIterator<'a> {
+ groups: std::collections::hash_map::Values<'a, GroupId, Box<Group>>
+}
+
+impl<'a> Iterator for GroupIterator<'a> {
+ type Item = &'a Box<Group>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.groups.next()
+ }
+}
diff --git a/game_server/src/main.rs b/game_server/src/main.rs
new file mode 100644
index 0000000..e129283
--- /dev/null
+++ b/game_server/src/main.rs
@@ -0,0 +1,20 @@
+mod group;
+mod test_group;
+mod lobby;
+mod gameserver;
+mod backend_connection;
+
+#[macro_use] extern crate log;
+use pretty_env_logger;
+
+use backend_connection::BackendConnection;
+
+fn main() {
+ pretty_env_logger::init();
+
+ let addr = ("127.0.0.1", 5001);
+ info!("create game server on {:?}", addr);
+ let gameserver = gameserver::GameServer::new(addr);
+ gameserver.run().unwrap();
+
+}
diff --git a/game_server/src/test_group.rs b/game_server/src/test_group.rs
new file mode 100644
index 0000000..bd570e3
--- /dev/null
+++ b/game_server/src/test_group.rs
@@ -0,0 +1,28 @@
+use super::group::{Group, GroupId};
+
+pub struct TestGroup {
+ id: GroupId,
+ name: String,
+}
+
+impl Group for TestGroup {
+ fn id(&self) -> GroupId {
+ self.id
+ }
+
+ fn name(&self) -> String {
+ self.name.clone()
+ }
+
+ fn run(&self) {
+ let id = self.id;
+ let name = self.name.to_owned();
+ std::thread::spawn(move || /*loop { println!("> group nr.{} wishes you: '{}'", id, name) }*/());
+ }
+}
+
+impl TestGroup {
+ pub fn new(id: GroupId, name: String) -> Self {
+ TestGroup { id, name }
+ }
+}
diff --git a/game_server/src/ws_test.html b/game_server/src/ws_test.html
new file mode 100644
index 0000000..ea259b7
--- /dev/null
+++ b/game_server/src/ws_test.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<html>
+ <head>
+ <title>WS Test</title>
+ <style>
+.b {
+ border-bottom: 1px solid black;
+}
+ </style>
+ </head>
+ <body style='background: black; color: white'>
+ <div id='cons'>connected</div><br>
+ <button onclick='test_connection()'>Launch</button><br>
+ <span>Server address: </span><input id='addr'></input>
+ <div id='chat' style='background: rgb(20, 20, 20); padding-left: 20px; margin: 40px' />
+ </body>
+ <script>
+function get_addr() {
+ return document.getElementById('addr').value;
+}
+
+function test_connection() {
+ let a = 'ws://' + get_addr();
+ add_text('create a new connection at "' + a + '"');
+ const ws = new WebSocket(a, 'tuesday');
+ ws.addEventListener('open', function (event) {
+ add_text('connection established');
+ toggle_connected(true);
+ ws.send('1230123');
+ });
+ ws.addEventListener('error', function (event) {
+ add_text('ws error occured: "' + event + '"');
+ toggle_connected(false);
+ });
+ ws.addEventListener('close', function (event) {
+ add_text('ws is closed now');
+ toggle_connected(false);
+ });
+ ws.addEventListener('message', function (event) {
+ add_text('got ws message: ' + event.data);
+ });
+}
+
+function add_text(text, color='white') {
+ let c = document.getElementById('chat');
+ let n = document.createElement('span');
+ n.setAttribute('class', 'b');
+ n.style = 'color: ' + color;
+ n.textContent = (new Date()).toTimeString().substring(0, 8) + '|> '+ text;
+ c.appendChild(n);
+ c.appendChild(document.createElement('br'));
+}
+function toggle_connected(con) {
+ let c = document.getElementById('cons');
+ if (con) {
+ c.style = 'background: green'
+ c.textContent = 'connected';
+ } else {
+ c.style = 'background: red'
+ c.textContent = 'not connected';
+ }
+}
+toggle_connected(false);
+add_text("JS loaded");
+ </script>
+</html>