Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vert.x redirect http to https

We have re-written our webservices with Vert.x 4 and we're more than satisfied. Before putting them in production we want to secure them and we're trying to enable https. This is the main verticle:

public class MainVerticle extends AbstractVerticle {

  @Override
  public void start() throws Exception {
    //Deploy the HTTP server
    vertx.deployVerticle(
      "com.albertomiola.equations.http.HttpServerVerticle",
      new DeploymentOptions().setInstances(3)
    );
  }

  // I use this only in IntelliJ Idea because when I hit "Run" the build starts
  public static void main(String[] args) {
    Launcher.executeCommand("run", MainVerticle.class.getName());
  }

}

And this is the most relevant part of the code of the HTTP server verticle:

public class HttpServerVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    var options = new HttpServerOptions();
    options.setPort(443)
           .setSSl(true)
           .setPemTrustOptions(...)
           .setPemKeyCertOptions(...);

    var server = vertx.createHttpServer(options);
    var router = Router.router(vertx);

    //Start
    server
      .requestHandler(router)
      .listen(portNumber, ar -> {
        if (ar.succeeded()) {
          startPromise.complete();
        } else {
          startPromise.fail(ar.cause());
        }
      });
  }

}

The above code works fine because my website is reachable at https://website.it and https://www.website.it (with letsencrypt certs).


The problem is that when I try to access http://website.it or http://www.website.it it doesn't work (meaning that I can't see the home because the server is unreachable).

How can I redirect http://website.it to https//website.it?

I have googled a lot and what I've managed to find is:

  • this vertx example that setups HTTPS as I do but it does not mention the redirect
  • this SO question that seems to tell me what to do but I cannot understand what to do and I am not sure if that setup has to go in the HttpServerOption object

Maybe is my https configuration wrong? I am using Java 11 on IntelliJ IDEA (Maven build) and Vert.x 4 latest version. Thank you

like image 463
Alberto Miola Avatar asked Sep 16 '19 18:09

Alberto Miola


1 Answers

The final solutions I came up with are shown below and they are equivalent. The idea in both cases is to have an http server (that listens on port 80 of course) that redirects every call to an https server.

So in my case I can do exactly what I want because http://mydomain.it is redirected to https://mydomain.it as expected. For example when I make a call to

http://mydomain.it/api/polynomial/laguerre

There is an http server alive that receives the request but then it throws the ball immediately to

https://mydomain.it/api/polynomial/laguerre

Of course if you directly call the https version this "intermediate" step does not happen.


Using Vert.x

The answer from Hugo in the above post gives a valid solution using Vertx. I have a main verticle that looks like this:

public class MainVerticle extends AbstractVerticle {

  @Override
  public void start() throws Exception {

    //Deploy the HTTPS server
    vertx.deployVerticle(
      "com.albertomiola.equations.http.HttpsServerVerticle",
      new DeploymentOptions().setInstances(n)
    );

    //Deploy the HTTP server
    vertx.deployVerticle(
      "com.albertomiola.equations.http.HttpServerVerticle",
      new DeploymentOptions().setInstances(1)
    );
  }

}

The first verticle is the "real" website because it contains all the logic that I need in my webservice (routers, handlers, models...) and it looks like this:

public class HttpsServerVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    // Setup the HTTPS
    var httpOptions = new HttpServerOptions()
      .setCompressionSupported(true)
      .setPort(443)
      .setSsl(true)
      .setPemTrustOptions(...)
      .setPemKeyCertOptions(...);

    // Start the server and the routes
    var server = vertx.createHttpServer(httpOptions);
    var router = Router.router(vertx);

    //Start
    server
      .requestHandler(router)
      .listen(ar -> {
        if (ar.succeeded()) {
          startPromise.complete();
        } else {
          startPromise.fail(ar.cause());
        }
      });
  }

}

The other verticle instead is just an http server that permanently redirects (with the 301) to the https version of the webserver. Here's the code:

public class HttpsServerVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    var server = vertx.createHttpServer(httpOptions);
    var router = Router.router(vertx);

    //Start
    server
      .requestHandler(r -> {
          r.response()
           .setStatusCode(301)
           .putHeader("Location", r.absoluteURI().replace("http", "https"))
           .end();
      });
  }

}

In this way there are 2 servers active but actually it's like if there were only 1 because the http server (port 80) redirects every call to the https server (port 443).


Using Nginx

The other approach that I have tested requires nginx but it does the same things that I've done in the above example. It listens to http requests on port 80 and then it redirects them to the https version.

  1. Install Nginx on my Ubuntu server (or whatever you have)
  2. Go in the configuration file which is in /etc/nginx/nginx.conf in my case
  3. Add the below code

    http {
        server {
                listen         80;
                server_name    mydomain.it;
                return         301 https://$server_name$request_uri;
        }
    
        //other code...
    }
    
    1. Restart with systemctl restart nginx

Now every call to the http version is redirected to the https version. Thanks to the user injecteer which has suggested me this way.

I am using this approach because I prefer not having a single verticle only for the http version. Also this article from the Vertx website says that this approach is valid:

It is common to expose HTTP servers in production through a front HTTP server / proxy like Nginx, and have it use HTTPS for incoming connections. Vert.x can also expose HTTPS by itself, so as to provide end-to-end encryption.

So yeah, setup https with Vertx (I'd recommend letsencrypt certifies) but also redirect calls to https with nginx.


I wrongly thought that I could do something particular with Vertx to handle this redirect but that's not possible. After the suggestions of the people in this answer AND some good googling around I've learned that this approach is common and that's what I should do!

like image 51
Alberto Miola Avatar answered Sep 23 '22 17:09

Alberto Miola