25

I have a couple of API endpoints that I want to serve from under a single location of /api with subpaths going to different endpoints. Specifically, I want webdis to be available at /api and a proprietary API available at /api/mypath.

I'm not worried about clashes with the webdis API because I am using subpaths which are unlikely to clash with redis command names, and also have full control over the design of the API to avoid clashes.

Here's the config file from my test server that I have been hacking on:

server {
  listen 80;
  server_name localhost;
  server_name 192.168.3.90;
  server_name 127.0.0.1;

  location / {
    root /home/me/src/phoenix/ui;
    index index.html;
  }

  # temporary hardcoded workaround
  location = /api/mypath/about {
    proxy_pass http://localhost:3936/v1/about;
  }

  location /api {
    rewrite ^/api/(.*)$ /$1 break;
    proxy_pass http://localhost:7379/;
  }

  # tried this but it gives "not found" error
  #location ^~ /api/mypath/ {
  #  rewrite ^/api/mypath/(.*)$ /$1 break;
  #  proxy_pass http://localhost:3936/v1/;
  #}
  #
  #location ^~ /api {
  #  rewrite ^/api/(.*)$ /$1 break;
  #  proxy_pass http://localhost:7379/;
  #}
}

How can I change my workaround so that any requests to /api/mypath/* will go to the endpoint at port 3936, and everything else to port 7379?

masegaloeh
  • 18,236
  • 10
  • 57
  • 106
hamstar
  • 745
  • 3
  • 9
  • 20
  • What do you mean by `tried this to no avail`? What happened when you enable that location directive? Connection timeout? Location not matched? – masegaloeh Dec 08 '14 at 22:53
  • Ah thanks for the prompt, it is giving a not found error, on further investigation it appears that error is coming from my API so it is working! :D But the rewrite rule is obviously not because I have to add v1 to the URL (http://localhost/api/mypath/v1/about)... :( – hamstar Dec 08 '14 at 23:12

2 Answers2

36

You don't need rewrite for this.

server {
  ...

  location ^~ /api/ {
    proxy_pass http://localhost:7379/;
  }
  location ^~ /api/mypath/ {
    proxy_pass http://localhost:3936/v1/;
  }
}

According to the nginx documentation

A location can either be defined by a prefix string, or by a regular expression. Regular expressions are specified with the preceding ~* modifier (for case-insensitive matching), or the ~ modifier (for case-sensitive matching). To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

If the longest matching prefix location has the ^~ modifier then regular expressions are not checked.

Therefore any request that begins with /api/mypath/ will always be served by the second block since that's the longest matching prefix location.

Any request that begins with /api/ not immediately followed by mypath/ will always be served by the first block, since the second block doesn't match, therefore making the first block the longest matching prefix location.

Doktor J
  • 1,107
  • 1
  • 10
  • 20
Alexey Ten
  • 8,435
  • 1
  • 34
  • 36
  • 2
    If you look at the location modifiers (`=`, `~*`, `~`, and `^~`) it may seem counter-intuitive that `^~` excludes regular expressions (since `~` indicates a regular expression match)... however, if you recall, `^` inside a regex character class (e.g. `[^a-z]`) *negates* that class (such that the example means (any character *except* those from a-z); similarly, `^~` negates any potential regular expression location blocks. – Doktor J Oct 06 '17 at 20:18
14

OK figured it out, I thought the "not found" error was coming from nginx, but actually it was coming from my API. This is my solution if anyone is interested:

server {
  listen 80;
  server_name localhost;
  server_name 192.168.3.90;
  server_name 127.0.0.1;

  location / {
    root /home/me/src/phoenix/ui;
    index index.html;
  }

  # automatically go to v1 of the (grape) API
  location ^~ /api/mypath/ {
    rewrite ^/api/mypath/(.*)$ /v1/$1 break;
    proxy_pass http://localhost:3936/;
  }

  location ^~ /api {
    rewrite ^/api/(.*)$ /$1 break;
    proxy_pass http://localhost:7379/;
  }
}
Xavier Lucas
  • 13,095
  • 2
  • 44
  • 50
hamstar
  • 745
  • 3
  • 9
  • 20