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
use crate::{
  config::{CompressionConfig, ServerConfig},
  files::{path_context::PathContext, Files},
};
use actix_http::http::uri::InvalidUri;
use actix_rt::Runtime;
use actix_web::{
  dev::ServerHandle,
  middleware::{self, Condition, Logger},
  App, HttpServer,
};
use collective::thread::{self, handle::ThreadHandle};
use snafu::{ResultExt, Snafu};
use std::{
  convert::{TryFrom, TryInto},
  path::PathBuf,
  sync::{
    mpsc::{self, RecvError, Sender},
    Arc,
  },
  thread::Thread,
};
use tokio::sync::RwLock;

#[derive(Debug, Snafu)]
pub enum Error {
  ChannelReceive {
    source: RecvError,
  },
  Bind {
    source: std::io::Error,
  },
  SystemRun {
    source: std::io::Error,
  },
  RedirectUrlParseError {
    source: InvalidUri,
  },
  CreatePathContext {
    source: crate::files::path_context::Error,
  },
}

pub type Result<T, E = Error> = std::result::Result<T, E>;

pub struct Server {
  config: ServerConfig,
  serve_dir: Arc<RwLock<Option<PathBuf>>>,
}

impl Server {
  pub fn new(config: ServerConfig, serve_dir: Arc<RwLock<Option<PathBuf>>>) -> Self {
    Server { config, serve_dir }
  }

  pub async fn spawn(
    self,
    notify_sender: Sender<Thread>,
  ) -> Result<(ServerHandle, ThreadHandle<Result<()>>)> {
    log::debug!("Starting server thread");

    let (tx, rx) = mpsc::channel();

    let server_address = self.config.address;
    let serve_dir = Arc::clone(&self.serve_dir);
    let redirect_to_slash = self.config.redirect_to_slash;

    let enable_auto_compression = matches!(
      self.config.compression,
      Some(CompressionConfig::Auto) | None
    );

    let root_path_context =
      Arc::new(PathContext::try_from(&self.config).context(CreatePathContext)?);
    let mut path_contexts: Vec<PathContext> = Vec::new();

    if let Some(path_configs) = &self.config.path_configs {
      for path_config in path_configs {
        path_contexts.push(path_config.try_into().context(CreatePathContext)?);
      }
    }

    let path_contexts = Arc::new(path_contexts);

    let thread_handle = thread::handle::spawn(notify_sender, move || {
      let rt = Runtime::new().unwrap();

      let srv = HttpServer::new(move || {
        let mut files_service = Files::new(
          "/",
          Arc::clone(&serve_dir),
          Arc::clone(&root_path_context),
          Arc::clone(&path_contexts),
        );

        if let Some(true) = redirect_to_slash {
          files_service = files_service.redirect_to_slash_directory();
        }

        App::new()
          .wrap(Logger::default())
          .wrap(Condition::new(
            enable_auto_compression,
            middleware::Compress::default(),
          ))
          .default_service(files_service)
      })
      .bind(server_address)
      .context(Bind)?
      .shutdown_timeout(60)
      .run();

      let _ = tx.send(srv.handle());

      rt.block_on(async { srv.await }).context(SystemRun)?;

      Ok(())
    });

    Ok((rx.recv().context(ChannelReceive)?, thread_handle))
  }
}