inertia_rust/
node_process.rs

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::error::Error;
use std::path::Path;
use std::process::{Child, Command};
use std::{fmt, io};

use reqwest::Url;

#[derive(Debug, Clone)]
pub struct NodeJsError {
    cause: String,
    description: String,
}

impl NodeJsError {
    pub fn new(cause: String, description: String) -> Self {
        NodeJsError { cause, description }
    }

    pub fn get_cause(&self) -> String {
        self.cause.clone()
    }

    pub fn get_description(&self) -> String {
        self.description.clone()
    }
}

impl fmt::Display for NodeJsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "cause: {};\ndescription: {}",
            self.cause, self.description
        )
    }
}

impl Error for NodeJsError {
    fn description(&self) -> &str {
        &self.description
    }
}

#[derive(Debug)]
pub struct NodeJsProc {
    child: Child,
    server: String,
}

impl NodeJsProc {
    /// Starts a Node.js child process that runs an Inertia ssr file.
    ///
    /// # Arguments
    /// * `server_path`     - The path to the `ssr.js` file. E.g. "dist/server/ssr.js".
    /// * `server_url`      - The url where the server is running.
    ///
    /// # Errors
    /// Returns an [`NodeJsError`] if it fails to start the process, e.g. if the machine do not have
    /// [node] installed.
    ///
    /// [node]: https://nodejs.org
    ///
    /// # Return
    /// Returns an `NodeJsProc` instance. Note that you should call the `NodeJsProc::kill(self)`
    /// method before the application fully shuts down, or else the Node.js process will keep alive.
    ///
    /// # Example
    /// ```rust
    /// use inertia_rust::node_process::{NodeJsError, NodeJsProc};
    /// use std::str::FromStr;
    ///
    /// async fn server() {
    ///     let local_url = match reqwest::Url::from_str("localhost:15000") {
    ///         Ok(url) => url,
    ///         Err(err) => panic!("Failed to parse url: {}", err),
    ///     };
    ///
    ///     let node = NodeJsProc::start("dist/server/ssr.js".into(), &local_url);
    ///
    ///     if node.is_err() {
    ///         let err: NodeJsError = node.unwrap_err();
    ///         panic!("Failed to start node server: {}", err);
    ///     }
    ///
    ///     let node = node.unwrap();
    ///
    ///     // runs the server asynchronously,blocking the function on .await
    ///     // when the server stops running, don't forget to:
    ///     let _ = node.kill();
    /// }
    /// ```
    pub fn start(server_path: String, server_url: &Url) -> Result<Self, NodeJsError> {
        let path = Path::new(&server_path);

        if !path.exists() {
            return Err(NodeJsError::new(
                "Invalid path".into(),
                format!("Server javascript file not found in {}.", &server_path),
            ));
        }

        let string_path = match path.to_str() {
            None => {
                return Err(NodeJsError::new(
                    "Invalid path".into(),
                    "The given path contains invalid UTF-8 characters.".into(),
                ))
            }
            Some(path) => path,
        };

        let child = match Command::new("node")
            .arg(string_path)
            .arg("--port")
            .arg(server_url.port().unwrap_or(10000).to_string())
            .spawn()
        {
            Err(err) => {
                return Err(NodeJsError::new(
                    "Process error".into(),
                    format!("Something went wrong on invoking a node server: {}", err),
                ))
            }
            Ok(child) => child,
        };

        Ok(NodeJsProc {
            child,
            server: server_url.to_string(),
        })
    }

    /// Kills the current Node.js process.
    pub async fn kill(self) -> io::Result<()> {
        let resp = reqwest::Client::new()
            .get(format!("{}/shutdown", self.server))
            .send()
            .await;

        if resp.is_err() {
            let _ = self.force_kill();
        }

        Ok(())
    }

    fn force_kill(mut self) -> io::Result<()> {
        self.child.kill()
    }
}