1 extern crate lemmy_server;
3 extern crate diesel_migrations;
6 use actix_files::NamedFile;
7 use actix_web::web::Query;
9 use actix_web_actors::ws;
10 use lemmy_server::api::community::ListCommunities;
11 use lemmy_server::api::Oper;
12 use lemmy_server::api::Perform;
13 use lemmy_server::api::UserOperation;
14 use lemmy_server::apub;
15 use lemmy_server::db::establish_connection;
16 use lemmy_server::feeds;
17 use lemmy_server::nodeinfo;
18 use lemmy_server::settings::Settings;
19 use lemmy_server::webfinger;
20 use lemmy_server::websocket::server::*;
21 use std::time::{Duration, Instant};
25 /// How often heartbeat pings are sent
26 const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
27 /// How long before lack of client response causes a timeout
28 const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
30 /// Entry point for our route
34 chat_server: web::Data<Addr<ChatServer>>,
35 ) -> Result<HttpResponse, Error> {
38 cs_addr: chat_server.get_ref().to_owned(),
44 .unwrap_or("127.0.0.1:12345")
47 .unwrap_or("127.0.0.1")
56 cs_addr: Addr<ChatServer>,
60 /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
61 /// otherwise we drop connection.
65 impl Actor for WSSession {
66 type Context = ws::WebsocketContext<Self>;
68 /// Method is called on actor start.
69 /// We register ws session with ChatServer
70 fn started(&mut self, ctx: &mut Self::Context) {
71 // we'll start heartbeat process on session start.
74 // register self in chat server. `AsyncContext::wait` register
75 // future within context, but context waits until this future resolves
76 // before processing any other events.
77 // across all routes within application
78 let addr = ctx.address();
82 addr: addr.recipient(),
83 ip: self.ip.to_owned(),
86 .then(|res, act, ctx| {
88 Ok(res) => act.id = res,
89 // something is wrong with chat server
97 fn stopping(&mut self, _ctx: &mut Self::Context) -> Running {
99 self.cs_addr.do_send(Disconnect {
101 ip: self.ip.to_owned(),
107 /// Handle messages from chat server, we simply send it to peer websocket
108 /// These are room messages, IE sent to others in the room
109 impl Handler<WSMessage> for WSSession {
112 fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) {
113 // println!("id: {} msg: {}", self.id, msg.0);
118 /// WebSocket message handler
119 impl StreamHandler<ws::Message, ws::ProtocolError> for WSSession {
120 fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
121 // println!("WEBSOCKET MESSAGE: {:?} from id: {}", msg, self.id);
123 ws::Message::Ping(msg) => {
124 self.hb = Instant::now();
127 ws::Message::Pong(_) => {
128 self.hb = Instant::now();
130 ws::Message::Text(text) => {
131 let m = text.trim().to_owned();
132 println!("WEBSOCKET MESSAGE: {:?} from id: {}", &m, self.id);
136 .send(StandardMessage {
141 .then(|res, _, ctx| {
143 Ok(res) => ctx.text(res),
152 ws::Message::Binary(_bin) => println!("Unexpected binary"),
153 ws::Message::Close(_) => {
162 /// helper method that sends ping to client every second.
164 /// also this method checks heartbeats from client
165 fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
166 ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
167 // check client heartbeats
168 if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
169 // heartbeat timed out
170 println!("Websocket Client heartbeat failed, disconnecting!");
172 // notify chat server
173 act.cs_addr.do_send(Disconnect {
175 ip: act.ip.to_owned(),
181 // don't try to send a ping
191 let _ = env_logger::init();
192 let sys = actix::System::new("lemmy");
194 // Run the migrations from code
195 let conn = establish_connection();
196 embedded_migrations::run(&conn).unwrap();
198 // Start chat server actor in separate thread
199 let server = ChatServer::default().start();
201 let settings = Settings::get();
203 // Create Http server with websocket support
204 HttpServer::new(move || {
206 .data(server.clone())
208 .service(actix_files::Files::new(
210 settings.front_end_dir.to_owned(),
212 .route("/", web::get().to(index))
214 "/home/type/{type}/sort/{sort}/page/{page}",
215 web::get().to(index),
217 .route("/login", web::get().to(index))
218 .route("/create_post", web::get().to(index))
219 .route("/create_community", web::get().to(index))
220 .route("/communities/page/{page}", web::get().to(index))
221 .route("/communities", web::get().to(index))
222 .route("/post/{id}/comment/{id2}", web::get().to(index))
223 .route("/post/{id}", web::get().to(index))
224 .route("/c/{name}/sort/{sort}/page/{page}", web::get().to(index))
225 .route("/c/{name}", web::get().to(index))
226 .route("/community/{id}", web::get().to(index))
228 "/u/{username}/view/{view}/sort/{sort}/page/{page}",
229 web::get().to(index),
231 .route("/u/{username}", web::get().to(index))
232 .route("/user/{id}", web::get().to(index))
233 .route("/inbox", web::get().to(index))
234 .route("/modlog/community/{community_id}", web::get().to(index))
235 .route("/modlog", web::get().to(index))
236 .route("/setup", web::get().to(index))
238 "/search/q/{q}/type/{type}/sort/{sort}/page/{page}",
239 web::get().to(index),
241 .route("/search", web::get().to(index))
242 .route("/sponsors", web::get().to(index))
243 .route("/password_change/{token}", web::get().to(index))
245 .service(web::resource("/api/v1/ws").to(chat_route))
247 .route("/nodeinfo/2.0.json", web::get().to(nodeinfo::node_info))
249 "/.well-known/nodeinfo",
250 web::get().to(nodeinfo::node_info_well_known),
253 .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed))
254 .route("/feeds/all.xml", web::get().to(feeds::get_all_feed))
257 "/federation/c/{community_name}",
258 web::get().to(apub::community::get_apub_community),
261 "/federation/c/{community_name}/followers",
262 web::get().to(apub::community::get_apub_community_followers),
265 "/federation/u/{user_name}",
266 web::get().to(apub::user::get_apub_user),
268 .route("/feeds/all.xml", web::get().to(feeds::get_all_feed));
271 if Settings::get().federation_enabled {
272 println!("federation enabled, host is {}", Settings::get().hostname);
275 ".well-known/webfinger",
276 web::get().to(webfinger::get_webfinger_response),
278 // TODO: this is a very quick and dirty implementation for http api calls
280 "/api/v1/communities/list",
281 web::get().to(|query: Query<ListCommunities>| {
282 let res = Oper::new(UserOperation::ListCommunities, query.into_inner())
286 .content_type("application/json")
287 .body(serde_json::to_string(&res).unwrap())
294 .bind((settings.bind, settings.port))
298 println!("Started http server at {}:{}", settings.bind, settings.port);
303 fn index() -> Result<NamedFile, actix_web::error::Error> {
305 Settings::get().front_end_dir.to_owned() + "/index.html",