I started, back when i was a kid i think, looking for ways to host a Public server from Local server at NPM. Searched for keywords like:
- Reverse proxy
- Ws tunnel
- Reverse HTTP
- TCP tunnel
Tried ws-tunnel:
It needed a separate port for WebSocket connection, and a separate port for hosting a Web server. Even though that way it could work, Heroku allowed only 1 port to be used, and i didnt know if any other Cloud provider would allow 2 public ports (i felt it was not possible).
Tried reverse-http:
It could act as a Reverse HTTP client. Reverse HTTP is a protocol provided by IETF. But, then i needed a Reverse HTTP server. However there still was a problem with Reverse HTTP, as it could stream only one request at a time. It was basically a HTTP connection in reverse from Public server to Local server. That meant either i would have to somehow serialize requests of multiple users to one or multiple connections (multiple Reverse HTTP connections would have to be setup between Local server and Public server).
Try using TCP packets:
Then i realized that is was possible to do this using a single connection, and a simpler non-buffered method of proxying. Reverse HTTP might need buffering if there was a new connection, and all Reverse HTTP connections were in use. Consider using simple packets, of the following format:
--------------------------------------
| Packet Size | Connection ID | Data |
--------------------------------------
I was looking for a way to do this using WebSockets, if they had some existing feature i could use, but then i saw Heroku allowed any HTTP upgrade, so i decided to go ahead with a TCP upgrade instead. I know, i made it up. Here is how the header looks like, i checked that Heroku accepts it:
GET / HTTP/1.1
Upgrade: tcp
Connection: Upgrade
Origin: XXX
Ok so now there was an idea how you could upgrade the connection between Local server and Public server using TCP, and then communicate using packets. Still however the packet format does not tell us whether the user connected, or disconnected. It only tells us what data the user sent. So i added another field to the packet:
----------------------------------------------
| Packet Size | Event | Connection ID | Data |
----------------------------------------------
One problem that was not solved yet, was distinguishing between the connection from a user and a Local server, since we are using the same port for both of them. There could be many ways to do this, but i decided to go with a specific User-Agent
for the Local server.
More doubts, whether to build the public server code using Node's HTTP or using Node's TCP. This time, i was reading about chunked encoding, trailers, and decided it would just be better to use TCP. So, i would have to parse the initial request for a connection, and based on the type determined, process the request as a user or as a Local server. But i imagined there would be a performance issue as i would be parsing the HTTP headers of every connection. But if we use a rare HTTP method for quick distinction, like HEAD
, we would only have to read 4 characters only. So we can now do this:
1. If first 4 characters not HEAD, process as User.
2. Parse all the HTTP headers.
3. If User-Agent is special value, process as Local server
4. Process as User.
I also added support for multiple channels to this now, which can now enable me to access as SSH server running on another machine (private ip). Moved the code and decorated the README: https://github.com/nodef/rhost.
A few interesting pages i found while searching: