feat(apisix): add Cloudron package
- Implements Apache APISIX packaging for Cloudron platform. - Includes Dockerfile, CloudronManifest.json, and start.sh. - Configured to use Cloudron's etcd addon. 🤖 Generated with Gemini CLI Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
-- contributor license agreements. See the NOTICE file distributed with
|
||||
-- this work for additional information regarding copyright ownership.
|
||||
-- The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
-- (the "License"); you may not use this file except in compliance with
|
||||
-- the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
local ngx = ngx
|
||||
local ngx_arg = ngx.arg
|
||||
local core = require("apisix.core")
|
||||
local req_set_uri = ngx.req.set_uri
|
||||
local req_set_body_data = ngx.req.set_body_data
|
||||
local decode_base64 = ngx.decode_base64
|
||||
local encode_base64 = ngx.encode_base64
|
||||
local bit = require("bit")
|
||||
local string = string
|
||||
|
||||
|
||||
local ALLOW_METHOD_OPTIONS = "OPTIONS"
|
||||
local ALLOW_METHOD_POST = "POST"
|
||||
local CONTENT_ENCODING_BASE64 = "base64"
|
||||
local CONTENT_ENCODING_BINARY = "binary"
|
||||
local DEFAULT_CORS_ALLOW_ORIGIN = "*"
|
||||
local DEFAULT_CORS_ALLOW_METHODS = ALLOW_METHOD_POST
|
||||
local DEFAULT_CORS_ALLOW_HEADERS = "content-type,x-grpc-web,x-user-agent"
|
||||
local DEFAULT_CORS_EXPOSE_HEADERS = "grpc-message,grpc-status"
|
||||
local DEFAULT_PROXY_CONTENT_TYPE = "application/grpc"
|
||||
|
||||
|
||||
local plugin_name = "grpc-web"
|
||||
|
||||
local schema = {
|
||||
type = "object",
|
||||
properties = {
|
||||
cors_allow_headers = {
|
||||
description =
|
||||
"multiple header use ',' to split. default: content-type,x-grpc-web,x-user-agent.",
|
||||
type = "string",
|
||||
default = DEFAULT_CORS_ALLOW_HEADERS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local grpc_web_content_encoding = {
|
||||
["application/grpc-web"] = CONTENT_ENCODING_BINARY,
|
||||
["application/grpc-web-text"] = CONTENT_ENCODING_BASE64,
|
||||
["application/grpc-web+proto"] = CONTENT_ENCODING_BINARY,
|
||||
["application/grpc-web-text+proto"] = CONTENT_ENCODING_BASE64,
|
||||
}
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
priority = 505,
|
||||
name = plugin_name,
|
||||
schema = schema,
|
||||
}
|
||||
|
||||
function _M.check_schema(conf)
|
||||
return core.schema.check(schema, conf)
|
||||
end
|
||||
|
||||
local function exit(ctx, status)
|
||||
ctx.grpc_web_skip_body_filter = true
|
||||
return status
|
||||
end
|
||||
|
||||
--- Build gRPC-Web trailer chunk
|
||||
-- grpc-web trailer format reference:
|
||||
-- envoyproxy/envoy/source/extensions/filters/http/grpc_web/grpc_web_filter.cc
|
||||
--
|
||||
-- Format for grpc-web trailer
|
||||
-- 1 byte: 0x80
|
||||
-- 4 bytes: length of the trailer
|
||||
-- n bytes: trailer
|
||||
-- It using upstream_trailer_* variables from nginx, it is available since NGINX version 1.13.10
|
||||
-- https://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_trailer_
|
||||
--
|
||||
-- @param grpc_status number grpc status code
|
||||
-- @param grpc_message string grpc message
|
||||
-- @return string grpc-web trailer chunk in raw string
|
||||
local build_trailer = function (grpc_status, grpc_message)
|
||||
local status_str = "grpc-status:" .. grpc_status
|
||||
local status_msg = "grpc-message:" .. ( grpc_message or "")
|
||||
local grpc_web_trailer = status_str .. "\r\n" .. status_msg .. "\r\n"
|
||||
local len = #grpc_web_trailer
|
||||
|
||||
-- 1 byte: 0x80
|
||||
local trailer_buf = string.char(0x80)
|
||||
-- 4 bytes: length of the trailer
|
||||
trailer_buf = trailer_buf .. string.char(
|
||||
bit.band(bit.rshift(len, 24), 0xff),
|
||||
bit.band(bit.rshift(len, 16), 0xff),
|
||||
bit.band(bit.rshift(len, 8), 0xff),
|
||||
bit.band(len, 0xff)
|
||||
)
|
||||
-- n bytes: trailer
|
||||
trailer_buf = trailer_buf .. grpc_web_trailer
|
||||
|
||||
return trailer_buf
|
||||
end
|
||||
|
||||
function _M.access(conf, ctx)
|
||||
-- set context variable mime
|
||||
-- When processing non gRPC Web requests, `mime` can be obtained in the context
|
||||
-- and set to the `Content-Type` of the response
|
||||
ctx.grpc_web_mime = core.request.header(ctx, "Content-Type")
|
||||
|
||||
local method = core.request.get_method()
|
||||
if method == ALLOW_METHOD_OPTIONS then
|
||||
return exit(ctx, 204)
|
||||
end
|
||||
|
||||
if method ~= ALLOW_METHOD_POST then
|
||||
-- https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md#cors-support
|
||||
core.log.error("request method: `", method, "` invalid")
|
||||
return exit(ctx, 405)
|
||||
end
|
||||
|
||||
local encoding = grpc_web_content_encoding[ctx.grpc_web_mime]
|
||||
if not encoding then
|
||||
core.log.error("request Content-Type: `", ctx.grpc_web_mime, "` invalid")
|
||||
return exit(ctx, 400)
|
||||
end
|
||||
|
||||
-- set context variable encoding method
|
||||
ctx.grpc_web_encoding = encoding
|
||||
|
||||
-- set grpc path
|
||||
if not (ctx.curr_req_matched and ctx.curr_req_matched[":ext"]) then
|
||||
core.log.error("routing configuration error, grpc-web plugin only supports ",
|
||||
"`prefix matching` pattern routing")
|
||||
return exit(ctx, 400)
|
||||
end
|
||||
|
||||
local path = ctx.curr_req_matched[":ext"]
|
||||
if path:byte(1) ~= core.string.byte("/") then
|
||||
path = "/" .. path
|
||||
end
|
||||
|
||||
req_set_uri(path)
|
||||
|
||||
-- set grpc body
|
||||
local body, err = core.request.get_body()
|
||||
if err or not body then
|
||||
core.log.error("failed to read request body, err: ", err)
|
||||
return exit(ctx, 400)
|
||||
end
|
||||
|
||||
if encoding == CONTENT_ENCODING_BASE64 then
|
||||
body = decode_base64(body)
|
||||
if not body then
|
||||
core.log.error("failed to decode request body")
|
||||
return exit(ctx, 400)
|
||||
end
|
||||
end
|
||||
|
||||
-- set grpc content-type
|
||||
core.request.set_header(ctx, "Content-Type", DEFAULT_PROXY_CONTENT_TYPE)
|
||||
-- set grpc body
|
||||
req_set_body_data(body)
|
||||
end
|
||||
|
||||
function _M.header_filter(conf, ctx)
|
||||
local method = core.request.get_method()
|
||||
if method == ALLOW_METHOD_OPTIONS then
|
||||
core.response.set_header("Access-Control-Allow-Methods", DEFAULT_CORS_ALLOW_METHODS)
|
||||
core.response.set_header("Access-Control-Allow-Headers", conf.cors_allow_headers)
|
||||
end
|
||||
|
||||
if not ctx.cors_allow_origins then
|
||||
core.response.set_header("Access-Control-Allow-Origin", DEFAULT_CORS_ALLOW_ORIGIN)
|
||||
end
|
||||
core.response.set_header("Access-Control-Expose-Headers", DEFAULT_CORS_EXPOSE_HEADERS)
|
||||
|
||||
if not ctx.grpc_web_skip_body_filter then
|
||||
core.response.set_header("Content-Type", ctx.grpc_web_mime)
|
||||
core.response.set_header("Content-Length", nil)
|
||||
end
|
||||
end
|
||||
|
||||
function _M.body_filter(conf, ctx)
|
||||
if ctx.grpc_web_skip_body_filter then
|
||||
return
|
||||
end
|
||||
|
||||
-- If the MIME extension type description of the gRPC-Web standard is not obtained,
|
||||
-- indicating that the request is not based on the gRPC Web specification,
|
||||
-- the processing of the request body will be ignored
|
||||
-- https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
|
||||
-- https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md#cors-support
|
||||
if not ctx.grpc_web_mime then
|
||||
return
|
||||
end
|
||||
|
||||
if ctx.grpc_web_encoding == CONTENT_ENCODING_BASE64 then
|
||||
local chunk = ngx_arg[1]
|
||||
chunk = encode_base64(chunk)
|
||||
ngx_arg[1] = chunk
|
||||
end
|
||||
|
||||
if ngx_arg[2] then -- if eof
|
||||
local status = ctx.var.upstream_trailer_grpc_status
|
||||
local message = ctx.var.upstream_trailer_grpc_message
|
||||
|
||||
-- When the response body completes and still does not receive the grpc status
|
||||
local resp_ok = status ~= nil and status ~= ""
|
||||
local trailer_buf = build_trailer(
|
||||
resp_ok and status or 2,
|
||||
resp_ok and message or "upstream grpc status not received"
|
||||
)
|
||||
if ctx.grpc_web_encoding == CONTENT_ENCODING_BASE64 then
|
||||
trailer_buf = encode_base64(trailer_buf)
|
||||
end
|
||||
|
||||
ngx_arg[1] = ngx_arg[1] .. trailer_buf
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
Reference in New Issue
Block a user