loading...

How I built Ngrok Alternative

azimjohn profile image Azimjon Pulatov ・2 min read

Here's a quick intro of what I built

Ngrok is a fantastic tool that helps developers to expose their localhost to the Internet with minimal effort. One day I was going to share a local project with a client without deploying it somewhere. But this time I needed to expose 2 ports: one running frontend and the other backend of the project, I found out that the ngrok's free plan only allows one tunnel at a time. Later that day, I also found out that there's a 40-requests/minute limit.

Bam! An idea.
What if I build an alternative instead of paying $5/mo.?
That's what developers do, right? I also had the idea of making it open-source.

It's time to plan.
There are obviously 3 parts:

  • a developer with a command-line tool (cli)
  • a proxy server
  • a client with a browser.

I was sure there had to be at least those steps to make it work:

  1. a developer connects to the server via a cli with a port number: jprq 8000
  2. the server responds with an assigned domain. amazing-coder.jprq.live
  3. a client opens the domain in a browser: https://amazing-coder.jprq.live
  4. the server receives the request and sends it to the cli.
  5. the cli makes the request to localhost at the given port and sends the response to the server.
  6. the server responds back to the client with the response it received from the cli.

Here's a chart to help you picture those steps.
Alt Text

The cli and the server needs to make back and forth communication. A carefully managed TCP Sockets would do a great job but would take a long time to implement.
I decided to use and used the WebSocket Protocol. As you might know, WebSocket is a hop-by-hop protocol and sadly, my ngrok alternative now can't support Polling and HTTP streaming (because they never end or last too long).

The command-line tool is written in Python and published to PyPy. Currently implementing the command-line in javascript to publish in npm just for fun. And the server-side is in Golang. I think it was the best decision to choose golang because of its easy data sharing between goroutines and learned about leaking goroutines the hard way. I now have a good understanding of memory leaks because of this project.

Another small but impactful mistake I made during implementing the client-server communication was using JSON. I realized it only after adding a feature for handling files. With JSON one can only serialize strings. To turn file contents, bytes into a string, I needed to Base64 them. It turns out to be CPU intensive process. It's better to use BSON, I believe.

The project is open-source and waiting for your contribution. Take some time to visit the GitHub repo at github.com/azimjohn/jprq

Keep building,
Cheers.

Discussion

markdown guide
 

I also have an open source alternative, in node.js: telebit.io

I'm in the process of transitioning to Go (because node's TCP stack was sooooo terrible and sooo buggy), but I'm not quite ready to publish it.

(although if I were to rewrite it in node today, most of the bugs that made it extremely difficult have since been fixed)

I went with the end-to-end encrypted model, and my choice to tunnel multiple connections within websockets was... challenging. Doing one-per-each may have been a better choice. The jury is till out.

My explanation: stackoverflow.com/a/52614266/151312

 

P.S. That would be a good thread to post a new answer with a link to your article.

 

Great work even though I was not able to use it and here is my 2 cents on this as an end-user:

You did the hard work already, but you skipped the easiest (the most important should I say)?

1) Write the instructions on how to self-host the server, I uploaded the code to my server and tried to access the url, but got "Tunnel doesn't exist", I have no idea what this is, with a little extra effort to write some human instructions you will save me and others a lot of time and headaches.

2) for the client part, write the lines you described in the video in the github README as well, there is no easiest way than copy/past, it's much faster than the short video itself :)

Might seem not that important to us as coders, but when you become the end-user you pay attention to how much these little things matter.

Good luck

 

If you use Ngrok's yaml config file you can expose 2 ports on the free plan

 

Nice work! I've been using ngrok before it became a service. Did you reference its source code to write it in Python?

 

Hi David, I didn't reference Ngrok's source code.
I had a simpler vision and I built it out from scratch.

 

awesome post! I use ngrok regularly, so it was cool to see it from this persepective

 

Impressive! Could you elaborate on what memory leaks you had with goroutines?