‘Reverse-Proxy Friendly’

tl;dr: Web apps should work behind reverse-proxy servers out of the box.  Use relative URLs.


As a digression from my usual policy-wonk fare, I’d like to take a short moment to coin a term: “reverse-proxy friendly”.  This is a property of web-applications such that they can be used behind a reverse-proxy server without the need for the proxy server to re-write URLs in HTML.  (And without the for the operator of said application to modify anything about the app.)  The main thing that a web app needs to do (in order to be reverse-proxy friendly) is to use relative URLs, but sometimes there are other changes as well, such as changes to Javascript that computes AJAX or WebSocket endpoints.

Here’s my issue:  I run both personal and public web sites, each of which has multiple web applications unified into a single namespace.  Often these applications use completely different technology stacks.  For example, on risacher.org, I’m running WordPress & Gallery (mod_php), occasionally Ajaxterm (python stand-alone server), Etherpad and tty.js (Node.js), a file-uploader app (mod_perl), and static & CGI content through Apache httpd. I unify all these apps into a single namespace through the use of a reverse proxy server.  For years, I did this with perlbal, but I now I use a 50-line custom proxy based on node-http-proxy.  (Other common solutions are nginx, Varnish, mod_proxy, Squid, or Pound.) (UPDATE: my reverse proxy server code is now at https://github.com/risacher/yxorp-edon)

For any of my applications, I generally want to map this application’s URL namespace into the overall namespace of the server.  So, for example, even though tty.js normally serves files from ‘/’, my instance is at “https://risacher.org/term/“.  Applications that only use relative paths for URLs do this easily.   Both Etherpad and tty.js use Socket.IO, to manage AJAX connections, and Etherpad computes the right socket.io endpoint URL automagically.  Alas, tty.js isn’t quite so smart, and requires a 1-line change to support a remapped socket.io endpoint and 7 URLs need fixing in “static/index.html”.  As a node.js guy, this was pretty easy to do.  (I will submit a pull request to tty.js soon.)

Recently I tried to stand up a WaveMaker (java/tomcat) instance (on my EC2 server) to evaluate its suitability for a project at work.  WaveMaker abjectly fails to be reverse-proxy friendly.  I can’t unify WaveMaker with my existing web applications (it’s not reverse-proxy friendly), and I cannot access the non-standard port directly (employer’s firewalls do not permit it) and I cannot install WaveMaker at the office (employer’s policies do not permit me to install unapproved software).   If I knew I really wanted WaveMaker, I might hack the code to make it reverse-proxy friendly.  Alas, I don’t know yet if it’s worth using, and I dislike using Java, so it’s hard to get up the motivation to move forward.

It should be noted that there are proxy servers that will re-write an applications HTML code to fix the absolute link problems (q.v. mod_proxy_html), but they are not general-purpose solutions because they will not address URLs generated in Javascript, which is an important issue for modern web applications.

A better (but less expedient) solution is for web applications to support reverse-proxy out-of-the-box.  Alas, it was difficult to articulate this requirement until right now, because the term “reverse-proxy friendly” hadn’t been defined.  Now that I’ve coined this term, you should bother authors of web-applications to implement this “feature”.

Go!  Go now!  Commence bothering!

(p.s. start with WaveMaker)

 

15 thoughts on “‘Reverse-Proxy Friendly’

  1. Eric Mill

    I’ve been following your blog for the wonkery (I work for the Sunlight Foundation), but as a fellow Node fan I found this post quite refreshing (and I agree with your point, of course). node-http-proxy is one of the most useful swiss army knife tools I’ve had the pleasure of using.

    I’m curious, what do you use for hosting? As much as I love Webfaction, I’m looking to finally ditch shared hosting and put all my apps on one box, for a price that won’t completely break my budget. The top contender right now is simply EC2, but maybe you know something I don’t! I’m on Twitter at @konklone if you don’t feel like running your comment thread aground in this sort of thing. 🙂

    Reply
    1. Dan Post author

      I host on EC2, currently on a T1-Micro instance, which runs me about $20/month, including bandwidth and storage.

      Prior to May 2011, I hosted in my basement, with connectivity through business-class FiOS (which came with Static IP). At that time, consumer FiOS in my neighborhood was $35/month and business FiOS was $100/month. So dropping business FiOS and hosting at Amazon would have been frugal, but I was too stubborn to notice.

      Then my house burned down, and I learned the value of (a) off-site backups, and (b) insurance. From the hotel, I moved to EC2 (just a placeholder page at first) and I’d never go back. Personally, I’m looking not only for a webhost, but also for shell service that I can access from anywhere. EC2 makes this easy.

      Reply
  2. tericee

    Note: it’s should be its in “Recently I tried to stand up a WaveMaker (java/tomcat) instance (on my EC2 server) to evaluate it’s suitability for a project at work.” Just sayin’. 😉

    Reply
  3. Jarrett

    Hi. I’ve recently been trying to do the same thing with tty.js. I’m really interested in having shell access with nothing more than a web browser. I got the node.js server up and running no problem, but trying to configure apache to reverse proxy to it with a different root has been a bit of a nightmare. You alluded to your solution, but didn’t go much into the specifics. Would you mind expanding on your solution in a bit more detail? Any help would be greatly appreciated! Thanks!

    Reply
  4. Amin

    Hi
    Can you please tell me how you achieved the tty.js url remapping? How do you instantiate the tty.js app?

    Thank you!

    Reply
    1. Dan Post author

      The way I do it is not particularly recommended, but for what it’s worth, here’s what I do:

      In /etc/rc.local, I have this line:
      (cd /home/magnus/node/tty.js; su ttyjs -c 'screen -S ttyjs -d -m bin/tty.js')

      This runs an instance of tty.js inside of a screen session running under a service account. The tty.js config shell option a script called ‘ssh-localhost’ which prompts for a username and execs ssh to username@localhost. (This was simpler than figuring out how to use getty properly.)

      A more-sensible approach would probably be to use ‘forever’ or something like that. But I kinda like running it in screen so I can connect to the session and see the stdout/stderr.

      In terms of url remapping, there should be nothing that needs to be done for tty.js, since chjj accepted pull request #88. What pr#88 did is made urls relative for the stylesheets and javascript files, and added a tiny bit of code that computes the relative path for the socket.io endpoint.

      The interesting bit of code is in static/tty.js and looks like this:

      if (document.location.pathname) {
      var parts = document.location.pathname.split('/')
      , base = parts.slice(0, parts.length - 1).join('/') + '/'
      , resource = base.substring(1) + 'socket.io';
      tty.socket = io.connect(null, { resource: resource });
      } else {
      tty.socket = io.connect();
      }

      Does that answer your question?

      Reply
      1. Amin

        Thank you very much for quick reply. By instantiation I did not mean that though. I have tty.js running inside a container and it runs fine on localhost:9091 for example. However, I want to run it on “localhost:9091/shell”. I seem to have difficulty doing that. Any idea?

        Reply
        1. Dan Post author

          Ah. I don’t do that, and I’m not completely sure why you would want to. I run tty.js as “localhost:8001”, and I use nginx to serve it out as “https://fully-qualified-server-name/term/”. My nginx configuration looks like this:


          location /term/ {
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Host $http_host;
          proxy_set_header X-NginX-Proxy true;

          # prevents 502 bad gateway error
          proxy_buffers 8 32k;
          proxy_buffer_size 64k;

          proxy_pass http://127.0.0.1:8001/;
          proxy_redirect off;

          # enables WS support
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
          }

          Reply
          1. Amin

            Dan, Thank you very much. Worked, though I had to switch to nginx. I was using apache and had problem with setting up the proxy. It almost worked with apache but not quite!

          2. Amin

            Any idea what can we do for fixing the resizing the term window issue and vim/nano view corruptions?

          3. Dan Post author

            Yeah, I fixed that too. The pull-requests are submitted ( tty.js #134 and term.js #13) but chjj hasn’t accepted tty.js #134 as of this writing.

            Really, the only change you probably need is to tty.js/package.json. Here’s the patch:

            - "term.js": "0.0.3"
            + "term.js": ">= 0.0.4""

            After making this change, run npm update, and the fixed resize code should be pulled in.

  5. Amin

    I decided to use term.js for my app. Do you know how shall I handle resizing on the server side? I believe I need to tell the pty to resize so that everything checks out. But I don’t know how to do it. Any help is appreciated!

    Reply
    1. Dan Post author

      You’d have to tell me a bit more about the situation you’re proposing. What kind of app are you writing? Are you using tty.js or just term.js?

      I spent some time a while ago trying to use term.js without tty.js, but at this point I’ve abandoned the project that I was working towards. I might know enough to give some pointers, but I’m not sure I understand what you’re asking.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *