Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add HTTP2 support. #1128

Merged
merged 4 commits into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Now the configuration of the issuer is cached to avoid flip-flop issues when OIDC connectivity fails. [THREESCALE-3809](https://issues.jboss.org/browse/THREESCALE-3809) [PR #1141](https://github.com/3scale/APIcast/pull/1141)
- Openresty dependencies comes now from RedHat build system. [THREESCALE-3771](https://issues.jboss.org/browse/THREESCALE-3771) [PR #1145](https://github.com/3scale/APIcast/pull/1145)
- Added HTTP2 support [THREESCALE-3271](https://issues.jboss.org/browse/THREESCALE-3271) [PR #1128](https://github.com/3scale/APIcast/pull/1128)


### Fixed
Expand Down
2 changes: 2 additions & 0 deletions gateway/Roverfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ luarocks {
group 'development' {
module {'ldoc'},
module {'lua-resty-repl'},
module {'fifo'},
module {'http'}
}
}

26 changes: 18 additions & 8 deletions gateway/Roverfile.lock
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
argparse 0.6.0-1||production
busted 2.0.rc12-1||testing
basexx 0.4.1-1||development
binaryheap 0.4-1||development
bit32 5.3.0-1||development
compat53 0.7-1||development
cqueues 20190813.51-0||development
dkjson 2.5-2||testing
fifo 0.2-0||development
http 0.3-0||development
inspect 3.1.1-0||production
ldoc 1.4.6-2||development
liquid 0.1.3-1||production
ljsonschema 0.1.0-1||testing
lpeg 1.0.2-1||development
lpeg_patterns 0.5-0||development
lua-resty-env 0.4.0-1||production
lua-resty-execvp 0.1.1-1||production
lua-resty-http 0.12-0||production
lua-resty-http 0.15-0||production
lua-resty-iputils 0.3.0-1||production
lua-resty-jit-uuid 0.0.7-1||production
lua-resty-jit-uuid 0.0.7-2||production
lua-resty-jwt 0.2.0-0||production
lua-resty-repl 0.0.6-0||development
lua-resty-repl 0.0.6-0|3878f41b7e8f97b1c96919db19dbee9496569dda|development
lua-resty-url 0.3.5-1||production
lua-term 0.7-1||testing
lua_cliargs 3.0-1||testing
lua_cliargs 3.0-2||testing
luacov 0.13.0-1||testing
luafilesystem 1.7.0-2||production,development,testing
luassert 1.7.10-0||testing
luaossl 20190731-0||development
luassert 1.8.0-0||testing
luasystem 0.2.1-0||testing
lyaml 6.2.3-1||production
lyaml 6.2.4-1||production
markdown 0.33-1||development
mediator_lua 1.1.2-0||testing
net-url 0.9-1||testing
nginx-lua-prometheus 0.20171117-4||production
penlight 1.5.4-1||production,development,testing
nginx-lua-prometheus 0.20181120-2||production
penlight 1.7.0-1||production,development,testing
router 2.1-0||production
say 1.3-1||testing
1 change: 0 additions & 1 deletion gateway/conf.d/apicast.conf
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ location @upstream {
# {% else %}
# proxy_next_upstream error timeout;
# {% endif %}

# these are duplicated so when request is redirected here those phases are executed
post_action @out_of_band_authrep_action;
body_filter_by_lua_block { require('apicast.executor'):body_filter() }
Expand Down
62 changes: 62 additions & 0 deletions gateway/conf.d/http2.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
location @grpc_upstream {

internal;

rewrite_by_lua_block {
require('resty.ctx').apply()
}

grpc_pass grpcs://upstream;


grpc_set_header X-Real-IP $remote_addr;
grpc_set_header Host $http_host;
grpc_set_header X-3scale-grpc-secret-token $secret_token;
grpc_set_header X-3scale-debug "";
grpc_set_header Connection "";

# This is a bit tricky. It uses liquid to set a SSL client certificate. In
# NGINX, all this is not executed as it is commented with '#'. However, in
# Liquid, all this will be evaluated. As a result, the following directives
# are set optionally: grpc_ssl_certificate, grpc_ssl_certificate_key,
# grpc_ssl_session_reuse, and grpc_ssl_password_file.

# {% if proxy_ssl_certificate != empty and proxy_ssl_certificate_key != empty %}
# {% capture proxy_ssl %}
#{#} grpc_ssl_certificate {{ proxy_ssl_certificate }};
#{#} grpc_ssl_certificate_key {{ proxy_ssl_certificate_key }};
# {% endcapture %}
# {{ proxy_ssl | replace: "#{#}", "" }}
#
# {% if proxy_ssl_password_file != empty %}
# {% capture proxy_ssl %}
#{#} grpc_ssl_password_file {{ proxy_ssl_password_file }};
# {% endcapture %}
# {{ proxy_ssl | replace: "#{#}", "" }}
# {% endif %}
#
# {% if proxy_ssl_session_reuse != empty %}
# {% capture proxy_ssl %}
#{#} grpc_ssl_session_reuse {{ proxy_ssl_session_reuse }};
# {% endcapture %}
# {{ proxy_ssl | replace: "#{#}", "" }}
# {% endif %}
# {% endif %}

# When 'upstream_retry_cases' is empty, apply the same default as NGINX.
# If the proxy_next_upstream directive is not declared, the retry policy
# will never retry.
# {% if upstream_retry_cases != empty %}
# {% capture proxy_next_upstream %}
#{#} grpc_next_upstream {{ upstream_retry_cases }};
# {% endcapture %}
# {{ proxy_next_upstream | replace: "#{#}", "" }}
# {% else %}
# grpc_next_upstream error timeout;
# {% endif %}
# these are duplicated so when request is redirected here those phases are executed

post_action @out_of_band_authrep_action;
body_filter_by_lua_block { require('apicast.executor'):body_filter() }
header_filter_by_lua_block { require('apicast.executor'):header_filter() }
}
3 changes: 2 additions & 1 deletion gateway/http.d/apicast.conf.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ server {
{% endif %}

{% if https_port -%}
listen {{ https_port }} ssl;
listen {{ https_port }} ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
{%- assign https_certificate = env.APICAST_HTTPS_CERTIFICATE -%}
ssl_certificate {% if https_certificate -%}
Expand Down Expand Up @@ -100,6 +100,7 @@ server {
{% include file %}
{% endfor %}
{% include "conf.d/apicast.conf" %}
{% include "conf.d/http2.conf" %}
}

{% if port.metrics %}
Expand Down
11 changes: 1 addition & 10 deletions gateway/src/apicast/backend_client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,7 @@ local resty_env = require('resty.env')
local backend_calls_metrics = require('apicast.metrics.3scale_backend_calls')

local http_proxy = require('resty.http.proxy')
local http_ng_ngx = require('resty.http_ng.backend.ngx')
local http_ng_resty = require('resty.http_ng.backend.resty')
-- resty.http_ng.backend.ngx is using ngx.location.capture, which is available only
-- on rewrite, access and content phases. We need to use cosockets (http_ng default backend)
-- everywhere else (like timers).
local http_ng_backend_phase = {
access = http_ng_ngx,
rewrite = http_ng_ngx,
content = http_ng_ngx,
}

local _M = {
endpoint = resty_env.value("BACKEND_ENDPOINT_OVERRIDE")
Expand All @@ -47,7 +38,7 @@ local function detect_http_client(endpoint)
if proxy then -- use default client
return
else
return http_ng_backend_phase[ngx.get_phase()]
return http_ng_resty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to evaluate whether this change has any impact on performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's using lua coroutines and it's the recommended way in Openresty, it's safe to update.

end
end

Expand Down
4 changes: 2 additions & 2 deletions gateway/src/apicast/oauth/apicast_oauth/authorize.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
local random = require('resty.random')
local ts = require('apicast.threescale_utils')
local backend_client = require('apicast.backend_client')
local http_ng_ngx = require('resty.http_ng.backend.ngx')
local http_ng_resty = require('resty.http_ng.backend.resty')

-- returns a unique string for the client_id. it will be short lived
local function nonce(client_id)
Expand Down Expand Up @@ -84,7 +84,7 @@ end

-- Check valid params ( client_id / secret / redirect_url, whichever are sent) against 3scale
local function check_credentials(service, params)
local backend = assert(backend_client:new(service, http_ng_ngx), 'missing backend')
local backend = assert(backend_client:new(service, http_ng_resty), 'missing backend')
local res = backend:authorize({ app_id = params.client_id, redirect_uri = params.redirect_uri })

ngx.log(ngx.INFO, "[oauth] Checking client credentials, status: ", res.status, " body: ", res.body)
Expand Down
6 changes: 3 additions & 3 deletions gateway/src/apicast/oauth/apicast_oauth/get_token.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local ts = require 'apicast.threescale_utils'
local re = require 'ngx.re'
local env = require 'resty.env'
local backend_client = require('apicast.backend_client')
local http_ng_ngx = require('resty.http_ng.backend.ngx')
local http_ng_resty = require('resty.http_ng.backend.resty')
local tonumber = tonumber

local oauth_tokens_default_ttl = 604800 -- 7 days
Expand Down Expand Up @@ -64,7 +64,7 @@ end

-- Check valid params ( client_id / secret / redirect_url, whichever are sent) against 3scale
local function check_client_credentials(service, params)
local backend = assert(backend_client:new(service, http_ng_ngx), 'missing backend')
local backend = assert(backend_client:new(service, http_ng_resty), 'missing backend')
local res = backend:authorize({ app_id = params.client_id, app_key = params.client_secret, redirect_uri = params.redirect_uri })

ngx.log(ngx.INFO, "[oauth] Checking client credentials, status: ", res.status, " body: ", res.body)
Expand Down Expand Up @@ -97,7 +97,7 @@ end

-- Stores the token in 3scale.
local function store_token(service, params, token)
local backend = assert(backend_client:new(service, http_ng_ngx), 'missing backend')
local backend = assert(backend_client:new(service, http_ng_resty), 'missing backend')
local res = assert(backend:store_oauth_token({ app_id = params.client_id, token = token.access_token, user_id = params.user_id, ttl = token.expires_in }))

return { status = res.status , body = res.body or res.status }
Expand Down
6 changes: 5 additions & 1 deletion gateway/src/apicast/policy/apicast/apicast.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ function _M:rewrite(context)

local err
context[self] = context[self] or {}
context[self].upstream, err = p.get_upstream(service)
context[self].upstream, err = p.get_upstream(service, context)
context.get_upstream = function()
return context[self].upstream
end

if err then
ngx.log(ngx.WARN, "upstream api for the service:", service.id, " is invalid, error:", err)
end
Expand Down
12 changes: 12 additions & 0 deletions gateway/src/apicast/policy/grpc/apicast-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "http://apicast.io/policy-v1.1/schema#manifest#",
"name": "HTTP2 endpoint",
"summary": "Main functionality to enable HTTP2 endpoint reply.",
"description":
["To enable full HTTP2 traffic from the user to the final endpoint "],
"version": "builtin",
"configuration": {
"type": "object",
"properties": { }
}
}
54 changes: 54 additions & 0 deletions gateway/src/apicast/policy/grpc/grpc.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
-- This policy enables HTTP2 handle on the API endpoint.

local policy = require('apicast.policy')
local _M = policy.new('grpc', "builtin")
local resty_url = require('resty.url')
local round_robin = require 'resty.balancer.round_robin'

local new = _M.new

local balancer = round_robin.new()

function _M.new(config)
local self = new(config)
return self
end

function _M:rewrite(context)
if ngx.var.server_protocol == "HTTP/2.0" then
-- upstream defined in gateway/conf.d/http2.conf
context.upstream_location_name = "@grpc_upstream"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment that references the file where this location is defined?

end
end

function _M:balancer(context)
if not context.upstream_location_name then
return
end

-- balancer need to be used due to grpc_pass does not support variables and
-- upstream block need to be in place.
local upstream = context:get_upstream()
if not upstream then
ngx.log(ngx.WARN, "Upstream is not present in the balancer")
return
end

local peers = balancer:peers(upstream.servers)
local peer, err = balancer:select_peer(peers)
if err then
ngx.log(ngx.WARN, "Cannot get a peer for the given upstream", err)
return
end

local ip = peer[1]
local port = peer[2] or upstream.uri.port or resty_url.default_port(upstream.uri.scheme)
local _, err = balancer:set_current_peer(ip, port)

if err then
ngx.log(ngx.WARN, "Cannot set balancer IP and port '", ip, ":", port, "'")
return
end
end

return _M
1 change: 1 addition & 0 deletions gateway/src/apicast/policy/grpc/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('grpc')
7 changes: 5 additions & 2 deletions gateway/src/apicast/proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,21 @@ function _M.set_service(service)
return service
end

function _M.get_upstream(service)
function _M.get_upstream(service, context)
service = service or ngx.ctx.service

if not service then
return errors.service_not_found()
end
local upstream, err = Upstream.new(service.api_backend)

if not upstream then
return nil, err
end

if context and context.upstream_location_name then
upstream.location_name = context.upstream_location_name
end

upstream:use_host_header(service.hostname_rewrite)

return upstream
Expand Down
6 changes: 6 additions & 0 deletions gateway/src/apicast/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ function _M:append_path(path)
self.uri.query = tmp_query
end

function _M:update_location(location_name)
if location_name then
self.location_name = location_name
end
end

--- Rewrite request Host header to what is provided in the argument or in the URL.
function _M:rewrite_request()

Expand Down
2 changes: 1 addition & 1 deletion t/apicast-blackbox.t
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ location /transactions/authrep.xml {
assert = require('luassert')
assert.equal('https', ngx.var.scheme)
assert.equal('$TEST_NGINX_RANDOM_PORT', ngx.var.server_port)
assert.equal('localhost', ngx.var.ssl_server_name)
assert.equal('test_backend', ngx.var.ssl_server_name)
}

content_by_lua_block {
Expand Down
Loading