Move Rojo server into root of the repository

This commit is contained in:
Lucien Greathouse
2019-08-27 16:56:52 -07:00
parent ec9afba029
commit 6f7dbe99fe
48 changed files with 50 additions and 54 deletions

145
src/web/api.rs Normal file
View File

@@ -0,0 +1,145 @@
//! Defines Rojo's HTTP API, all under /api. These endpoints generally return
//! JSON.
use std::{collections::HashSet, sync::Arc};
use futures::{future, Future};
use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
use rbx_dom_weak::RbxId;
use serde::{Deserialize, Serialize};
use crate::{serve_session::ServeSession, session_id::SessionId};
const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
const PROTOCOL_VERSION: u64 = 3;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerInfoResponse<'a> {
pub session_id: SessionId,
pub server_version: &'a str,
pub protocol_version: u64,
pub expected_place_ids: Option<HashSet<u64>>,
// pub root_instance_id: RbxId,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReadResponse {
pub session_id: SessionId,
// pub message_cursor: u32,
// pub instances: HashMap<RbxId, InstanceWithMetadata<'a>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubscribeResponse {
pub session_id: SessionId,
// pub message_cursor: u32,
// pub messages: Cow<'a, [InstanceChanges]>,
}
fn response_json<T: serde::Serialize>(value: T) -> Response<Body> {
let serialized = match serde_json::to_string(&value) {
Ok(v) => v,
Err(err) => {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header(header::CONTENT_TYPE, "text/plain")
.body(Body::from(err.to_string()))
.unwrap();
}
};
Response::builder()
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(serialized))
.unwrap()
}
pub struct ApiService {
serve_session: Arc<ServeSession>,
}
impl Service for ApiService {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
type Future =
Box<dyn Future<Item = hyper::Response<Self::ReqBody>, Error = Self::Error> + Send>;
fn call(&mut self, request: hyper::Request<Self::ReqBody>) -> Self::Future {
let response = match (request.method(), request.uri().path()) {
(&Method::GET, "/api/rojo") => self.handle_api_rojo(),
(&Method::GET, path) if path.starts_with("/api/read/") => self.handle_api_read(request),
(&Method::GET, path) if path.starts_with("/api/subscribe/") => {
return self.handle_api_subscribe(request);
}
_ => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap(),
};
Box::new(future::ok(response))
}
}
impl ApiService {
pub fn new(serve_session: Arc<ServeSession>) -> ApiService {
ApiService { serve_session }
}
/// Get a summary of information about the server
fn handle_api_rojo(&self) -> Response<Body> {
response_json(&ServerInfoResponse {
server_version: SERVER_VERSION,
protocol_version: PROTOCOL_VERSION,
session_id: self.serve_session.session_id(),
expected_place_ids: self.serve_session.serve_place_ids().map(Clone::clone),
})
}
/// Retrieve any messages past the given cursor index, and if
/// there weren't any, subscribe to receive any new messages.
fn handle_api_subscribe(&self, request: Request<Body>) -> <ApiService as Service>::Future {
let argument = &request.uri().path()["/api/subscribe/".len()..];
let _cursor: u32 = match argument.parse() {
Ok(v) => v,
Err(err) => {
return Box::new(future::ok(
Response::builder()
.status(StatusCode::BAD_REQUEST)
.header(header::CONTENT_TYPE, "text/plain")
.body(Body::from(err.to_string()))
.unwrap(),
));
}
};
Box::new(future::ok(response_json(SubscribeResponse {
session_id: self.serve_session.session_id(),
})))
}
fn handle_api_read(&self, request: Request<Body>) -> Response<Body> {
let argument = &request.uri().path()["/api/read/".len()..];
let requested_ids: Option<Vec<RbxId>> = argument.split(',').map(RbxId::parse_str).collect();
let _requested_ids = match requested_ids {
Some(id) => id,
None => {
return Response::builder()
.status(StatusCode::BAD_REQUEST)
.header(header::CONTENT_TYPE, "text/plain")
.body(Body::from("Malformed ID list"))
.unwrap();
}
};
response_json(ReadResponse {
session_id: self.serve_session.session_id(),
})
}
}

90
src/web/interface.rs Normal file
View File

@@ -0,0 +1,90 @@
//! Defines the HTTP-based UI. These endpoints generally return HTML and SVG.
use std::sync::Arc;
use futures::{future, Future};
use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
use ritz::html;
use crate::serve_session::ServeSession;
const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
static HOME_CSS: &str = include_str!("../../assets/index.css");
pub struct InterfaceService {
#[allow(unused)] // TODO: Fill out interface service
serve_session: Arc<ServeSession>,
}
impl Service for InterfaceService {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
type Future = Box<dyn Future<Item = Response<Self::ReqBody>, Error = Self::Error> + Send>;
fn call(&mut self, request: Request<Self::ReqBody>) -> Self::Future {
let response = match (request.method(), request.uri().path()) {
(&Method::GET, "/") => self.handle_home(),
(&Method::GET, "/visualize/rbx") => self.handle_visualize_rbx(),
(&Method::GET, "/visualize/imfs") => self.handle_visualize_imfs(),
_ => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap(),
};
Box::new(future::ok(response))
}
}
impl InterfaceService {
pub fn new(serve_session: Arc<ServeSession>) -> InterfaceService {
InterfaceService { serve_session }
}
fn handle_home(&self) -> Response<Body> {
let page = html! {
<html>
<head>
<title>"Rojo"</title>
<style>
{ ritz::UnescapedText::new(HOME_CSS) }
</style>
</head>
<body>
<div class="main">
<h1 class="title">
"Rojo Live Sync is up and running!"
</h1>
<h2 class="subtitle">
"Version " { SERVER_VERSION }
</h2>
<a class="docs" href="https://lpghatguy.github.io/rojo">
"Rojo Documentation"
</a>
</div>
</body>
</html>
};
Response::builder()
.header(header::CONTENT_TYPE, "text/html")
.body(Body::from(format!("<!DOCTYPE html>{}", page)))
.unwrap()
}
fn handle_visualize_rbx(&self) -> Response<Body> {
Response::builder()
.header(header::CONTENT_TYPE, "text/plain")
.body(Body::from("TODO: /visualize/rbx"))
.unwrap()
}
fn handle_visualize_imfs(&self) -> Response<Body> {
Response::builder()
.header(header::CONTENT_TYPE, "text/plain")
.body(Body::from("TODO: /visualize/imfs"))
.unwrap()
}
}

70
src/web/mod.rs Normal file
View File

@@ -0,0 +1,70 @@
mod api;
mod interface;
use std::sync::Arc;
use futures::{
future::{self, FutureResult},
Future,
};
use hyper::{service::Service, Body, Request, Response, Server};
use log::trace;
use crate::serve_session::ServeSession;
use self::{api::ApiService, interface::InterfaceService};
pub struct RootService {
api: api::ApiService,
interface: interface::InterfaceService,
}
impl Service for RootService {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
type Future = Box<dyn Future<Item = Response<Self::ReqBody>, Error = Self::Error> + Send>;
fn call(&mut self, request: Request<Self::ReqBody>) -> Self::Future {
trace!("{} {}", request.method(), request.uri().path());
if request.uri().path().starts_with("/api") {
self.api.call(request)
} else {
self.interface.call(request)
}
}
}
impl RootService {
pub fn new(serve_session: Arc<ServeSession>) -> RootService {
RootService {
api: ApiService::new(Arc::clone(&serve_session)),
interface: InterfaceService::new(Arc::clone(&serve_session)),
}
}
}
pub struct LiveServer {
serve_session: Arc<ServeSession>,
}
impl LiveServer {
pub fn new(serve_session: Arc<ServeSession>) -> LiveServer {
LiveServer { serve_session }
}
pub fn start(self, port: u16) {
let address = ([127, 0, 0, 1], port).into();
let server = Server::bind(&address)
.serve(move || {
let service: FutureResult<_, hyper::Error> =
future::ok(RootService::new(Arc::clone(&self.serve_session)));
service
})
.map_err(|e| eprintln!("Server error: {}", e));
hyper::rt::run(server);
}
}