Repository: housleyjk/ws-rs (language: rust)

Issue:
[Request] Static file handler
It would be nice if there was a way to serve up static files as a regular ol' HTTP server (on the same port) so that web browsers can connect to the listening socket and _then_ make the WebSocket connection.  If there's a way to do this already I'd love to see it :smile: 

Just some way to serve up something like `index.html` which delivers a `<script>` to the client that then makes the WebSocket connection.



Discussion / hints:
I do this with nginx since I would normally use it for serving static files. Is there a reason why using a reverse proxy like nginx is a problem for you? I've also used other static file servers in development (including Rust-based ones, Python-based ones, and node-based ones). But if I can make your life easier, I am willing to try. I'm assuming that in your case you are not trying to share memory with the websocket for some reason, you just want a convenient way of launching a server that both responds to requests for static files and to websocket requests.

Yes, I'm trying to write a self-contained server that acts as a web-based remote desktop.  The idea being that a user can connect to the listening web server from their browser, authenticate to their X11 desktop, and use it like VNC (except with vastly superior performance).

All I really need is for it to be able to serve up the usual .html, .js, .css, etc.  Directory traversing and automatically figuring out the Content-Type header is unnecessary for my use case.  Really, I just need some way to set the response body which ws-rs currently doesn't support.  I can already set the headers so I just need a `Body` added to the Response struct so I can work with it.

It needs to be able to serve up its own static files so that users can "just run it" instead of installing, configuring, and running a separate web server.  Adding a static file handler would solve this problem.

For reference, _I've already written something like this that works_ in Python.  The problem is that Python--even if I optimize the hell out of it--isn't terribly fast at doing things like encoding streams of images when you don't have a C-based module to do stuff like that for you :smiley: .  It works _great_ except while you're using it it gobbles up CPU like nobody's business and has quite a large memory footprint (when not using JPEG encoding).

My plan was to re-implement the whole solution in Rust to vastly improve performance :racehorse: .  The WebSocket-based protocol I've developed is _already_ 10x (or more) bandwidth efficient than VNC and about 2-4x more efficient than RDP and I'm just getting started!

I definitely understand about using Python. 

So, I'm getting that you need this feature because you want individual users to deploy the WebSocket server as part of a complete package. You could write a static server in rust (I have one that I have used that's simple and fast, wouldn't take you more than 5 minutes to get going) as a separate thread or process, and then bind the static server to one port and the websocket server to another. But, I don't think as it is right now that you can avoid binding two separate ports. Even using nginx locks down two ports (e.g. 80 and 3012) because unix socket support isn't there yet (it's on my todo list).

Hopefully, this will be easier when I am able to get the library up to MIO 0.6+, but yeah you can't control the response body right now. It's that way to try to help people avoid writing code that breaks the handshake. The `Response` struct doesn't even have a place to store the body of the response since WebSocket responses don't have bodies.

What do you think about the idea of using a Rust-based static server, perhaps in a main thread and running the websocket in a separate thread? That would get you the "one command" startup I think you are looking for.

Honestly I don't care how many threads it takes.  What matters is the number of ports.  It absolutely _cannot_ be more than one.  That's the hard and fast requirement I must meet.

I've made tools like this before and people _freak_ about using "unnecessary ports".  It's part of the "yet another daemon" problem that plagues enterprises.  Every little thing wants its own daemon _and_ port.  If your daemon needs _two_ or even _more_ ports you're not even going to get consideration.

In fact, my ultimate plan is to have no daemon and no ports at all!  Haha.  How?  I was going to have it use a systemd socket (or an xinetd service) by default with the separate daemon process as an optional run mode.

Edit:  The "yet another daemon" problem is why "agentless" is such a hot buzzword right now.

That's cool! Thank you for giving me the constraints. I will review the code to see if I can add a body to the response without too much work.


Fix patch:
diff --git a/examples/html_chat.rs b/examples/html_chat.rs
new file mode 100644
index 0000000..27bbef9
--- /dev/null
+++ b/examples/html_chat.rs
@@ -0,0 +1,66 @@
+/// An example of a chat web application server
+extern crate ws;
+use ws::{listen, Handler, Message, Request, Response, Result, Sender};
+
+// This can be read from a file
+static INDEX_HTML: &'static [u8] = br#"
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="utf-8">
+	</head>
+	<body>
+      <pre id="messages"></pre>
+			<form id="form">
+				<input type="text" id="msg">
+				<input type="submit" value="Send">
+			</form>
+      <script>
+        var socket = new WebSocket("ws://" + window.location.host + "/ws");
+        socket.onmessage = function (event) {
+          var messages = document.getElementById("messages");
+          messages.append(event.data + "\n");
+        };
+        var form = document.getElementById("form");
+        form.addEventListener('submit', function (event) {
+          event.preventDefault();
+          var input = document.getElementById("msg");
+          socket.send(input.value);
+          input.value = "";
+        });
+		</script>
+	</body>
+</html>
+    "#;
+
+// Server web application handler
+struct Server {
+  out: Sender,
+}
+
+impl Handler for Server {
+  //
+  fn on_request(&mut self, req: &Request) -> Result<(Response)> {
+    // Using multiple handlers is better (see router example)
+    match req.resource() {
+      // The default trait implementation
+      "/ws" => Response::from_request(req),
+
+      // Create a custom response
+      "/" => Ok(Response::new(200, "OK", INDEX_HTML.to_vec())),
+
+      _ => Ok(Response::new(404, "Not Found", b"404 - Not Found".to_vec())),
+    }
+  }
+
+  // Handle messages recieved in the websocket (in this case, only on /ws)
+  fn on_message(&mut self, msg: Message) -> Result<()> {
+    // Broadcast to all connections
+    self.out.broadcast(msg)
+  }
+}
+
+fn main() {
+  // Listen on an address and call the closure for each connection
+  listen("127.0.0.1:8000", |out| Server { out }).unwrap()
+}
diff --git a/src/handshake.rs b/src/handshake.rs
index 8082017..c08b0af 100644
--- a/src/handshake.rs
+++ b/src/handshake.rs
@@ -181,6 +181,12 @@ impl Request {
         }
     }
 
+    /// Get the request method.
+    #[inline]
+    pub fn method(&self) -> &str {
+      &self.method
+    }
+
     /// Get the path of the request.
     #[allow(dead_code)]
     #[inline]
@@ -400,11 +406,30 @@ pub struct Response {
     status: u16,
     reason: String,
     headers: Vec<(String, Vec<u8>)>,
+    body: Vec<u8>,
 }
 
 impl Response {
     // TODO: resolve the overlap with Request
 
+    /// Construct a generic HTTP response with a body.
+    pub fn new<R>(status: u16, reason: R, body: Vec<u8>) -> Response
+            where R: Into<String>,
+        {
+            Response {
+                status: status,
+                reason: reason.into(),
+                headers: vec![("Content-Length".into(), body.len().to_string().into())],
+                body: body
+            }
+        }
+
+    /// Get the response body.
+    #[inline]
+    pub fn body(&self) -> &[u8] {
+        &self.body
+    }
+
     /// Get the value of the first instance of an HTTP header.
     fn header(&self, header: &str) -> Option<&Vec<u8>> {
         self.headers
@@ -545,6 +570,7 @@ impl Response {
                 status: res.code.unwrap(),
                 reason: res.reason.unwrap().into(),
                 headers: res.headers.iter().map(|h| (h.name.into(), h.value.into())).collect(),
+                body: Vec::new(),
             }))
         } else {
             Ok(None)
@@ -563,6 +589,7 @@ impl Response {
                 ("Sec-WebSocket-Accept".into(), try!(req.hashed_key()).into()),
                 ("Upgrade".into(), "websocket".into()),
             ],
+            body: Vec::new(),
         };
 
         debug!("Built response from request:\n{}", res);
@@ -580,6 +607,7 @@ impl Response {
             try!(write!(w, "\r\n"));
         }
         try!(write!(w, "\r\n"));
+        try!(w.write(&self.body));
         Ok(())
     }
 }


Test patch: