├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── Vagrantfile ├── kong-plugin-proxy-cache-2.0.0-1.rockspec ├── provision.sh ├── proxy-cache ├── access.lua ├── body_filter.lua ├── cache.lua ├── encoder.lua ├── handler.lua ├── header_filter.lua ├── schema.lua ├── storage.lua └── validators.lua └── spec ├── 01-access-spec.lua ├── 02-cache-spec.lua └── 03-validators-spec.lua /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | [*.lua] 10 | indent_style = space 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | # Vagrant 43 | .vagrant 44 | 45 | # Logging 46 | *.log -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | - Changes that have landed in master but are not yet released. 11 | 12 | ## 2.0.0 - 2018-11-13 13 | ### Added 14 | - Add Redis Sentinel support 15 | 16 | ### Changed 17 | - Change the storage to use `pintsized/lua-resty-redis-connector` instead of `openresty/lua-resty-redis` 18 | 19 | ### Fixed 20 | - Fix error `API disabled in the current context` when `Cache-Control` is `true` 21 | 22 | ## 1.3.3 - 2018-11-12 23 | ### Fixed 24 | - Fix error `API disabled in the current context` when `Cache-Control` is `true` 25 | 26 | ## 1.3.2 - 2018-11-07 27 | ### Changed 28 | - Change the default value for `cache_control` because the RFC behavior must be enabled by default 29 | 30 | ## 1.3.1 - 2018-10-31 31 | ### Fixed 32 | - Change the body_filter should not set the `X-Cache-Status` header. 33 | 34 | ## 1.3.0 - 2018-10-30 35 | ### Added 36 | - Add `max_idle_timeout` and `pool_size` as schema 37 | - Add filter to remove hop-by-hop headers 38 | 39 | ### Changed 40 | - Improves performance 41 | 42 | ## 1.2.5 - 2018-10-29 43 | ### Changed 44 | - Improves performance 45 | 46 | ## 1.2.4 - 2018-10-22 47 | ### Changed 48 | - Improves performance 49 | 50 | ### Fixed 51 | - The plugin should not process the `:body_filter(config)` when `rt_body_chunks` is `nil`. 52 | 53 | ## 1.2.3 - 2018-10-22 54 | ### Changed 55 | - Improves performance 56 | 57 | ## 1.2.2 - 2018-10-22 58 | ### Changed 59 | - Improves performance 60 | 61 | ## 1.2.1 - 2018-10-17 62 | ### Changed 63 | - Improves performance 64 | 65 | ## 1.2.0 - 2018-10-15 66 | ### Fixed 67 | - The `Cache-Control` was implemented incorrectly. It was respecting the client header instead of the upstream header. 68 | 69 | ### Changed 70 | - Change default `response_code` to `200`, `301` and `302` like the nginx default config. 71 | 72 | ### Removed 73 | - Remove `REFRESH` from `X-Cache-Status`. 74 | - Remove `Cache-Control: no-cache` validation on access. 75 | 76 | ## 1.1.0 - 2018-10-09 77 | ### Added 78 | - The `ngx.host` was added to compose cache key. 79 | 80 | ### Fixed 81 | - The cache key composition considered only the last nginx variable. 82 | 83 | ## 1.0.1 - 2018-10-04 84 | ### Added 85 | - Add error handling on plugin exceptions. 86 | 87 | ## 1.0.0 - 2018-10-03 88 | ### Added 89 | - This CHANGELOG file. 90 | 91 | [1.3.2]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.3.1...1.3.2 92 | [1.3.1]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.3.0...1.3.1 93 | [1.3.0]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.2.5...1.3.0 94 | [1.2.5]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.2.4...1.2.5 95 | [1.2.4]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.2.3...1.2.4 96 | [1.2.3]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.2.2...1.2.3 97 | [1.2.2]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.2.1...1.2.2 98 | [1.2.1]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.2.0...1.2.1 99 | [1.2.0]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.1.0...1.2.0 100 | [1.1.0]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.0.1...1.1.0 101 | [1.0.1]: https://github.com/globocom/kong-plugin-proxy-cache/compare/1.0.0...1.0.1 102 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Want to contribute to kong-plugin-proxy-cache? There are a few things you need to know. We wrote this contribution guide to help you get started. 4 | 5 | ## Semantic Versioning 6 | 7 | kong-plugin-proxy-cache follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). We release patch versions for bugfixes, minor versions for new features, and major versions for any breaking changes. 8 | 9 | Every significant change is documented in the changelog file. 10 | 11 | ## Sending a Pull Request 12 | 13 | The core team is monitoring for pull requests. We will review your pull request and either merge it, request changes to it, or close it with an explanation. For API changes we may need to fix our internal uses at Globo.com, which could cause some delay. We’ll do our best to provide updates and feedback throughout the process. 14 | 15 | **Before submitting a pull request**, please make sure the following is done: 16 | 17 | 1. Fork the repository and create your branch from master. 18 | 2. Run `make create-virtualmachine` in the repository root. 19 | 3. If you’ve fixed a bug or added code that should be tested, add tests! 20 | 21 | ### Contribution Prerequisites 22 | 23 | * You have [Vagrant](https://www.vagrantup.com/) installed. 24 | * You have [VirtualBox](https://www.virtualbox.org/) installed. 25 | * You are familiar with Git. 26 | 27 | ### Development Workflow 28 | 29 | After cloning kong-plugin-proxy-cache, run `make create-virtualmachine` to build the virtual machine. Then, you can run several commands: 30 | 31 | * `make help` show the help 32 | * `make test` runs tests for Kong Plugin 33 | * `make create-virtualmachine` build the virtual machine 34 | * `make remove-virtualmachine` delete the virtual machine 35 | 36 | ## License 37 | 38 | By contributing to kong-plugin-proxy-cache, you agree that your contributions will be licensed under its [MIT license](./LICENSE). 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Globo.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VAGRANT_PROVIDER=virtualbox 2 | # COLORS 3 | GREEN := $(shell tput -Txterm setaf 2) 4 | YELLOW := $(shell tput -Txterm setaf 3) 5 | WHITE := $(shell tput -Txterm setaf 7) 6 | RED := $(shell tput -Txterm setaf 1) 7 | RESET := $(shell tput -Txterm sgr0) 8 | 9 | 10 | TARGET_MAX_CHAR_NUM=20 11 | 12 | .PHONY: help 13 | 14 | 15 | ## Show this help. 16 | help: 17 | @echo 'Usage:' 18 | @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' 19 | @echo '' 20 | @echo 'Targets:' 21 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 22 | helpMessage = match(lastLine, /^## (.*)/); \ 23 | if (helpMessage) { \ 24 | helpCommand = substr($$1, 0, index($$1, ":")); \ 25 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ 26 | printf " ${YELLOW}%-$(TARGET_MAX_CHAR_NUM)s${RESET} ${GREEN}%s${RESET}\n", helpCommand, helpMessage; \ 27 | } \ 28 | } \ 29 | { lastLine = $$0 }' $(MAKEFILE_LIST) 30 | 31 | 32 | ## build the virtual machine 33 | create-virtualmachine: 34 | @echo '${GREEN}==>${RESET} Building the Virtual Machine'; 35 | @vagrant up --provider=$(VAGRANT_PROVIDER); 36 | @echo '${GREEN}==>${RESET} Done.'; 37 | 38 | 39 | ## delete the virtual machine 40 | remove-virtualmachine: 41 | @echo '${GREEN}==>${RESET} Deleting the Virtual Machine'; 42 | @vagrant destroy; 43 | @echo '${GREEN}==>${RESET} Done.'; 44 | 45 | ## runs tests for Kong Plugin 46 | test: 47 | @echo '${GREEN}==>${RESET} Testing Kong Plugin in Virtual Machine'; 48 | @vagrant ssh -c "export PATH=${PATH}:/usr/local/openresty/bin && \ 49 | cd /kong && \ 50 | bin/busted -v -o gtest /proxy-cache-spec --pattern=*-spec.lua --defer-print"; 51 | @echo '${GREEN}==>${RESET} Done.'; 52 | 53 | ## create a new version of plugin. ex: make new_version TAG=1.0.0 54 | new_version: 55 | @luarocks new_version --tag=$(TAG) 56 | @ls kong-plugin-proxy-cache* | grep $(TAG) | xargs luarocks pack 57 | @ls kong-plugin-proxy-cache* | grep -v $(TAG) | xargs rm 58 | 59 | publish: 60 | @ls kong-plugin-proxy-cache-*.rockspec | xargs luarocks upload --api-key=${API_KEY} 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | kong-plugin-proxy-cache 3 |

4 | 5 |

6 | A Proxy Caching plugin for Kong 7 |

8 | 9 |

10 | 11 | 12 | 13 |

14 | 15 | A Proxy Caching plugin for Kong makes it fast and easy to configure caching of responses and serving of those cached responses in Redis. It caches responses bases on configurable response code and request headers with the request method. 16 | 17 | This Kong plugin adds a non-standard `X-Cache-Status` header. There are several possible values for this header: 18 | 19 | * `MISS`: The resource was not found in cache, and the request was proxied upstream. 20 | * `HIT`: The request was satisfied and served from cache. 21 | * `BYPASS`: The request could not be satisfied from cache based on plugin settings. 22 | 23 | ## Getting started 24 | 25 | Configure this plugin on a Service by making the following request: 26 | 27 | ```shell 28 | $ curl -X POST http://kong:8001/services/debug-upstream/plugins \ 29 | --data "name=proxy-cache" \ 30 | --data "config.cache_ttl=300" \ 31 | --data "config.redis.host=127.0.0.1" 32 | ``` 33 | 34 | * **service**: the id or name of the Service that this plugin configuration will target. 35 | 36 | ## Configuration 37 | 38 | Here's a list of all the settings which can be used in this plugin: 39 | 40 | > Note: The required fields are in bold. 41 | 42 | | Field | Default | Description 43 | |----------------|---------------|---------------------------------------------------- 44 | | **response_code** | 200, 301, 302 | Upstream response status code considered cacheable 45 | | vary_headers | | Relevant headers considered for the cache key 46 | | vary_nginx_variables | | Relevant nginx variables considered for the cache key 47 | | **cache_ttl** | 300 | TTL, in seconds, of cache responses 48 | | cache_control | true | Respect the Cache-Control behaviors 49 | | **redis.host** | | Host to use for Redis connection 50 | | **redis.port** | 6379 | Port to use for Redis connection 51 | | **redis.timeout** | 2000 | Connection timeout to use for Redis connection 52 | | redis.password | | Password to use for Redis connection 53 | | **redis.database** | 0 | Database to use for Redis connection 54 | | redis.sentinel_master_name | | Sentinel master name to use for Redis connection. Defining this value implies using Redis Sentinel 55 | | redis.sentinel_role | | Sentinel role to use for Redis connection 56 | | redis.sentinel_addresses | | Sentinel address to use for Redis connection 57 | | **redis.max_idle_timeout** | 10000 | maximal idle timeout (in ms) for keep alive the Redis connection 58 | | **redis.pool_size** | 1000 | maximal pool size by Redis connection 59 | 60 | ### Cache Control 61 | 62 | When the `cache_control` is enabled by settings, Kong will respect request and response `Cache-Control` headers as defined by RFC7234, with a few notes: 63 | 64 | * The behavior of no-cache is simplified to exclude the entity from being cached entirely. 65 | * Secondary key calculation via Vary is not yet supported. 66 | 67 | ## Installing 68 | 69 | ### From LuaRocks 70 | 71 | ```shell 72 | $ luarocks install kong-plugin-proxy-cache 73 | ``` 74 | 75 | ### From GitHub 76 | 77 | ```shell 78 | $ sh -c "git clone https://github.com/globocom/kong-plugin-proxy-cache /tmp/kong-plugin-proxy-cache && cd /tmp/kong-plugin-proxy-cache && luarocks make *.rockspec" 79 | ``` 80 | 81 | ## Contributing 82 | 83 | Please, read the contribute guide [CONTRIBUTING](./CONTRIBUTING.md). 84 | 85 | ## License 86 | 87 | kong-plugin-proxy-cache is [MIT licensed](./LICENSE). 88 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.require_version ">= 1.4.3" 7 | 8 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 9 | if ENV["KONG_PATH"] 10 | source = ENV["KONG_PATH"] 11 | elsif File.directory?("./kong") 12 | source = "./kong" 13 | elsif File.directory?("../kong") 14 | source = "../kong" 15 | else 16 | source = "" 17 | end 18 | 19 | plugin_source = "./proxy-cache" 20 | 21 | if ENV['KONG_VB_MEM'] 22 | memory = ENV["KONG_VB_MEM"] 23 | else 24 | memory = 4096 25 | end 26 | 27 | if ENV["KONG_NGINX_WORKER_PROCESSES"] 28 | cpus = ENV["KONG_NGINX_WORKER_PROCESSES"] 29 | else 30 | cpus = 2 31 | end 32 | 33 | if ENV["KONG_VERSION"] 34 | version = ENV["KONG_VERSION"] 35 | else 36 | version = "0.14.1" 37 | end 38 | 39 | if ENV["KONG_CASSANDRA"] 40 | cversion = ENV["KONG_CASSANDRA"] 41 | if not cversion == "2" 42 | if not cversion == "3" 43 | raise "KONG_CASSANDRA must be either 2 or 3" 44 | end 45 | end 46 | else 47 | # set a default, 3.x, or for Kong < 0.10 then 2.x 48 | cversion = "3" 49 | v = version.split('.') 50 | if v[1].strip.to_i == 0 51 | if v[2].strip.to_i < 10 52 | cversion = "2" 53 | end 54 | end 55 | end 56 | 57 | if ENV["KONG_UTILITIES"] 58 | utils = "1" 59 | else 60 | utils = "" 61 | end 62 | 63 | if ENV["KONG_ANONYMOUS_REPORTS"] 64 | anreports = ENV["KONG_ANONYMOUS_REPORTS"] 65 | else 66 | anreports = "" 67 | end 68 | 69 | if ENV["KONG_LOG_LEVEL"] 70 | loglevel = ENV["KONG_LOG_LEVEL"] 71 | else 72 | loglevel = "" 73 | end 74 | 75 | config.vm.provider :virtualbox do |vb| 76 | vb.name = "vagrant_kong" 77 | vb.memory = memory 78 | vb.cpus = cpus 79 | vb.customize ["guestproperty", "set", :id, "/VirtualBox/GuestAdd/VBoxService/ — timesync-set-threshold", 10000] 80 | end 81 | 82 | config.vm.box = "ubuntu/xenial64" 83 | 84 | if not source == "" 85 | config.vm.synced_folder source, "/kong" 86 | end 87 | if not plugin_source == "" 88 | config.vm.synced_folder plugin_source, "/proxy-cache" 89 | end 90 | config.vm.synced_folder 'spec', "/proxy-cache-spec" 91 | 92 | config.vm.network :forwarded_port, guest: 8000, host: 8000 93 | config.vm.network :forwarded_port, guest: 8001, host: 8001 94 | config.vm.network :forwarded_port, guest: 8443, host: 8443 95 | config.vm.network :forwarded_port, guest: 8444, host: 8444 96 | config.vm.network :forwarded_port, guest: 5432, host: 65432 97 | 98 | config.vm.provision "shell", path: "provision.sh", 99 | env: { "HTTP_PROXY": ENV["HTTP_PROXY"], "HTTPS_PROXY": ENV["HTTPS_PROXY"]}, 100 | :args => [version, cversion, utils, anreports, loglevel] 101 | end -------------------------------------------------------------------------------- /kong-plugin-proxy-cache-2.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "kong-plugin-proxy-cache" 2 | version = "2.0.0-1" 3 | source = { 4 | url = "git+ssh://git@github.com/globocom/kong-plugin-proxy-cache.git", 5 | tag = "2.0.0" 6 | } 7 | description = { 8 | detailed = "A Proxy Caching plugin for Kong", 9 | homepage = "https://github.com/globocom/kong-plugin-proxy-cache", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["kong.plugins.proxy-cache.access"] = "proxy-cache/access.lua", 16 | ["kong.plugins.proxy-cache.body_filter"] = "proxy-cache/body_filter.lua", 17 | ["kong.plugins.proxy-cache.cache"] = "proxy-cache/cache.lua", 18 | ["kong.plugins.proxy-cache.encoder"] = "proxy-cache/encoder.lua", 19 | ["kong.plugins.proxy-cache.handler"] = "proxy-cache/handler.lua", 20 | ["kong.plugins.proxy-cache.header_filter"] = "proxy-cache/header_filter.lua", 21 | ["kong.plugins.proxy-cache.schema"] = "proxy-cache/schema.lua", 22 | ["kong.plugins.proxy-cache.storage"] = "proxy-cache/storage.lua", 23 | ["kong.plugins.proxy-cache.validators"] = "proxy-cache/validators.lua" 24 | }, 25 | dependencies = { 26 | "lua-resty-redis-connector == 0.07" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /provision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | 5 | #Stop the STDin warnings 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | 9 | # parse/set up input parameters 10 | 11 | KONG_VERSION=$1 12 | CASSANDRA_VERSION=$2 13 | KONG_UTILITIES=$3 14 | ANREPORTS=$4 15 | LOGLEVEL=$5 16 | 17 | VERSION_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z-]*\)' 18 | KONG_MAJOR=$(echo $1 | sed -e "s#$VERSION_RE#\1#") 19 | KONG_MINOR=$(echo $1 | sed -e "s#$VERSION_RE#\2#") 20 | KONG_PATCH=$(echo $1 | sed -e "s#$VERSION_RE#\3#") 21 | let "KONG_NUM_VERSION = KONG_MAJOR * 10000 + KONG_MINOR * 100 + KONG_PATCH" 22 | 23 | echo "*************************************************************************" 24 | echo "Installing Kong version: $KONG_VERSION" 25 | echo "*************************************************************************" 26 | 27 | 28 | if [ "$CASSANDRA_VERSION" = "2" ]; then 29 | CASSANDRA_VERSION=2.2.8 30 | else 31 | CASSANDRA_VERSION=3.0.9 32 | fi 33 | 34 | POSTGRES_VERSION=9.5 35 | 36 | #Set some version dependent options 37 | 38 | KONG_DOWNLOAD_URL="https://github.com/Kong/kong/releases/download/$KONG_VERSION/kong-$KONG_VERSION.precise_all.deb" 39 | KONG_ADMIN_LISTEN="0.0.0.0:8001, 0.0.0.0:8444 ssl" 40 | 41 | if [ $KONG_NUM_VERSION -gt 001003 ]; then 42 | #Kong 0.10.4 and later are on Bintray 43 | KONG_DOWNLOAD_URL="https://bintray.com/kong/kong-community-edition-deb/download_file?file_path=dists%2Fkong-community-edition-${KONG_VERSION}.trusty.all.deb" 44 | fi 45 | 46 | 47 | 48 | 49 | # set up the environment 50 | 51 | # Assign permissions to "vagrant" user 52 | sudo chown -R vagrant /usr/local 53 | 54 | if [ -n "$HTTP_PROXY" -o -n "$HTTPS_PROXY" ]; then 55 | touch /etc/profile.d/proxy.sh 56 | touch /etc/apt/apt.conf.d/50proxy 57 | fi 58 | 59 | if [ -n "$HTTP_PROXY" ]; then 60 | printf "using http proxy: %s\n" $HTTP_PROXY 61 | 62 | echo "http_proxy=$HTTP_PROXY" >> /etc/profile.d/proxy.sh 63 | echo "HTTP_PROXY=$HTTP_PROXY" >> /etc/profile.d/proxy.sh 64 | echo "Acquire::http::proxy \"$HTTP_PROXY\";" >> /etc/apt/apt.conf.d/50proxy 65 | echo "http_proxy=$HTTP_PROXY" >> /etc/wgetrc 66 | fi 67 | 68 | if [ -n "$HTTPS_PROXY" ]; then 69 | printf "using https proxy: %s\n" $HTTPS_PROXY 70 | 71 | echo "https_proxy=$HTTPS_PROXY" >> /etc/profile.d/proxy.sh 72 | echo "HTTPS_PROXY=$HTTPS_PROXY" >> /etc/profile.d/proxy.sh 73 | echo "Acquire::https::proxy \"$HTTP_PROXY\";" >> /etc/apt/apt.conf.d/50proxy 74 | echo "https_proxy=$HTTPS_PROXY" >> /etc/wgetrc 75 | fi 76 | 77 | 78 | echo "*************************************************************************" 79 | echo Setting up APT repositories 80 | echo "*************************************************************************" 81 | 82 | #postgres 83 | sudo add-apt-repository "deb https://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" 84 | wget --quiet -O - https://postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 85 | 86 | #cassandra 87 | if [ ! -f /etc/apt/sources.list.d/cassandra.sources.list ]; then 88 | echo 'deb http://debian.datastax.com/community stable main' | sudo tee -a /etc/apt/sources.list.d/cassandra.sources.list 89 | wget -q -O - '$@' http://debian.datastax.com/debian/repo_key | sudo apt-key add - 90 | fi 91 | 92 | sudo apt-get update 93 | 94 | echo "*************************************************************************" 95 | echo Installing APT packages 96 | echo "*************************************************************************" 97 | 98 | 99 | #generic usability utilities 100 | sudo apt-get install -y httpie jq 101 | 102 | #Installing required dependencies 103 | sudo apt-get install -y git curl make pkg-config unzip apt-transport-https language-pack-en libssl-dev 104 | 105 | echo "*************************************************************************" 106 | echo "installing test tools for Test::Nginx" 107 | echo "*************************************************************************" 108 | 109 | echo "y" | cpan 110 | cpan -T Test::Nginx 111 | 112 | #################### 113 | echo "*************************************************************************" 114 | echo Installing and configuring Postgres 115 | echo "*************************************************************************" 116 | 117 | set +o errexit 118 | dpkg --list postgresql-$POSTGRES_VERSION > /dev/null 2>&1 119 | if [ $? -ne 0 ]; then 120 | sudo apt-get install -y postgresql-$POSTGRES_VERSION 121 | 122 | # Configure Postgres 123 | sudo sed -i "s/#listen_address.*/listen_addresses '*'/" /etc/postgresql/$POSTGRES_VERSION/main/postgresql.conf 124 | sudo bash -c "cat > /etc/postgresql/$POSTGRES_VERSION/main/pg_hba.conf" << EOL 125 | local all all trust 126 | host all all 127.0.0.1/32 trust 127 | host all all ::1/128 trust 128 | host all all 0.0.0.0/0 trust 129 | EOL 130 | 131 | sudo /etc/init.d/postgresql restart 132 | 133 | # Create PG user and database 134 | psql -U postgres < /dev/null 2>&1 160 | if [ $? -ne 0 ]; then 161 | echo Fetching and installing java... 162 | sudo mkdir -p /usr/lib/jvm 163 | sudo wget -q -O /tmp/jre-linux-x64.tar.gz --no-cookies --no-check-certificate --header 'Cookie: oraclelicense=accept-securebackup-cookie' http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jre-8u131-linux-x64.tar.gz 164 | sudo tar zxf /tmp/jre-linux-x64.tar.gz -C /usr/lib/jvm 165 | sudo update-alternatives --install '/usr/bin/java' 'java' '/usr/lib/jvm/jre1.8.0_131/bin/java' 1 166 | sudo update-alternatives --set java /usr/lib/jvm/jre1.8.0_131/bin/java 167 | fi 168 | 169 | #Install cassandra 170 | dpkg --list cassandra > /dev/null 2>&1 171 | if [ $? -ne 0 ]; then 172 | echo Fetching and installing Cassandra... 173 | sudo apt-get install cassandra=$CASSANDRA_VERSION -y --force-yes 174 | sudo /etc/init.d/cassandra restart 175 | fi 176 | set -o errexit 177 | ################ 178 | echo "*************************************************************************" 179 | echo Fetching and installing Kong... 180 | echo "*************************************************************************" 181 | 182 | wget -q -O kong.deb "$KONG_DOWNLOAD_URL" 183 | 184 | if [ $KONG_NUM_VERSION -lt 1000 ]; then 185 | #dnsmasq for Kong 0.9.x and earlier 186 | sudo apt-get install -y dnsmasq 187 | fi 188 | 189 | sudo dpkg -i kong.deb 190 | rm kong.deb 191 | 192 | 193 | ########################### 194 | # Install profiling tools # 195 | ########################### 196 | if [ -n "$KONG_UTILITIES" ]; then 197 | # install tools 198 | echo "*************************************************************************" 199 | echo Installing systemtap, stapxx and openresty-systemtap-toolkit 200 | echo "*************************************************************************" 201 | 202 | # install systemtap 203 | # https://openresty.org/en/build-systemtap.html 204 | sudo apt-get install -y build-essential zlib1g-dev elfutils libdw-dev gettext 205 | wget -q http://sourceware.org/systemtap/ftp/releases/systemtap-3.0.tar.gz 206 | tar -xf systemtap-3.0.tar.gz 207 | cd systemtap-3.0/ 208 | ./configure --prefix=/opt/stap --disable-docs \ 209 | --disable-publican --disable-refdocs CFLAGS="-g -O2" 210 | make 211 | sudo make install 212 | rm -rf ./systemtap-3.0 systemtap-3.0.tar.gz 213 | 214 | # install stapxx and openresty-systemtap-toolkit 215 | pushd /usr/local 216 | git clone https://github.com/openresty/stapxx.git 217 | git clone https://github.com/openresty/openresty-systemtap-toolkit.git 218 | 219 | # install flamegraph 220 | git clone https://github.com/brendangregg/FlameGraph.git 221 | 222 | # install wrk and copy the binary to a location in PATH 223 | git clone https://github.com/wg/wrk.git 224 | cd wrk && make && sudo cp ./wrk /usr/local/bin/ && cd .. 225 | popd 226 | fi 227 | 228 | 229 | echo "*************************************************************************" 230 | echo Update localization, paths, ulimit, etc 231 | echo "*************************************************************************" 232 | 233 | # Aliases 234 | echo 'alias ks="kong start -c kong.conf.default"' >> /home/vagrant/.bashrc 235 | echo 'alias kmu="kong migrations up -c kong.conf.default"' >> /home/vagrant/.bashrc 236 | echo 'alias kmr="kong migrations reset -c kong.conf.default"' >> /home/vagrant/.bashrc 237 | echo 'alias kss="kong stop ; ks"' >> /home/vagrant/.bashrc 238 | 239 | # Adjust PATH 240 | export PATH=$PATH:/usr/local/bin:/usr/local/openresty/bin:/opt/stap/bin:/usr/local/stapxx 241 | 242 | # Prepare path to lua libraries 243 | ln -sfn /usr/local /home/vagrant/.luarocks 244 | 245 | # Set higher ulimit 246 | sudo bash -c 'echo "fs.file-max = 65536" >> /etc/sysctl.conf' 247 | sudo sysctl -p 248 | sudo bash -c "cat >> /etc/security/limits.conf" << EOL 249 | * soft nproc 65535 250 | * hard nproc 65535 251 | * soft nofile 65535 252 | * hard nofile 65535 253 | EOL 254 | 255 | 256 | # Adjust PATH for future ssh 257 | echo "export PATH=\$PATH:/usr/local/bin:/usr/local/openresty/bin:/opt/stap/bin:/usr/local/stapxx:/usr/local/openresty/nginx/sbin:/usr/local/openresty/luajit/bin" >> /home/vagrant/.bashrc 258 | 259 | # do the same for root so we access to profiling tools 260 | echo "export PATH=\$PATH:/usr/local/bin:/usr/local/openresty/bin:/opt/stap/bin:/usr/local/stapxx:/usr/local/openresty/nginx/sbin:/usr/local/openresty/luajit/bin" >> /root/.bashrc 261 | 262 | # copy host settings 263 | if [ -n "$LOGLEVEL" ]; then 264 | echo "export KONG_LOG_LEVEL=$LOGLEVEL" >> /home/vagrant/.bashrc 265 | fi 266 | if [ -n "$ANREPORTS" ]; then 267 | echo "export KONG_ANONYMOUS_REPORTS=$ANREPORTS" >> /home/vagrant/.bashrc 268 | fi 269 | 270 | # create prefix (working directory) to the same location as source tree if available 271 | if [ ! -d "/kong" ]; then 272 | sudo mkdir /kong 273 | sudo chown -R vagrant /kong 274 | fi 275 | echo "export KONG_PREFIX=/kong/servroot" >> /home/vagrant/.bashrc 276 | 277 | # set admin listen addresses 278 | echo "export KONG_ADMIN_LISTEN=\"$KONG_ADMIN_LISTEN\"" >> /home/vagrant/.bashrc 279 | 280 | # Adjust LUA_PATH to find the plugin dev setup 281 | echo "export LUA_PATH=\"/proxy-cache/?.lua;/proxy-cache/?/init.lua;;\"" >> /home/vagrant/.bashrc 282 | echo "if [ \$((1 + RANDOM % 20)) -eq 1 ]; then kong roar; fi" >> /home/vagrant/.bashrc 283 | 284 | # set Test::Nginx variables since it cannot run on a mounted 285 | # drive, hence we move it into home. 286 | echo "export TEST_NGINX_CERT_DIR=/kong/t/certs" >> /home/vagrant/.bashrc 287 | echo "export TEST_NGINX_SERVROOT=/home/vagrant/servroot" >> /home/vagrant/.bashrc 288 | 289 | # Set locale 290 | echo "export LC_ALL=en_US.UTF-8" >> /home/vagrant/.bashrc 291 | echo "export LC_CTYPE=en_US.UTF-8" >> /home/vagrant/.bashrc 292 | # fix locale warning 293 | sudo echo "LC_CTYPE=\"en_US.UTF-8\"" >> /etc/default/locale 294 | sudo echo "LC_ALL=\"en_US.UTF-8\"" >> /etc/default/locale 295 | 296 | # Assign permissions to "vagrant" user 297 | sudo chown -R vagrant /usr/local 298 | 299 | # Install lua packages 300 | git clone https://github.com/Kong/kong.git /kong 301 | git config --global url."https://".insteadOf git:// 302 | export LUA_DEPENDENCIES="busted luacheck lua-llthreads2 lua-resty-redis-connector" 303 | echo $LUA_DEPENDENCIES | xargs -n 1 sudo luarocks install 304 | 305 | # Install Redis server 306 | sudo apt install -y make gcc libc6-dev tcl 307 | wget http://download.redis.io/redis-stable.tar.gz 308 | tar xvzf redis-stable.tar.gz 309 | cd redis-stable && sudo make install 310 | redis-server --daemonize yes 311 | 312 | # Linking plugin 313 | sudo ln -s /proxy-cache /usr/local/share/lua/5.1/kong/plugins/proxy-cache 314 | 315 | echo . 316 | echo "Successfully Installed Kong version: $KONG_VERSION" 317 | -------------------------------------------------------------------------------- /proxy-cache/access.lua: -------------------------------------------------------------------------------- 1 | local Storage = require 'kong.plugins.proxy-cache.storage' 2 | local validators = require 'kong.plugins.proxy-cache.validators' 3 | local Cache = require 'kong.plugins.proxy-cache.cache' 4 | local Encoder = require 'kong.plugins.proxy-cache.encoder' 5 | 6 | local _M = {} 7 | 8 | local function render_from_cache(cache_key, cached_value) 9 | local response = Encoder.decode(cached_value) 10 | if response.headers then 11 | for header, value in pairs(response.headers) do 12 | ngx.header[header] = value 13 | end 14 | end 15 | ngx.header['X-Cache-Status'] = 'HIT' 16 | ngx.status = response.status 17 | ngx.print(response.content) 18 | ngx.exit(response.status) 19 | end 20 | 21 | function _M.execute(config) 22 | local storage = Storage:new() 23 | local cache = Cache:new() 24 | storage:set_config(config) 25 | cache:set_config(config) 26 | 27 | if not validators.check_request_method() then 28 | ngx.header['X-Cache-Status'] = 'BYPASS' 29 | return 30 | end 31 | local cache_key = cache:generate_cache_key(ngx.req, ngx.var) 32 | local cached_value, err = storage:get(cache_key) 33 | if not (cached_value and cached_value ~= ngx.null) then 34 | ngx.header['X-Cache-Status'] = 'MISS' 35 | ngx.ctx.cache_key = cache_key 36 | ngx.ctx.rt_body_chunks = {} 37 | ngx.ctx.rt_body_chunk_number = 1 38 | return 39 | end 40 | return render_from_cache(cache_key, cached_value) 41 | end 42 | 43 | return _M 44 | -------------------------------------------------------------------------------- /proxy-cache/body_filter.lua: -------------------------------------------------------------------------------- 1 | local Storage = require 'kong.plugins.proxy-cache.storage' 2 | local validators = require 'kong.plugins.proxy-cache.validators' 3 | local Cache = require 'kong.plugins.proxy-cache.cache' 4 | local Encoder = require 'kong.plugins.proxy-cache.encoder' 5 | 6 | local _M = {} 7 | 8 | local function filter_headers(headers) 9 | -- remove hop-by-hop headers 10 | headers["Connection"] = nil 11 | headers["connection"] = nil 12 | headers["Keep-Alive"] = nil 13 | headers["keep-alive"] = nil 14 | headers["Public"] = nil 15 | headers["public"] = nil 16 | headers["Proxy-Authenticate"] = nil 17 | headers["proxy-authenticate"] = nil 18 | headers["Transfer-Encoding"] = nil 19 | headers["transfer-encoding"] = nil 20 | headers["Upgrade"] = nil 21 | headers["upgrade"] = nil 22 | headers["Via"] = nil 23 | headers["via"] = nil 24 | -- remove kong custom headers 25 | headers["X-Kong-Upstream-Latency"] = nil 26 | headers["X-Kong-Proxy-Latency"] = nil 27 | -- remove plugin custom headers 28 | headers["X-Cache-Status"] = nil 29 | return headers 30 | end 31 | 32 | local function async_update_cache(config, cache_key, body) 33 | local cache = Cache:new() 34 | cache:set_config(config) 35 | 36 | local cache_ttl = cache:cache_ttl() 37 | local headers = ngx.resp.get_headers(0, true) 38 | local status = ngx.status 39 | ngx.timer.at(0, function(premature) 40 | local storage = Storage:new() 41 | storage:set_config(config) 42 | if cache_ttl ~= nil then 43 | local cache_value = Encoder.encode(status, body, filter_headers(headers)) 44 | storage:set(cache_key, cache_value, cache_ttl) 45 | end 46 | end) 47 | end 48 | 49 | function _M.execute(config) 50 | local cache_key = ngx.ctx.cache_key 51 | local rt_body_chunks = ngx.ctx.rt_body_chunks 52 | local rt_body_chunk_number = ngx.ctx.rt_body_chunk_number 53 | local chunk, eof = ngx.arg[1], ngx.arg[2] 54 | 55 | if eof then 56 | local body = table.concat(rt_body_chunks) 57 | ngx.arg[1] = body 58 | if validators.check_response_code(config.response_code, ngx.status) and 59 | validators.check_request_method() then 60 | return async_update_cache(config, cache_key, body) 61 | end 62 | else 63 | rt_body_chunks[rt_body_chunk_number] = chunk 64 | rt_body_chunk_number = rt_body_chunk_number + 1 65 | ngx.arg[1] = nil 66 | ngx.ctx.rt_body_chunks = rt_body_chunks 67 | ngx.ctx.rt_body_chunk_number = rt_body_chunk_number 68 | end 69 | end 70 | 71 | return _M 72 | -------------------------------------------------------------------------------- /proxy-cache/cache.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local function append_to_cache_key(cache_key, list, allowlist) 4 | local ordered_list = list 5 | table.sort(ordered_list) 6 | for _, allowed in ipairs(allowlist) do 7 | local value = ordered_list[allowed] 8 | if value then 9 | if type(value) == "table" then 10 | table.sort(value) 11 | value = table.concat(value, ",") 12 | end 13 | cache_key = cache_key..":"..allowed.."="..value 14 | end 15 | end 16 | return cache_key 17 | end 18 | 19 | function _M:new(o) 20 | o = o or {} 21 | setmetatable(o, self) 22 | self.__index = self 23 | return o 24 | end 25 | 26 | function _M:set_config(config) 27 | self.config = config or {} 28 | end 29 | 30 | function _M:generate_cache_key(request, nginx_variables) 31 | local cache_key = nginx_variables.host..':'..request.get_method()..':'..nginx_variables.request_uri 32 | if self.config.vary_headers then 33 | cache_key = append_to_cache_key(cache_key, request.get_headers(), self.config.vary_headers) 34 | end 35 | if self.config.vary_nginx_variables then 36 | cache_key = append_to_cache_key(cache_key, nginx_variables, self.config.vary_nginx_variables) 37 | end 38 | return string.lower(cache_key) 39 | end 40 | 41 | function _M:cache_ttl() 42 | if self.config.cache_control then 43 | local cache_control = ngx.header['cache-control'] or '' 44 | return string.match(cache_control, '[max-age=](%d+)') 45 | end 46 | return self.config.cache_ttl 47 | end 48 | 49 | return _M 50 | -------------------------------------------------------------------------------- /proxy-cache/encoder.lua: -------------------------------------------------------------------------------- 1 | local cjson_decode = require("cjson").decode 2 | local cjson_encode = require("cjson").encode 3 | 4 | local _M = {} 5 | 6 | local function json_decode(json) 7 | if json then 8 | local status, res = pcall(cjson_decode, json) 9 | if status then 10 | return res 11 | end 12 | end 13 | end 14 | 15 | local function json_encode(table) 16 | if table then 17 | local status, res = pcall(cjson_encode, table) 18 | if status then 19 | return res 20 | end 21 | end 22 | end 23 | 24 | function _M.encode(status, content, headers) 25 | return json_encode({ 26 | status = status, 27 | content = content, 28 | headers = headers 29 | }) 30 | end 31 | 32 | function _M.decode(str) 33 | return json_decode(str) 34 | end 35 | 36 | return _M 37 | -------------------------------------------------------------------------------- /proxy-cache/handler.lua: -------------------------------------------------------------------------------- 1 | local BasePlugin = require "kong.plugins.base_plugin" 2 | local access = require 'kong.plugins.proxy-cache.access' 3 | local body_filter = require 'kong.plugins.proxy-cache.body_filter' 4 | local header_filter = require 'kong.plugins.proxy-cache.header_filter' 5 | 6 | local ProxyCaching = BasePlugin:extend() 7 | 8 | ProxyCaching.PRIORITY = 1006 9 | ProxyCaching.VERSION = '2.0.0' 10 | 11 | function ProxyCaching:new() 12 | ProxyCaching.super.new(self, "proxy-cache") 13 | end 14 | 15 | function ProxyCaching:init_worker() 16 | ProxyCaching.super.init_worker(self) 17 | end 18 | 19 | function ProxyCaching:access(config) 20 | ProxyCaching.super.access(self) 21 | local ok, err = pcall(access.execute, config) 22 | if not ok then 23 | ngx.log(ngx.CRIT, err) 24 | end 25 | end 26 | 27 | function ProxyCaching:header_filter(config) 28 | ProxyCaching.super.header_filter(self) 29 | local ok, err = pcall(header_filter.execute, config) 30 | if not ok then 31 | ngx.log(ngx.CRIT, err) 32 | end 33 | end 34 | 35 | function ProxyCaching:body_filter(config) 36 | ProxyCaching.super.body_filter(self) 37 | local rt_body_chunks = ngx.ctx.rt_body_chunks 38 | local is_miss = ngx.header['X-Cache-Status'] == 'MISS' 39 | if rt_body_chunks and is_miss then 40 | local ok, err = pcall(body_filter.execute, config) 41 | if not ok then 42 | ngx.log(ngx.CRIT, err) 43 | end 44 | end 45 | end 46 | 47 | return ProxyCaching 48 | -------------------------------------------------------------------------------- /proxy-cache/header_filter.lua: -------------------------------------------------------------------------------- 1 | local validators = require 'kong.plugins.proxy-cache.validators' 2 | 3 | local _M = {} 4 | 5 | function _M.execute(config) 6 | if not validators.check_response_code(config.response_code, ngx.status) then 7 | ngx.header['X-Cache-Status'] = 'BYPASS' 8 | end 9 | end 10 | 11 | return _M 12 | -------------------------------------------------------------------------------- /proxy-cache/schema.lua: -------------------------------------------------------------------------------- 1 | local function server_port(given_value, given_config) 2 | if given_value > 65534 then 3 | return false, "port value too high" 4 | end 5 | end 6 | 7 | return { 8 | no_consume = true, 9 | 10 | fields = { 11 | response_code = { 12 | type = "array", 13 | default = {"200", "301", "302"}, 14 | required = true 15 | }, 16 | vary_headers = { 17 | type = "array", 18 | required = false 19 | }, 20 | vary_nginx_variables = { 21 | type = "array", 22 | required = false 23 | }, 24 | cache_ttl = { 25 | type = "number", 26 | default = 300, 27 | required = true 28 | }, 29 | cache_control = {type = "boolean", default = true}, 30 | redis = { 31 | type = "table", 32 | schema = { 33 | fields = { 34 | host = {type = "string", required = false}, 35 | sentinel_master_name = {type = "string", required = false}, 36 | sentinel_role = {type = "string", required = false, default = "master"}, 37 | sentinel_addresses = {type = "array", required = false}, 38 | port = { 39 | type = "number", 40 | func = server_port, 41 | default = 6379, 42 | required = true 43 | }, 44 | timeout = {type = "number", required = true, default = 2000}, 45 | password = {type = "string", required = false}, 46 | database = {type = "number", required = true, default = 0}, 47 | max_idle_timeout = {type = "number", required = true, default = 10000}, 48 | pool_size = {type = "number", required = true, default = 1000} 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /proxy-cache/storage.lua: -------------------------------------------------------------------------------- 1 | local redis_connector = require("resty.redis.connector") 2 | 3 | local _M = {} 4 | 5 | function _M:new(o) 6 | o = o or {} 7 | setmetatable(o, self) 8 | self.__index = self 9 | return o 10 | end 11 | 12 | function _M:set_config(config) 13 | local redis_config = { 14 | host = config.redis.host, 15 | port = config.redis.port, 16 | password = config.redis.password, 17 | db = config.redis.database, 18 | read_timeout = config.redis.timeout, 19 | keepalive_timeout = config.redis.max_idle_timeout, 20 | keepalive_poolsize = config.redis.pool_size 21 | } 22 | local sentinel_master_name = config.redis.sentinel_master_name 23 | if sentinel_master_name ~= nil and string.len(sentinel_master_name) > 0 then 24 | redis_config.master_name = sentinel_master_name 25 | redis_config.role = config.redis.sentinel_role 26 | local sentinels = config.redis.sentinel_addresses 27 | if sentinels then 28 | redis_config.sentinels = {} 29 | for _, sentinel in ipairs(sentinels) do 30 | local sentinel_host, sentinel_port = string.match(sentinel, "(.*)[:](%d*)") 31 | redis_config.sentinels[#redis_config.sentinels+1] = { 32 | host = sentinel_host, 33 | port = sentinel_port 34 | } 35 | end 36 | end 37 | end 38 | self.connector = redis_connector.new(redis_config) 39 | end 40 | 41 | function _M:connect() 42 | local red, err = self.connector:connect() 43 | if red == nil then 44 | ngx.log(ngx.ERR, "failed to connect to Redis: ", err) 45 | return false 46 | end 47 | self.red = red 48 | return true 49 | end 50 | 51 | function _M:close() 52 | local ok, err = self.connector:set_keepalive(self.red) 53 | if not ok then 54 | ngx.log(ngx.ERR, "failed to set keepalive: ", err) 55 | return false 56 | end 57 | return true 58 | end 59 | 60 | function _M:set(key, value, expire_time) 61 | local connected = self:connect() 62 | if not connected then 63 | return 64 | end 65 | local ok, err = self.red:set(key, value) 66 | if not ok then 67 | ngx.log(ngx.ERR, "failed to set cache: ", err) 68 | return 69 | end 70 | self.red:expire(key, expire_time) 71 | self:close() 72 | end 73 | 74 | function _M:get(key) 75 | local connected = self:connect() 76 | if not connected then 77 | return nil 78 | end 79 | local cached_value, err = self.red:get(key) 80 | if err then 81 | ngx.log(ngx.ERR, "failed to get cache: ", err) 82 | return nil, err 83 | end 84 | self:close() 85 | return cached_value 86 | end 87 | 88 | return _M 89 | -------------------------------------------------------------------------------- /proxy-cache/validators.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | function _M.check_response_code(tab, val) 4 | for index, value in ipairs(tab) do 5 | if tonumber(value) == val then 6 | return true 7 | end 8 | end 9 | return false 10 | end 11 | 12 | function _M.check_request_method() 13 | local request_method = ngx.req.get_method() 14 | return request_method == 'GET' or request_method == 'HEAD' 15 | end 16 | 17 | return _M 18 | -------------------------------------------------------------------------------- /spec/01-access-spec.lua: -------------------------------------------------------------------------------- 1 | local helpers = require "spec.helpers" 2 | local redis = require "resty.redis" 3 | local pretty = require "pl.pretty" 4 | 5 | for _, strategy in helpers.each_strategy() do 6 | describe("Proxy Cache: (access) [#" .. strategy .. "]", function() 7 | local proxy_client 8 | local red = redis:new() 9 | 10 | setup(function() 11 | local bp = helpers.get_db_utils(strategy) 12 | local route1 = bp.routes:insert({ 13 | hosts = { "test1.com" }, 14 | }) 15 | local route2 = bp.routes:insert({ 16 | hosts = { "test2.com" }, 17 | }) 18 | local route3 = bp.routes:insert({ 19 | hosts = { "responsecode.com" }, 20 | }) 21 | local route4 = bp.routes:insert({ 22 | hosts = { "test-cache-control.com" }, 23 | }) 24 | bp.plugins:insert { 25 | name = "proxy-cache", 26 | route_id = route1.id, 27 | config = { 28 | cache_control = false, 29 | redis = { 30 | host = "localhost" 31 | } 32 | }, 33 | } 34 | bp.plugins:insert { 35 | name = "proxy-cache", 36 | route_id = route2.id, 37 | config = { 38 | cache_control = true, 39 | redis = { 40 | host = "localhost" 41 | } 42 | }, 43 | } 44 | bp.plugins:insert { 45 | name = "request-transformer", 46 | route_id = route2.id, 47 | config = { 48 | add = { 49 | headers = "Cache-Control:max-age=2" 50 | } 51 | }, 52 | } 53 | bp.plugins:insert { 54 | name = "proxy-cache", 55 | route_id = route3.id, 56 | config = { 57 | cache_control = false, 58 | redis = { 59 | host = "localhost" 60 | }, 61 | response_code = {"404"} 62 | }, 63 | } 64 | bp.plugins:insert { 65 | name = "proxy-cache", 66 | route_id = route4.id, 67 | config = { 68 | cache_control = true, 69 | redis = { 70 | host = "localhost" 71 | } 72 | }, 73 | } 74 | 75 | assert(helpers.start_kong({ 76 | database = strategy, 77 | nginx_conf = "spec/fixtures/custom_nginx.template", 78 | plugins = "bundled,proxy-cache", 79 | })) 80 | end) 81 | 82 | teardown(function() 83 | helpers.stop_kong(nil, true) 84 | end) 85 | 86 | before_each(function() 87 | proxy_client = helpers.proxy_client() 88 | local ok, err = red:connect('localhost', '6379') 89 | assert(ok, err) 90 | end) 91 | 92 | after_each(function() 93 | if proxy_client then proxy_client:close() end 94 | red:flushall() 95 | red:set_keepalive(1000, 100) 96 | end) 97 | 98 | describe("request methods", function() 99 | it("should caches when method GET", function() 100 | local response = proxy_client:get("/", { 101 | headers = { 102 | host = "test1.com" 103 | } 104 | }) 105 | local cached_value, err = red:get('get:/') 106 | assert(cached_value, err) 107 | assert.is.truthy(cached_value) 108 | end) 109 | 110 | it("should caches when method HEAD", function() 111 | local response = assert(proxy_client:send { 112 | path = '/', 113 | method = 'HEAD', 114 | headers = { 115 | host = "test1.com" 116 | } 117 | }) 118 | local cached_value, err = red:get('head:/') 119 | assert(cached_value, err) 120 | assert.is.truthy(cached_value) 121 | end) 122 | 123 | it("should not caches when method POST", function() 124 | local response = proxy_client:post("/", { 125 | headers = { 126 | host = "test1.com" 127 | } 128 | }) 129 | local cached_value, err = red:get('post:/') 130 | assert(cached_value, err) 131 | assert.is.truthy(cached_value) 132 | end) 133 | end) 134 | 135 | describe("response headers", function() 136 | after_each(function() 137 | red:flushall() 138 | end) 139 | 140 | it("should contains 'MISS' in 'X-Cache-Status' when first access", function() 141 | local response = proxy_client:get("/", { 142 | headers = { 143 | host = "test1.com" 144 | } 145 | }) 146 | local cache_status = response.headers["X-Cache-Status"] 147 | assert(cache_status == 'MISS', "'X-Cache-Status' must be 'MISS'") 148 | end) 149 | 150 | it("should contains 'HIT' in 'X-Cache-Status' when access '/' two times", function() 151 | proxy_client:get("/status/200", { 152 | headers = { 153 | host = "test1.com" 154 | } 155 | }) 156 | 157 | local proxy_client2 = helpers.proxy_client() 158 | 159 | local response = proxy_client2:get("/status/200", { 160 | headers = { 161 | host = "test1.com" 162 | } 163 | }) 164 | 165 | local cache_status = response.headers["X-Cache-Status"] 166 | assert(cache_status == 'HIT', "'X-Cache-Status' must be 'HIT'") 167 | end) 168 | 169 | it("should contains 'BYPASS' in 'X-Cache-Status' when invalid method", function() 170 | local response = proxy_client:post("/", { 171 | headers = { 172 | host = "test1.com" 173 | } 174 | }) 175 | local cache_status = response.headers["X-Cache-Status"] 176 | assert(cache_status == 'BYPASS', "'X-Cache-Status' must be 'BYPASS'") 177 | end) 178 | 179 | it("should contains 'BYPASS' in 'X-Cache-Status' when response status is 404", function() 180 | local response = proxy_client:get("/404", { 181 | headers = { 182 | host = "test1.com" 183 | } 184 | }) 185 | local cache_status = response.headers["X-Cache-Status"] 186 | assert(cache_status == 'BYPASS', "'X-Cache-Status' must be 'BYPASS'") 187 | end) 188 | 189 | describe("when request has Cache-Control", function() 190 | it("should contains 'MISS' in 'X-Cache-Status' when 'Cache-Control' not found", function() 191 | local response = proxy_client:get("/", { 192 | headers = { 193 | host = "test-cache-control.com", 194 | } 195 | }) 196 | local cache_status = assert.response(response).has.header("X-Cache-Status") 197 | assert(cache_status == 'MISS', "'X-Cache-Status' must be 'MISS'") 198 | end) 199 | 200 | it("should contains 'MISS' in 'X-Cache-Status' when cache key expires", function() 201 | proxy_client:get("/status/200", { 202 | headers = { 203 | host = "test2.com", 204 | }, 205 | }) 206 | local proxy_client2 = helpers.proxy_client() 207 | ngx.sleep(3) 208 | local response = proxy_client2:get("/status/200", { 209 | headers = { 210 | host = "test2.com", 211 | } 212 | }) 213 | local cache_status = response.headers["X-Cache-Status"] 214 | assert(cache_status == 'MISS', "'X-Cache-Status' must be 'MISS'") 215 | end) 216 | end) 217 | describe("Response Code:", function() 218 | after_each(function() 219 | red:flushall() 220 | end) 221 | 222 | it("should cache default response codes(200)", function() 223 | local response1 = proxy_client:get("/", { 224 | headers = { 225 | host = "test1.com", 226 | ['Cache-Control'] = "max-age=400" 227 | } 228 | }) 229 | local cache_status = response1.headers["X-Cache-Status"] 230 | assert(cache_status == 'MISS', "'X-Cache-Status' must be 'MISS'") 231 | assert(200 == response1["status"]) 232 | local proxy_client2 = helpers.proxy_client() 233 | local response2 = proxy_client2:get("/", { 234 | headers = { 235 | host = "test1.com" 236 | } 237 | }) 238 | 239 | local cache_status2 = response2.headers["X-Cache-Status"] 240 | assert(cache_status2 == 'HIT', "'X-Cache-Status' must be 'HIT'") 241 | assert(200 == response2["status"]) 242 | end) 243 | 244 | it("should not cache default response code(404)", function() 245 | local response1 = proxy_client:get("/status/404", { 246 | headers = { 247 | host = "test1.com", 248 | ['Cache-Control'] = "max-age=400" 249 | } 250 | }) 251 | 252 | local cache_status = assert.response(response1).has.header("X-Cache-Status") 253 | assert(cache_status == 'BYPASS', "'X-Cache-Status' must be 'BYPASS'") 254 | assert(404 == response1["status"], "expected 404 from status") 255 | local proxy_client2 = helpers.proxy_client() 256 | local response2 = proxy_client2:get("/status/404", { 257 | headers = { 258 | host = "test1.com" 259 | } 260 | }) 261 | 262 | local cache_status2 = assert.response(response2).has.header("X-Cache-Status") 263 | assert(cache_status2 == 'BYPASS', "'X-Cache-Status' must be 'BYPASS'") 264 | assert(404 == response2["status"], "expected 404 from status") 265 | end) 266 | 267 | it("should cache a configured response code (404)", function() 268 | local response1 = proxy_client:get("/status/404", { 269 | headers = { 270 | host = "responsecode.com", 271 | } 272 | }) 273 | 274 | local cache_status = assert.response(response1).has.header("X-Cache-Status") 275 | assert(404 == response1["status"], "expected 404 from status") 276 | assert(cache_status == 'MISS', "'X-Cache-Status' must be 'MISS'") 277 | local proxy_client2 = helpers.proxy_client() 278 | local response2 = proxy_client2:get("/status/404", { 279 | headers = { 280 | host = "responsecode.com" 281 | } 282 | }) 283 | 284 | local cache_status2 = assert.response(response2).has.header("X-Cache-Status") 285 | assert(404 == response2["status"], "expected 404 from status") 286 | assert(cache_status2 == 'HIT', "'X-Cache-Status' must be 'HIT'") 287 | end) 288 | end) 289 | end) 290 | end) 291 | end 292 | -------------------------------------------------------------------------------- /spec/02-cache-spec.lua: -------------------------------------------------------------------------------- 1 | local Cache = require("kong.plugins.proxy-cache.cache") 2 | 3 | local function make_request(method, headers) 4 | local request = { 5 | get_method = function() 6 | return method 7 | end, 8 | get_headers = function() 9 | return headers or {} 10 | end 11 | } 12 | return request 13 | end 14 | 15 | local function make_ngx(variables) 16 | local default_variables = { 17 | request_uri = "request_uri", 18 | host = "test.com" 19 | } 20 | if variables then 21 | for k,v in pairs(variables) do 22 | default_variables[k] = v 23 | end 24 | end 25 | return default_variables 26 | end 27 | 28 | describe("Proxy Cache: (cache) ", function() 29 | describe("generate_cache_key", function() 30 | it("should return cache key with 'request_uri'", function() 31 | -- arrange 32 | local request = make_request('GET') 33 | local nginx_variables = make_ngx() 34 | local cache = Cache:new() 35 | cache:set_config() 36 | -- act 37 | local cache_key = cache:generate_cache_key(request, nginx_variables) 38 | -- assert 39 | assert(string.match(cache_key, "request_uri"), "'request_uri' not found in "..cache_key) 40 | end) 41 | 42 | it("should return cache key with 'request_method'", function() 43 | -- arrange 44 | local request = make_request('GET') 45 | local nginx_variables = make_ngx() 46 | local cache = Cache:new() 47 | cache:set_config() 48 | -- act 49 | local cache_key = cache:generate_cache_key(request, nginx_variables) 50 | -- assert 51 | assert(string.match(cache_key, "get"), "'request_method' not found in "..cache_key) 52 | end) 53 | 54 | it("should return cache key with 'request_method'", function() 55 | -- arrange 56 | local request = make_request('GET') 57 | local nginx_variables = make_ngx() 58 | local cache = Cache:new() 59 | cache:set_config() 60 | -- act 61 | local cache_key = cache:generate_cache_key(request, nginx_variables) 62 | -- assert 63 | assert(string.match(cache_key, "test.com"), "'host' not found in "..cache_key) 64 | end) 65 | 66 | it("should return cache key with header 'Authorization'", function() 67 | -- arrange 68 | local request = make_request('GET', { 69 | Authorization = 'basic' 70 | }) 71 | local nginx_variables = make_ngx() 72 | local config = { 73 | vary_headers = {"Authorization"} 74 | } 75 | local cache = Cache:new() 76 | cache:set_config(config) 77 | -- act 78 | local cache_key = cache:generate_cache_key(request, nginx_variables) 79 | -- assert 80 | assert(string.match(cache_key, "authorization=basic"), "header 'Authorization' not found in "..cache_key) 81 | end) 82 | 83 | it("should return cache key with nginx variable 'auth_client_id'", function() 84 | -- arrange 85 | local request = make_request('GET') 86 | local nginx_variables = make_ngx({ 87 | auth_client_id = 'abcd1234', 88 | }) 89 | local config = { 90 | vary_nginx_variables = {"auth_client_id"} 91 | } 92 | local cache = Cache:new() 93 | cache:set_config(config) 94 | -- act 95 | local cache_key = cache:generate_cache_key(request, nginx_variables) 96 | -- assert 97 | assert(string.match(cache_key, "auth_client_id=abcd1234"), "nginx variable 'auth_client_id' not found in "..cache_key) 98 | end) 99 | 100 | it("should return cache key with nginx variable 'auth_client_id' and 'auth_email'", function() 101 | -- arrange 102 | local request = make_request('GET') 103 | local nginx_variables = make_ngx({ 104 | auth_client_id = 'abcd1234', 105 | auth_email = 'xxx@mail.com' 106 | }) 107 | local config = { 108 | vary_nginx_variables = {"auth_client_id", 'auth_email'} 109 | } 110 | local cache = Cache:new() 111 | cache:set_config(config) 112 | -- act 113 | local cache_key = cache:generate_cache_key(request, nginx_variables) 114 | -- assert 115 | assert(string.match(cache_key, "auth_client_id=abcd1234"), "nginx variable 'auth_client_id' not found in "..cache_key) 116 | end) 117 | end) 118 | end) 119 | -------------------------------------------------------------------------------- /spec/03-validators-spec.lua: -------------------------------------------------------------------------------- 1 | local Validators = require("kong.plugins.proxy-cache.validators") 2 | local schema = require("kong.plugins.proxy-cache.schema") 3 | 4 | describe("Validators:", function() 5 | describe("Response Code:", function() 6 | setup(function() 7 | default_response_code = schema["fields"]["response_code"]["default"] 8 | end) 9 | 10 | it("should validate a default reponse_code", function() 11 | assert(Validators.check_response_code(default_response_code, 200)) 12 | assert(Validators.check_response_code(default_response_code, 301)) 13 | assert(Validators.check_response_code(default_response_code, 302)) 14 | end) 15 | 16 | it("should not validate a reponse_code that isn't in schema default", function() 17 | assert(false == Validators.check_response_code(default_response_code, 500)) 18 | end) 19 | end) 20 | end) 21 | --------------------------------------------------------------------------------