1 extern crate lemmy_server;
3 extern crate diesel_migrations;
6 use actix_files::NamedFile;
8 use actix_web_actors::ws;
9 use lemmy_server::db::establish_connection;
10 use lemmy_server::feeds;
11 use lemmy_server::nodeinfo;
12 use lemmy_server::websocket::server::*;
13 use lemmy_server::Settings;
15 use std::time::{Duration, Instant};
19 /// How often heartbeat pings are sent
20 const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
21 /// How long before lack of client response causes a timeout
22 const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
24 /// Entry point for our route
28 chat_server: web::Data<Addr<ChatServer>>,
29 ) -> Result<HttpResponse, Error> {
32 cs_addr: chat_server.get_ref().to_owned(),
38 .unwrap_or("127.0.0.1:12345")
41 .unwrap_or("127.0.0.1")
50 cs_addr: Addr<ChatServer>,
54 /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
55 /// otherwise we drop connection.
59 impl Actor for WSSession {
60 type Context = ws::WebsocketContext<Self>;
62 /// Method is called on actor start.
63 /// We register ws session with ChatServer
64 fn started(&mut self, ctx: &mut Self::Context) {
65 // we'll start heartbeat process on session start.
68 // register self in chat server. `AsyncContext::wait` register
69 // future within context, but context waits until this future resolves
70 // before processing any other events.
71 // across all routes within application
72 let addr = ctx.address();
76 addr: addr.recipient(),
77 ip: self.ip.to_owned(),
80 .then(|res, act, ctx| {
82 Ok(res) => act.id = res,
83 // something is wrong with chat server
91 fn stopping(&mut self, _ctx: &mut Self::Context) -> Running {
93 self.cs_addr.do_send(Disconnect {
95 ip: self.ip.to_owned(),
101 /// Handle messages from chat server, we simply send it to peer websocket
102 /// These are room messages, IE sent to others in the room
103 impl Handler<WSMessage> for WSSession {
106 fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) {
107 // println!("id: {} msg: {}", self.id, msg.0);
112 /// WebSocket message handler
113 impl StreamHandler<ws::Message, ws::ProtocolError> for WSSession {
114 fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
115 // println!("WEBSOCKET MESSAGE: {:?} from id: {}", msg, self.id);
117 ws::Message::Ping(msg) => {
118 self.hb = Instant::now();
121 ws::Message::Pong(_) => {
122 self.hb = Instant::now();
124 ws::Message::Text(text) => {
125 let m = text.trim().to_owned();
126 println!("WEBSOCKET MESSAGE: {:?} from id: {}", &m, self.id);
130 .send(StandardMessage {
135 .then(|res, _, ctx| {
137 Ok(res) => ctx.text(res),
146 ws::Message::Binary(_bin) => println!("Unexpected binary"),
147 ws::Message::Close(_) => {
156 /// helper method that sends ping to client every second.
158 /// also this method checks heartbeats from client
159 fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
160 ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
161 // check client heartbeats
162 if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
163 // heartbeat timed out
164 println!("Websocket Client heartbeat failed, disconnecting!");
166 // notify chat server
167 act.cs_addr.do_send(Disconnect {
169 ip: act.ip.to_owned(),
175 // don't try to send a ping
185 let _ = env_logger::init();
186 let sys = actix::System::new("lemmy");
188 // Run the migrations from code
189 let conn = establish_connection();
190 embedded_migrations::run(&conn).unwrap();
192 // Start chat server actor in separate thread
193 let server = ChatServer::default().start();
195 let settings = Settings::get();
197 // Create Http server with websocket support
198 HttpServer::new(move || {
200 .data(server.clone())
201 .service(web::resource("/api/v1/ws").to(chat_route))
202 // .service(web::resource("/api/v1/rest").route(web::post().to(||{})))
203 .service(web::resource("/").to(index))
204 .route("/nodeinfo/2.0.json", web::get().to(nodeinfo::node_info))
206 "/.well-known/nodeinfo",
207 web::get().to(nodeinfo::node_info_well_known),
209 .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed))
210 // TODO: probably need a different function for this (or just handle all of /feeds?
211 // TODO: would be nice to use ListingType, but that doesnt include user
212 .route("/feeds/all.xml", web::get().to(feeds::get_all_feed))
214 .service(actix_files::Files::new("/static", front_end_dir()))
216 .bind((settings.bind, settings.port))
220 println!("Started http server at {}:{}", settings.bind, settings.port);
224 fn index() -> Result<NamedFile, actix_web::error::Error> {
225 Ok(NamedFile::open(front_end_dir() + "/index.html")?)
228 fn front_end_dir() -> String {
229 env::var("LEMMY_FRONT_END_DIR").unwrap_or("../ui/dist".to_string())