lua 

Send to Kindle
home » snippets » nginx » lua



Notes

Note on ngx.exit:  When calling ngx.exit(ngx.OK) within a rewrite_by_lua handler, the nginx request processing control flow will still continue to the content handler.  To terminate the current request from within a rewrite_by_lua handler, calling ngx.exit with status > 200 (ngx.HTTP_OK) and status < 300 (ngx.HTTP_SPECIAL_RESPONSE) for successful quits and ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) (or its friends) for failures.

Snippets

print

print(...)ngx.log(ngx.NOTICE, ...)

Note: There is a hard coded 2048 byte limitation on error message lengths in the Nginx core.  This limit includes trailing newlines and leading time stamps.  If the message size exceeds this limit, Nginx will truncate the message text accordingly.

ngx.say and ngx.print

Ref: ngx.print

ngx.sayngx.print but adds a trailing newline.

Convenient way to proxy pass to dev_appserver backend so I don't have to remember the port number.

Note:  Set the redis kv on app startup in dev mode.

This allows me to visit http://appname.localhost and proxy pass to whatever port the dev_appserver is currently running on.  (Requires wildcard localhost subdomains – ref os_x/dns.

nginx.conf

http {
  # Misc nginx config

  # LUA library paths.  Ensure that cjson.so and lua-resty are available.
  lua_package_path   '/Users/chirayu/ck/vcs/ck_3p/lib/lua/?.lua;/Users/chirayu/ck/vcs/ck5/lua/?.lua;;';
  lua_package_cpath  '/Users/chirayu/ck/vcs/ck_3p/lib/lua/?.so;;';

  init_by_lua '
    ck_nginx_main = require "ck.nginx.main"
    ck_nginx_main.config.HOME_PATH = "/Users/chirayu/"
    ck_nginx_main.config.WEB_ROOT = "/etc/ck/web_roots"
    ck_nginx_main.config.IMPLICIT_WEBROOTS_ENABLED = true
  ';

  server {
      server_name ~^(?<ck_subdomain>.*)\.(localhost|cheshire\.local\.c-k\.me)$
                  ~^(?<ck_domain>.*\..*)$ ;
      listen          [::1]:80;
      listen          127.0.0.1:80;

      # Example:
      #
      #    redis-cli hset ck.config.local.subdomain sample.dart.app '{"type": "proxy", "port": 6060}'

      location @proxy_pass {
          # Pre-req: Must set $proxy_port
          proxy_cache off;
          proxy_pass_header Server;
          #ckck proxy_pass_header Connection;  # ckck
          #ckck proxy_pass_header Upgrade;  # ckck

          # It's better to use $host instead of $http_host.  It's equal to
          # the value of $http_host (the name of the server in the
          # request-header), but when there is no such header, it's equal to
          # the name of the nginx server instead of being blank.
          proxy_set_header  Host $host;

          proxy_redirect    off;
          proxy_set_header  X-Real-IP $remote_addr;
          proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header  X-Scheme $scheme;
          proxy_connect_timeout      90;
          proxy_send_timeout         90;
          proxy_read_timeout         90;
          proxy_send_lowat           12000;

          # To support websocket proxying.
          proxy_buffering off;
          # proxy_buffer_size          4k;
          # proxy_buffers              4 32k;
          # proxy_busy_buffers_size    64k;

          # proxy_temp_file_write_size 64k;
          proxy_pass        http://127.0.0.1:$ck_proxy_port;

          # WebSockets also proxied.
          # Ref: http://nginx.org/en/docs/http/websocket.html
          # Requires nginx 1.3.13+
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          # TODO(chirayu): Is there any point to using the
          # $connection_upgrade map here?  Passing $http_connection through
          # appears to work fine.
          # proxy_set_header Connection $connection_upgrade;
          proxy_set_header Connection $http_connection;
      }

      location @link {
          root $ck_link_root;
          default_type application/octet-stream;
          # Ref: http://nginx.org/en/docs/http/ngx_http_autoindex_module.html
          autoindex on;  # Show directory listings.
          autoindex_exact_size off;  # Display human readable sizes.
      }

      location @wsgi {
          # Needs --reload-os-env on uwsgi.
          # Ref: http://uwsgi.readthedocs.org/en/latest/features/magic-variables.html
          uwsgi_param UWSGI_SETENV CK_PATH_PREFIX=/;
          uwsgi_param SCRIPT_NAME /;
          uwsgi_param UWSGI_MODULE ck.web.wsgi_apps.$ck_wsgi_name;
          uwsgi_param UWSGI_CALLABLE wsgi_app;
          uwsgi_modifier1 30; # properly sets PATH_INFO
          uwsgi_modifier2 0;
          uwsgi_pass uwsgi_vhost_on_nginx;
          include /etc/ck/customize/nginx/uwsgi_params;
      }


      location / {
          set $ck_proxy_port -1 ;
          set $ck_link_root "" ;
          set $ck_wsgi_name "" ;
          rewrite_by_lua 'ck_nginx_main.rewrite()';
      }
  }
}

LUA lib "ck.nginx.main"

do
  local ngx   = require("ngx")
  local cjson = require("cjson")
  local redis = require("resty.redis")

  local API_CONFIG = {
    HOME_PATH = "/Users/chirayu/",
    WEB_ROOT = "/Users/chirayu/z/web_roots/",
    IMPLICIT_WEBROOTS_ENABLED = false,
  }

  local function _SubdomainHandler_NewFields()
    return { red = nil }
  end

  local _SubdomainHandler = _SubdomainHandler_NewFields()

  function _SubdomainHandler:new()
    local result = _SubdomainHandler_NewFields()
    setmetatable(result, self)
    self.__index = self
    -- self.__newindex = self
    return result
  end

  function _SubdomainHandler:_get_redis()
    if self.red ~= nil then return self.red end
    local red = redis:new()
    red:set_timeout(1000) -- 1 sec
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then return nil end
    self.red = red
    return self.red
  end

  function _SubdomainHandler:_close_redis()
    if self.red ~= nil then
      -- put it into the connection pool of size 100,
      -- with 0 idle timeout
      local ok, err = self.red:set_keepalive(0, 100)
      self.red = nil
    end
  end

  function _SubdomainHandler:_bail(message)
    self:_close_redis()
    ngx.status = ngx.HTTP_NOT_FOUND;
    ngx.header["Content-Type"] = "text/plain";
    ngx.say(message)
    return ngx.exit(ngx.HTTP_NOT_FOUND);
  end

  function _SubdomainHandler:_lookup_subdomain(subdomain)
    local json = self.red:hget("ck.config.local.subdomain", subdomain)
    if not json or json == ngx.null then
      return nil, self:_bail("Could not lookup ck.config.local.subdomain:" .. subdomain)
    end

    local config = cjson.decode(json)
    return config, nil
  end

  function _SubdomainHandler:rewrite(domain_key)
    local red = self:_get_redis()
    if not red then
      return self:_bail("Could not connect to redis")
    end

    local config, err = self:_lookup_subdomain(domain_key)
    if err then return err end

    if type(config) == "string" then
      config, err = self:_lookup_subdomain(config)
      if err then return err end
    end

    self:_close_redis()

    if config.type == "proxy" then
      ngx.var.ck_proxy_port = config.port
      return ngx.exec("@proxy_pass")
    elseif config.type == "link" then
      if config.root ~= nil then
        if string.sub(config.root, 1, 2) == "~/" then
          ngx.var.ck_link_root = API_CONFIG.HOME_PATH .. string.sub(config.root, 3)
        else
          ngx.var.ck_link_root = config.root
        end
      else
        if API_CONFIG.IMPLICIT_WEBROOTS_ENABLED then
          ngx.var.ck_link_root = API_CONFIG.WEB_ROOT .. domain_key
        else
          return self:_bail("Implicit web_roots disabled on this host.  - chirayu")
        end
      end
      return ngx.exec("@link")
    elseif config.type == "wsgi" then
      ngx.var.ck_wsgi_name = domain_key
      return ngx.exec("@wsgi")
    else
      return self:_bail("Unknown subdomain type:" .. config.type)
    end

  end

  return {
    config = API_CONFIG,
    rewrite = function()
        local domain_key = ngx.var["ck_subdomain"]
        if domain_key == nil then
          -- make absolute domain for lookup
          domain_key = ngx.var["ck_domain"] .. "."
        end
        return _SubdomainHandler:new():rewrite(domain_key)
      end
  }

end