DEV Community

Azimjon Pulatov
Azimjon Pulatov

Posted on

How I built Ngrok Alternative

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.

Top comments (11)

Collapse
 
coolaj86 profile image
AJ ONeal (formerly @solderjs)

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

Collapse
 
azimjohn profile image
Azimjon Pulatov

Recently someone used jprq for phishing. First Google Chrome started showing "deceptive site" banner then the domain was suspended. I am still looking at ways to prevents this from happening again.

I was wondering if you have had any experience with dealing with those issues? I recently found out about github.com/publicsuffix/list and found telebit.io there and can it solve this problem?

Collapse
 
coolaj86 profile image
AJ ONeal (formerly @solderjs) • Edited

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

Collapse
 
anderspitman profile image
Anders Pitman

I've been trying to find that SO answer forever. I remember reading it a while back, specifically the part about e2e encryption. Would have been useful when I was working on e2e for boringproxy.

Collapse
 
medboo profile image
medboo

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

Collapse
 
__asam12__ profile image
Julius Novachrono🇳🇬🇳🇬

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

Collapse
 
anderspitman profile image
Anders Pitman • Edited

See also this list of 30+ ngrok alternatives and similar tools: github.com/anderspitman/awesome-tu...

Collapse
 
abdisalan_js profile image
Abdisalan

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

Collapse
 
wulymammoth profile image
David

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

Collapse
 
azimjohn profile image
Azimjon Pulatov

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

Collapse
 
ulugbekna profile image
Ulugbek Abdullaev

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