Rename controller to nginx
This commit is contained in:
parent
41c34bd9e8
commit
b7dee6f95c
47 changed files with 24 additions and 24 deletions
30
controllers/nginx/Dockerfile
Normal file
30
controllers/nginx/Dockerfile
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2015 The Kubernetes Authors. All rights reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
FROM gcr.io/google_containers/nginx-slim:0.4
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
diffutils \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY nginx-ingress-controller /
|
||||
COPY nginx.tmpl /
|
||||
COPY default.conf /etc/nginx/nginx.conf
|
||||
|
||||
COPY lua /etc/nginx/lua/
|
||||
|
||||
WORKDIR /
|
||||
|
||||
CMD ["/nginx-ingress-controller"]
|
||||
17
controllers/nginx/Makefile
Normal file
17
controllers/nginx/Makefile
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
all: push
|
||||
|
||||
# 0.0 shouldn't clobber any release builds
|
||||
TAG = 0.4
|
||||
PREFIX = gcr.io/google_containers/nginx-ingress-controller
|
||||
|
||||
controller: controller.go clean
|
||||
CGO_ENABLED=0 GOOS=linux godep go build -a -installsuffix cgo -ldflags '-w' -o nginx-ingress-controller
|
||||
|
||||
container: controller
|
||||
docker build -t $(PREFIX):$(TAG) .
|
||||
|
||||
push: container
|
||||
gcloud docker push $(PREFIX):$(TAG)
|
||||
|
||||
clean:
|
||||
rm -f nginx-ingress-controller
|
||||
444
controllers/nginx/README.md
Normal file
444
controllers/nginx/README.md
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
# Nginx Ingress Controller
|
||||
|
||||
This is a nginx Ingress controller that uses [ConfigMap](https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/configmap.md) to store the nginx configuration. See [Ingress controller documentation](../README.md) for details on how it works.
|
||||
|
||||
|
||||
## What it provides?
|
||||
|
||||
- Ingress controller
|
||||
- nginx 1.9.x with
|
||||
- SSL support
|
||||
- custom ssl_dhparam (optional). Just mount a secret with a file named `dhparam.pem`.
|
||||
- support for TCP services (flag `--tcp-services-configmap`)
|
||||
- custom nginx configuration using [ConfigMap](https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/configmap.md)
|
||||
- custom error pages. Using the flag `--custom-error-service` is possible to use a custom compatible [404-server](https://github.com/kubernetes/contrib/tree/master/404-server) image [nginx-error-server](https://github.com/aledbf/contrib/tree/nginx-debug-server/Ingress/images/nginx-error-server) that provides an additional `/errors` route that returns custom content for a particular error code. **This is completely optional**
|
||||
|
||||
|
||||
## Requirements
|
||||
- default backend [404-server](https://github.com/kubernetes/contrib/tree/master/404-server) (or a custom compatible image)
|
||||
|
||||
## TLS
|
||||
|
||||
You can secure an Ingress by specifying a secret that contains a TLS private key and certificate. Currently the Ingress only supports a single TLS port, 443, and assumes TLS termination. This controller supports SNI. The TLS secret must contain keys named tls.crt and tls.key that contain the certificate and private key to use for TLS, eg:
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
data:
|
||||
tls.crt: base64 encoded cert
|
||||
tls.key: base64 encoded key
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: testsecret
|
||||
namespace: default
|
||||
type: Opaque
|
||||
```
|
||||
|
||||
Referencing this secret in an Ingress will tell the Ingress controller to secure the channel from the client to the loadbalancer using TLS:
|
||||
|
||||
```
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: no-rules-map
|
||||
spec:
|
||||
tls:
|
||||
secretName: testsecret
|
||||
backend:
|
||||
serviceName: s1
|
||||
servicePort: 80
|
||||
```
|
||||
Please follow [test.sh](https://github.com/bprashanth/Ingress/blob/master/examples/sni/nginx/test.sh) as a guide on how to generate secrets containing SSL certificates. The name of the secret can be different than the name of the certificate.
|
||||
|
||||
|
||||
#### Optimizing TLS Time To First Byte (TTTFB)
|
||||
|
||||
NGINX provides the configuration option [ssl_buffer_size](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size) to allow the optimization of the TLS record size. This improves the [Time To First Byte](https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/) (TTTFB). The default value in the Ingress controller is `4k` (nginx default is `16k`);
|
||||
|
||||
|
||||
## Examples:
|
||||
|
||||
First we need to deploy some application to publish. To keep this simple we will use the [echoheaders app](https://github.com/kubernetes/contrib/blob/master/ingress/echoheaders/echo-app.yaml) that just returns information about the http request as output
|
||||
```
|
||||
kubectl run echoheaders --image=gcr.io/google_containers/echoserver:1.1 --replicas=1 --port=8080
|
||||
```
|
||||
|
||||
Now we expose the same application in two different services (so we can create different Ingress rules)
|
||||
```
|
||||
kubectl expose rc echoheaders --port=80 --target-port=8080 --name=echoheaders-x
|
||||
kubectl expose rc echoheaders --port=80 --target-port=8080 --name=echoheaders-y
|
||||
```
|
||||
|
||||
Next we create a couple of Ingress rules
|
||||
```
|
||||
kubectl create -f examples/ingress.yaml
|
||||
```
|
||||
|
||||
we check that ingress rules are defined:
|
||||
```
|
||||
$ kubectl get ing
|
||||
NAME RULE BACKEND ADDRESS
|
||||
echomap -
|
||||
foo.bar.com
|
||||
/foo echoheaders-x:80
|
||||
bar.baz.com
|
||||
/bar echoheaders-y:80
|
||||
/foo echoheaders-x:80
|
||||
```
|
||||
|
||||
Before the deploy of nginx we need a default backend [404-server](https://github.com/kubernetes/contrib/tree/master/404-server) (or a compatible custom image)
|
||||
```
|
||||
kubectl create -f examples/default-backend.yaml
|
||||
kubectl expose rc default-http-backend --port=80 --target-port=8080 --name=default-http-backend
|
||||
```
|
||||
|
||||
# Default configuration
|
||||
|
||||
The last step is the deploy of nginx Ingress rc (from the examples directory)
|
||||
```
|
||||
kubectl create -f examples/rc-default.yaml
|
||||
```
|
||||
|
||||
To test if evertyhing is working correctly:
|
||||
|
||||
`curl -v http://<node IP address>:80/foo -H 'Host: foo.bar.com'`
|
||||
|
||||
You should see an output similar to
|
||||
```
|
||||
* Trying 172.17.4.99...
|
||||
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
|
||||
> GET /foo HTTP/1.1
|
||||
> Host: foo.bar.com
|
||||
> User-Agent: curl/7.43.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Server: nginx/1.9.8
|
||||
< Date: Tue, 15 Dec 2015 13:45:13 GMT
|
||||
< Content-Type: text/plain
|
||||
< Transfer-Encoding: chunked
|
||||
< Connection: keep-alive
|
||||
< Vary: Accept-Encoding
|
||||
<
|
||||
CLIENT VALUES:
|
||||
client_address=10.2.84.43
|
||||
command=GET
|
||||
real path=/foo
|
||||
query=nil
|
||||
request_version=1.1
|
||||
request_uri=http://foo.bar.com:8080/foo
|
||||
|
||||
SERVER VALUES:
|
||||
server_version=nginx: 1.9.7 - lua: 9019
|
||||
|
||||
HEADERS RECEIVED:
|
||||
accept=*/*
|
||||
connection=close
|
||||
host=foo.bar.com
|
||||
user-agent=curl/7.43.0
|
||||
x-forwarded-for=172.17.4.1
|
||||
x-forwarded-host=foo.bar.com
|
||||
x-forwarded-server=foo.bar.com
|
||||
x-real-ip=172.17.4.1
|
||||
BODY:
|
||||
* Connection #0 to host 172.17.4.99 left intact
|
||||
```
|
||||
|
||||
If we try to get a non exising route like `/foobar` we should see
|
||||
```
|
||||
$ curl -v 172.17.4.99/foobar -H 'Host: foo.bar.com'
|
||||
* Trying 172.17.4.99...
|
||||
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
|
||||
> GET /foobar HTTP/1.1
|
||||
> Host: foo.bar.com
|
||||
> User-Agent: curl/7.43.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 404 Not Found
|
||||
< Server: nginx/1.9.8
|
||||
< Date: Tue, 15 Dec 2015 13:48:18 GMT
|
||||
< Content-Type: text/html
|
||||
< Transfer-Encoding: chunked
|
||||
< Connection: keep-alive
|
||||
< Vary: Accept-Encoding
|
||||
<
|
||||
default backend - 404
|
||||
* Connection #0 to host 172.17.4.99 left intact
|
||||
```
|
||||
|
||||
(this test checked that the default backend is properly working)
|
||||
|
||||
*Replacing the default backend with a custom one we can change the default error pages provided by nginx*
|
||||
|
||||
# Exposing TCP services
|
||||
|
||||
First we need to remove the running
|
||||
```
|
||||
kubectl delete rc nginx-ingress-3rdpartycfg
|
||||
```
|
||||
|
||||
To configure which services and ports will be exposed
|
||||
```
|
||||
kubectl create -f examples/tcp-configmap-example.yaml
|
||||
```
|
||||
|
||||
The file `examples/tcp-configmap-example.yaml` uses a ConfigMap where the key is the external port to use and the value is
|
||||
`<namespace/service name>:<service port>`
|
||||
It is possible to use a number or the name of the port.
|
||||
|
||||
|
||||
```
|
||||
kubectl create -f examples/rc-tcp.yaml
|
||||
```
|
||||
|
||||
Now we can test the new service:
|
||||
```
|
||||
$ (sleep 1; echo "GET / HTTP/1.1"; echo "Host: 172.17.4.99:9000"; echo;echo;sleep 2) | telnet 172.17.4.99 9000
|
||||
|
||||
Trying 172.17.4.99...
|
||||
Connected to 172.17.4.99.
|
||||
Escape character is '^]'.
|
||||
HTTP/1.1 200 OK
|
||||
Server: nginx/1.9.7
|
||||
Date: Tue, 15 Dec 2015 14:46:28 GMT
|
||||
Content-Type: text/plain
|
||||
Transfer-Encoding: chunked
|
||||
Connection: keep-alive
|
||||
|
||||
f
|
||||
CLIENT VALUES:
|
||||
|
||||
1a
|
||||
client_address=10.2.84.45
|
||||
|
||||
c
|
||||
command=GET
|
||||
|
||||
c
|
||||
real path=/
|
||||
|
||||
a
|
||||
query=nil
|
||||
|
||||
14
|
||||
request_version=1.1
|
||||
|
||||
25
|
||||
request_uri=http://172.17.4.99:8080/
|
||||
|
||||
1
|
||||
|
||||
|
||||
f
|
||||
SERVER VALUES:
|
||||
|
||||
28
|
||||
server_version=nginx: 1.9.7 - lua: 9019
|
||||
|
||||
1
|
||||
|
||||
|
||||
12
|
||||
HEADERS RECEIVED:
|
||||
|
||||
16
|
||||
host=172.17.4.99:9000
|
||||
|
||||
6
|
||||
BODY:
|
||||
|
||||
14
|
||||
-no body in request-
|
||||
0
|
||||
```
|
||||
|
||||
## SSL
|
||||
|
||||
First create a secret containing the ssl certificate and key. This example creates the certificate and the secret (json):
|
||||
|
||||
`SECRET_NAME=secret-echoheaders-1 HOSTS=foo.bar.com ./examples/certs.sh`
|
||||
|
||||
Create the secret:
|
||||
```
|
||||
kubectl create -f secret-secret-echoheaders-1-foo.bar.com.json
|
||||
```
|
||||
|
||||
Check if the secret was created:
|
||||
```
|
||||
$ kubectl get secrets
|
||||
NAME TYPE DATA AGE
|
||||
secret-echoheaders-1 Opaque 2 9m
|
||||
```
|
||||
|
||||
|
||||
Like before we need to remove the running nginx rc
|
||||
```
|
||||
kubectl delete rc nginx-ingress-3rdpartycfg
|
||||
```
|
||||
|
||||
Next create a new rc that uses the secret
|
||||
```
|
||||
kubectl create -f examples/rc-ssl.yaml
|
||||
```
|
||||
|
||||
*Note:* this example uses a self signed certificate.
|
||||
|
||||
Example output:
|
||||
```
|
||||
$ curl -v https://172.17.4.99/foo -H 'Host: bar.baz.com' -k
|
||||
* Trying 172.17.4.99...
|
||||
* Connected to 172.17.4.99 (172.17.4.99) port 4444 (#0)
|
||||
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
* Server certificate: foo.bar.com
|
||||
> GET /foo HTTP/1.1
|
||||
> Host: bar.baz.com
|
||||
> User-Agent: curl/7.43.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Server: nginx/1.9.8
|
||||
< Date: Thu, 17 Dec 2015 14:57:03 GMT
|
||||
< Content-Type: text/plain
|
||||
< Transfer-Encoding: chunked
|
||||
< Connection: keep-alive
|
||||
< Vary: Accept-Encoding
|
||||
<
|
||||
CLIENT VALUES:
|
||||
client_address=10.2.84.34
|
||||
command=GET
|
||||
real path=/foo
|
||||
query=nil
|
||||
request_version=1.1
|
||||
request_uri=http://bar.baz.com:8080/foo
|
||||
|
||||
SERVER VALUES:
|
||||
server_version=nginx: 1.9.7 - lua: 9019
|
||||
|
||||
HEADERS RECEIVED:
|
||||
accept=*/*
|
||||
connection=close
|
||||
host=bar.baz.com
|
||||
user-agent=curl/7.43.0
|
||||
x-forwarded-for=172.17.4.1
|
||||
x-forwarded-host=bar.baz.com
|
||||
x-forwarded-server=bar.baz.com
|
||||
x-real-ip=172.17.4.1
|
||||
BODY:
|
||||
* Connection #0 to host 172.17.4.99 left intact
|
||||
-no body in request-
|
||||
```
|
||||
|
||||
|
||||
## Custom errors
|
||||
|
||||
The default backend provides a way to customize the default 404 page. This helps but sometimes is not enough.
|
||||
Using the flag `--custom-error-service` is possible to use an image that must be 404 compatible and provide the route /error
|
||||
[Here](https://github.com/aledbf/contrib/tree/nginx-debug-server/Ingress/images/nginx-error-server) there is an example of the the image
|
||||
|
||||
The route `/error` expects two arguments: code and format
|
||||
* code defines the wich error code is expected to be returned (502,503,etc.)
|
||||
* format the format that should be returned For instance /error?code=504&format=json or /error?code=502&format=html
|
||||
|
||||
Using a volume pointing to `/var/www/html` directory is possible to use a custom error
|
||||
|
||||
|
||||
## Debug
|
||||
|
||||
Using the flag `--v=XX` it is possible to increase the level of logging.
|
||||
In particular:
|
||||
- `--v=2` shows details using `diff` about the changes in the configuration in nginx
|
||||
|
||||
```
|
||||
I0316 12:24:37.581267 1 utils.go:148] NGINX configuration diff a//etc/nginx/nginx.conf b//etc/nginx/nginx.conf
|
||||
I0316 12:24:37.581356 1 utils.go:149] --- /tmp/922554809 2016-03-16 12:24:37.000000000 +0000
|
||||
+++ /tmp/079811012 2016-03-16 12:24:37.000000000 +0000
|
||||
@@ -235,7 +235,6 @@
|
||||
|
||||
upstream default-echoheadersx {
|
||||
least_conn;
|
||||
- server 10.2.112.124:5000;
|
||||
server 10.2.208.50:5000;
|
||||
|
||||
}
|
||||
I0316 12:24:37.610073 1 command.go:69] change in configuration detected. Reloading...
|
||||
```
|
||||
|
||||
- `--v=3` shows details about the service, Ingress rule, endpoint changes and it dumps the nginx configuration in JSON format
|
||||
- `--v=5` configures NGINX in [debug mode](http://nginx.org/en/docs/debugging_log.html)
|
||||
|
||||
## Custom NGINX configuration
|
||||
|
||||
Using a ConfigMap it is possible to customize the defaults in nginx.
|
||||
The next command shows the defaults:
|
||||
```
|
||||
$ ./nginx-third-party-lb --dump-nginx—configuration
|
||||
Example of ConfigMap to customize NGINX configuration:
|
||||
data:
|
||||
body-size: 1m
|
||||
error-log-level: info
|
||||
gzip-types: application/atom+xml application/javascript application/json application/rss+xml
|
||||
application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json
|
||||
application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon
|
||||
text/css text/plain text/x-component
|
||||
hts-include-subdomains: "true"
|
||||
hts-max-age: "15724800"
|
||||
keep-alive: "75"
|
||||
max-worker-connections: "16384"
|
||||
proxy-connect-timeout: "30"
|
||||
proxy-read-timeout: "30"
|
||||
proxy-real-ip-cidr: 0.0.0.0/0
|
||||
proxy-send-timeout: "30"
|
||||
server-name-hash-bucket-size: "64"
|
||||
server-name-hash-max-size: "512"
|
||||
ssl-buffer-size: 4k
|
||||
ssl-ciphers: ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
|
||||
ssl-protocols: TLSv1 TLSv1.1 TLSv1.2
|
||||
ssl-session-cache: "true"
|
||||
ssl-session-cache-size: 10m
|
||||
ssl-session-tickets: "true"
|
||||
ssl-session-timeout: 10m
|
||||
use-gzip: "true"
|
||||
use-hts: "true"
|
||||
worker-processes: "8"
|
||||
metadata:
|
||||
name: custom-name
|
||||
namespace: a-valid-namespace
|
||||
```
|
||||
|
||||
For instance, if we want to change the timeouts we need to create a ConfigMap:
|
||||
```
|
||||
$ cat nginx-load-balancer-conf.yaml
|
||||
apiVersion: v1
|
||||
data:
|
||||
proxy-connect-timeout: "10"
|
||||
proxy-read-timeout: "120"
|
||||
proxy-send-imeout: "120"
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nginx-load-balancer-conf
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
$ kubectl create -f nginx-load-balancer-conf.yaml
|
||||
```
|
||||
|
||||
Please check the example `rc-custom-configuration.yaml`
|
||||
|
||||
If the Configmap it is updated, NGINX will be reloaded with the new configuration
|
||||
|
||||
### NGINX status page
|
||||
|
||||
The ngx_http_stub_status_module module provides access to basic status information. This is the default module active in the url `/nginx_status`.
|
||||
This controller provides an alternitive to this module using [nginx-module-vts](https://github.com/vozlt/nginx-module-vts) third party module.
|
||||
To use this module just provide a ConfigMap with the key `enable-vts-status=true`. The URL is exposed in the port 8080.
|
||||
Please check the example `example/rc-default.yaml`
|
||||
|
||||

|
||||
|
||||
To extract the information in JSON format the module provides a custom URL: `/nginx_status/format/json`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Problems encountered during [1.2.0-alpha7 deployment](https://github.com/kubernetes/kubernetes/blob/master/docs/getting-started-guides/docker.md):
|
||||
* make setup-files.sh file in hypercube does not provide 10.0.0.1 IP to make-ca-certs, resulting in CA certs that are issued to the external cluster IP address rather then 10.0.0.1 -> this results in nginx-third-party-lb appearing to get stuck at "Utils.go:177 - Waiting for default/default-http-backend" in the docker logs. Kubernetes will eventually kill the container before nginx-third-party-lb times out with a message indicating that the CA certificate issuer is invalid (wrong ip), to verify this add zeros to the end of initialDelaySeconds and timeoutSeconds and reload the RC, and docker will log this error before kubernetes kills the container.
|
||||
* To fix the above, setup-files.sh must be patched before the cluster is inited (refer to https://github.com/kubernetes/kubernetes/pull/21504)
|
||||
566
controllers/nginx/controller.go
Normal file
566
controllers/nginx/controller.go
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/controller/framework"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx"
|
||||
)
|
||||
|
||||
const (
|
||||
defUpstreamName = "upstream-default-backend"
|
||||
)
|
||||
|
||||
var (
|
||||
keyFunc = framework.DeletionHandlingMetaNamespaceKeyFunc
|
||||
)
|
||||
|
||||
// loadBalancerController watches the kubernetes api and adds/removes services
|
||||
// from the loadbalancer
|
||||
type loadBalancerController struct {
|
||||
client *client.Client
|
||||
ingController *framework.Controller
|
||||
endpController *framework.Controller
|
||||
svcController *framework.Controller
|
||||
ingLister StoreToIngressLister
|
||||
svcLister cache.StoreToServiceLister
|
||||
endpLister cache.StoreToEndpointsLister
|
||||
nginx *nginx.Manager
|
||||
lbInfo *lbInfo
|
||||
defaultSvc string
|
||||
nxgConfigMap string
|
||||
tcpConfigMap string
|
||||
|
||||
syncQueue *taskQueue
|
||||
|
||||
// stopLock is used to enforce only a single call to Stop is active.
|
||||
// Needed because we allow stopping through an http endpoint and
|
||||
// allowing concurrent stoppers leads to stack traces.
|
||||
stopLock sync.Mutex
|
||||
shutdown bool
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// newLoadBalancerController creates a controller for nginx loadbalancer
|
||||
func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration, defaultSvc,
|
||||
namespace, nxgConfigMapName, tcpConfigMapName string, lbRuntimeInfo *lbInfo) (*loadBalancerController, error) {
|
||||
lbc := loadBalancerController{
|
||||
client: kubeClient,
|
||||
stopCh: make(chan struct{}),
|
||||
lbInfo: lbRuntimeInfo,
|
||||
nginx: nginx.NewManager(kubeClient),
|
||||
nxgConfigMap: nxgConfigMapName,
|
||||
tcpConfigMap: tcpConfigMapName,
|
||||
defaultSvc: defaultSvc,
|
||||
}
|
||||
|
||||
lbc.syncQueue = NewTaskQueue(lbc.sync)
|
||||
|
||||
eventHandler := framework.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
lbc.syncQueue.enqueue(obj)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
lbc.syncQueue.enqueue(obj)
|
||||
},
|
||||
UpdateFunc: func(old, cur interface{}) {
|
||||
if !reflect.DeepEqual(old, cur) {
|
||||
lbc.syncQueue.enqueue(cur)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
lbc.ingLister.Store, lbc.ingController = framework.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: ingressListFunc(lbc.client, namespace),
|
||||
WatchFunc: ingressWatchFunc(lbc.client, namespace),
|
||||
},
|
||||
&extensions.Ingress{}, resyncPeriod, eventHandler)
|
||||
|
||||
lbc.endpLister.Store, lbc.endpController = framework.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: endpointsListFunc(lbc.client, namespace),
|
||||
WatchFunc: endpointsWatchFunc(lbc.client, namespace),
|
||||
},
|
||||
&api.Endpoints{}, resyncPeriod, eventHandler)
|
||||
|
||||
lbc.svcLister.Store, lbc.svcController = framework.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: serviceListFunc(lbc.client, namespace),
|
||||
WatchFunc: serviceWatchFunc(lbc.client, namespace),
|
||||
},
|
||||
&api.Service{}, resyncPeriod, framework.ResourceEventHandlerFuncs{})
|
||||
|
||||
return &lbc, nil
|
||||
}
|
||||
|
||||
func ingressListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) {
|
||||
return func(opts api.ListOptions) (runtime.Object, error) {
|
||||
return c.Extensions().Ingress(ns).List(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func ingressWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) {
|
||||
return func(options api.ListOptions) (watch.Interface, error) {
|
||||
return c.Extensions().Ingress(ns).Watch(options)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) {
|
||||
return func(opts api.ListOptions) (runtime.Object, error) {
|
||||
return c.Services(ns).List(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) {
|
||||
return func(options api.ListOptions) (watch.Interface, error) {
|
||||
return c.Services(ns).Watch(options)
|
||||
}
|
||||
}
|
||||
|
||||
func endpointsListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) {
|
||||
return func(opts api.ListOptions) (runtime.Object, error) {
|
||||
return c.Endpoints(ns).List(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func endpointsWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) {
|
||||
return func(options api.ListOptions) (watch.Interface, error) {
|
||||
return c.Endpoints(ns).Watch(options)
|
||||
}
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) controllersInSync() bool {
|
||||
return lbc.ingController.HasSynced() && lbc.svcController.HasSynced() && lbc.endpController.HasSynced()
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) getConfigMap(ns, name string) (*api.ConfigMap, error) {
|
||||
return lbc.client.ConfigMaps(ns).Get(name)
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) getTCPConfigMap(ns, name string) (*api.ConfigMap, error) {
|
||||
return lbc.client.ConfigMaps(ns).Get(name)
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) sync(key string) {
|
||||
if !lbc.controllersInSync() {
|
||||
lbc.syncQueue.requeue(key, fmt.Errorf("deferring sync till endpoints controller has synced"))
|
||||
return
|
||||
}
|
||||
|
||||
ings := lbc.ingLister.Store.List()
|
||||
upstreams, servers := lbc.getUpstreamServers(ings)
|
||||
|
||||
var cfg *api.ConfigMap
|
||||
|
||||
ns, name, _ := parseNsName(lbc.nxgConfigMap)
|
||||
cfg, err := lbc.getConfigMap(ns, name)
|
||||
if err != nil {
|
||||
cfg = &api.ConfigMap{}
|
||||
}
|
||||
|
||||
ngxConfig := lbc.nginx.ReadConfig(cfg)
|
||||
tcpServices := lbc.getTCPServices()
|
||||
lbc.nginx.CheckAndReload(ngxConfig, nginx.IngressConfig{
|
||||
Upstreams: upstreams,
|
||||
Servers: servers,
|
||||
TCPUpstreams: tcpServices,
|
||||
})
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) getTCPServices() []*nginx.Location {
|
||||
if lbc.tcpConfigMap == "" {
|
||||
// no configmap for TCP services
|
||||
return []*nginx.Location{}
|
||||
}
|
||||
|
||||
ns, name, err := parseNsName(lbc.tcpConfigMap)
|
||||
if err != nil {
|
||||
glog.Warningf("%v", err)
|
||||
return []*nginx.Location{}
|
||||
}
|
||||
tcpMap, err := lbc.getTCPConfigMap(ns, name)
|
||||
if err != nil {
|
||||
glog.V(3).Infof("no configured tcp services found: %v", err)
|
||||
return []*nginx.Location{}
|
||||
}
|
||||
|
||||
var tcpSvcs []*nginx.Location
|
||||
// k -> port to expose in nginx
|
||||
// v -> <namespace>/<service name>:<port from service to be used>
|
||||
for k, v := range tcpMap.Data {
|
||||
port, err := strconv.Atoi(k)
|
||||
if err != nil {
|
||||
glog.Warningf("%v is not valid as a TCP port", k)
|
||||
continue
|
||||
}
|
||||
|
||||
svcPort := strings.Split(v, ":")
|
||||
if len(svcPort) != 2 {
|
||||
glog.Warningf("invalid format (namespace/name:port) '%v'", k)
|
||||
continue
|
||||
}
|
||||
|
||||
svcNs, svcName, err := parseNsName(svcPort[0])
|
||||
if err != nil {
|
||||
glog.Warningf("%v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
svcObj, svcExists, err := lbc.svcLister.Store.GetByKey(svcPort[0])
|
||||
if err != nil {
|
||||
glog.Warningf("error getting service %v: %v", svcPort[0], err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !svcExists {
|
||||
glog.Warningf("service %v was not found", svcPort[0])
|
||||
continue
|
||||
}
|
||||
|
||||
svc := svcObj.(*api.Service)
|
||||
|
||||
var endps []nginx.UpstreamServer
|
||||
targetPort, err := strconv.Atoi(svcPort[1])
|
||||
if err != nil {
|
||||
endps = lbc.getEndpoints(svc, intstr.FromString(svcPort[1]))
|
||||
} else {
|
||||
// we need to use the TargetPort (where the endpoints are running)
|
||||
for _, sp := range svc.Spec.Ports {
|
||||
if sp.Port == targetPort {
|
||||
endps = lbc.getEndpoints(svc, sp.TargetPort)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tcp upstreams cannot contain empty upstreams and there is no
|
||||
// default backend equivalent for TCP
|
||||
if len(endps) == 0 {
|
||||
glog.Warningf("service %v/%v does no have any active endpoints", svcNs, svcName)
|
||||
continue
|
||||
}
|
||||
|
||||
tcpSvcs = append(tcpSvcs, &nginx.Location{
|
||||
Path: k,
|
||||
Upstream: nginx.Upstream{
|
||||
Name: fmt.Sprintf("%v-%v-%v", svcNs, svcName, port),
|
||||
Backends: endps,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return tcpSvcs
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) getDefaultUpstream() *nginx.Upstream {
|
||||
upstream := &nginx.Upstream{
|
||||
Name: defUpstreamName,
|
||||
}
|
||||
svcKey := lbc.defaultSvc
|
||||
svcObj, svcExists, err := lbc.svcLister.Store.GetByKey(svcKey)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error searching the default backend %v: %v", lbc.defaultSvc, err)
|
||||
upstream.Backends = append(upstream.Backends, nginx.NewDefaultServer())
|
||||
return upstream
|
||||
}
|
||||
|
||||
if !svcExists {
|
||||
glog.Warningf("service %v does no exists", svcKey)
|
||||
upstream.Backends = append(upstream.Backends, nginx.NewDefaultServer())
|
||||
return upstream
|
||||
}
|
||||
|
||||
svc := svcObj.(*api.Service)
|
||||
|
||||
endps := lbc.getEndpoints(svc, svc.Spec.Ports[0].TargetPort)
|
||||
if len(endps) == 0 {
|
||||
glog.Warningf("service %v does no have any active endpoints", svcKey)
|
||||
upstream.Backends = append(upstream.Backends, nginx.NewDefaultServer())
|
||||
} else {
|
||||
upstream.Backends = append(upstream.Backends, endps...)
|
||||
}
|
||||
|
||||
return upstream
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) getUpstreamServers(data []interface{}) ([]*nginx.Upstream, []*nginx.Server) {
|
||||
upstreams := lbc.createUpstreams(data)
|
||||
servers := lbc.createServers(data)
|
||||
|
||||
upstreams[defUpstreamName] = lbc.getDefaultUpstream()
|
||||
|
||||
for _, ingIf := range data {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if rule.IngressRuleValue.HTTP == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
server := servers[rule.Host]
|
||||
locations := []*nginx.Location{}
|
||||
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
upsName := fmt.Sprintf("%v-%v-%v", ing.GetNamespace(), path.Backend.ServiceName, path.Backend.ServicePort.IntValue())
|
||||
ups := upstreams[upsName]
|
||||
|
||||
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)
|
||||
svcObj, svcExists, err := lbc.svcLister.Store.GetByKey(svcKey)
|
||||
if err != nil {
|
||||
glog.Infof("error getting service %v from the cache: %v", svcKey, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !svcExists {
|
||||
glog.Warningf("service %v does no exists", svcKey)
|
||||
continue
|
||||
}
|
||||
|
||||
svc := svcObj.(*api.Service)
|
||||
|
||||
for _, servicePort := range svc.Spec.Ports {
|
||||
if servicePort.Port == path.Backend.ServicePort.IntValue() {
|
||||
endps := lbc.getEndpoints(svc, servicePort.TargetPort)
|
||||
if len(endps) == 0 {
|
||||
glog.Warningf("service %v does no have any active endpoints", svcKey)
|
||||
}
|
||||
|
||||
ups.Backends = append(ups.Backends, endps...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, ups := range upstreams {
|
||||
if upsName == ups.Name {
|
||||
loc := &nginx.Location{Path: path.Path}
|
||||
loc.Upstream = *ups
|
||||
locations = append(locations, loc)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, loc := range locations {
|
||||
server.Locations = append(server.Locations, loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: find a way to make this more readable
|
||||
// The structs must be ordered to always generate the same file
|
||||
// if the content does not change.
|
||||
aUpstreams := make([]*nginx.Upstream, 0, len(upstreams))
|
||||
for _, value := range upstreams {
|
||||
if len(value.Backends) == 0 {
|
||||
value.Backends = append(value.Backends, nginx.NewDefaultServer())
|
||||
}
|
||||
sort.Sort(nginx.UpstreamServerByAddrPort(value.Backends))
|
||||
aUpstreams = append(aUpstreams, value)
|
||||
}
|
||||
sort.Sort(nginx.UpstreamByNameServers(aUpstreams))
|
||||
|
||||
aServers := make([]*nginx.Server, 0, len(servers))
|
||||
for _, value := range servers {
|
||||
sort.Sort(nginx.LocationByPath(value.Locations))
|
||||
aServers = append(aServers, value)
|
||||
}
|
||||
sort.Sort(nginx.ServerByName(aServers))
|
||||
|
||||
return aUpstreams, aServers
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) createUpstreams(data []interface{}) map[string]*nginx.Upstream {
|
||||
upstreams := make(map[string]*nginx.Upstream)
|
||||
upstreams[defUpstreamName] = nginx.NewUpstream(defUpstreamName)
|
||||
|
||||
for _, ingIf := range data {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if rule.IngressRuleValue.HTTP == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
name := fmt.Sprintf("%v-%v-%v", ing.GetNamespace(), path.Backend.ServiceName, path.Backend.ServicePort.IntValue())
|
||||
if _, ok := upstreams[name]; !ok {
|
||||
upstreams[name] = nginx.NewUpstream(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return upstreams
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) createServers(data []interface{}) map[string]*nginx.Server {
|
||||
servers := make(map[string]*nginx.Server)
|
||||
|
||||
pems := lbc.getPemsFromIngress(data)
|
||||
|
||||
for _, ingIf := range data {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if _, ok := servers[rule.Host]; !ok {
|
||||
servers[rule.Host] = &nginx.Server{Name: rule.Host, Locations: []*nginx.Location{}}
|
||||
}
|
||||
|
||||
if pemFile, ok := pems[rule.Host]; ok {
|
||||
server := servers[rule.Host]
|
||||
server.SSL = true
|
||||
server.SSLCertificate = pemFile
|
||||
server.SSLCertificateKey = pemFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[string]string {
|
||||
pems := make(map[string]string)
|
||||
|
||||
for _, ingIf := range data {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
|
||||
for _, tls := range ing.Spec.TLS {
|
||||
secretName := tls.SecretName
|
||||
secret, err := lbc.client.Secrets(ing.Namespace).Get(secretName)
|
||||
if err != nil {
|
||||
glog.Warningf("Error retriveing secret %v for ing %v: %v", secretName, ing.Name, err)
|
||||
continue
|
||||
}
|
||||
cert, ok := secret.Data[api.TLSCertKey]
|
||||
if !ok {
|
||||
glog.Warningf("Secret %v has no private key", secretName)
|
||||
continue
|
||||
}
|
||||
key, ok := secret.Data[api.TLSPrivateKeyKey]
|
||||
if !ok {
|
||||
glog.Warningf("Secret %v has no cert", secretName)
|
||||
continue
|
||||
}
|
||||
|
||||
pemFileName := lbc.nginx.AddOrUpdateCertAndKey(secretName, string(cert), string(key))
|
||||
cn, err := lbc.nginx.CheckSSLCertificate(pemFileName)
|
||||
if err != nil {
|
||||
glog.Warningf("No valid SSL certificate found in secret %v", secretName)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, host := range tls.Hosts {
|
||||
if isHostValid(host, cn) {
|
||||
pems[host] = pemFileName
|
||||
} else {
|
||||
glog.Warningf("SSL Certificate stored in secret %v is not valid for the host %v defined in the Ingress rule %v", secretName, host, ing.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pems
|
||||
}
|
||||
|
||||
// getEndpoints returns a list of <endpoint ip>:<port> for a given service/target port combination.
|
||||
func (lbc *loadBalancerController) getEndpoints(s *api.Service, servicePort intstr.IntOrString) []nginx.UpstreamServer {
|
||||
glog.V(3).Infof("getting endpoints for service %v/%v and port %v", s.Namespace, s.Name, servicePort.String())
|
||||
ep, err := lbc.endpLister.GetServiceEndpoints(s)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error obtaining service endpoints: %v", err)
|
||||
return []nginx.UpstreamServer{}
|
||||
}
|
||||
|
||||
upsServers := []nginx.UpstreamServer{}
|
||||
|
||||
for _, ss := range ep.Subsets {
|
||||
for _, epPort := range ss.Ports {
|
||||
var targetPort int
|
||||
switch servicePort.Type {
|
||||
case intstr.Int:
|
||||
if epPort.Port == servicePort.IntValue() {
|
||||
targetPort = epPort.Port
|
||||
}
|
||||
case intstr.String:
|
||||
if epPort.Name == servicePort.StrVal {
|
||||
targetPort = epPort.Port
|
||||
}
|
||||
}
|
||||
|
||||
if targetPort == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, epAddress := range ss.Addresses {
|
||||
ups := nginx.UpstreamServer{Address: epAddress.IP, Port: fmt.Sprintf("%v", targetPort)}
|
||||
upsServers = append(upsServers, ups)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(3).Infof("endpoints found: %v", upsServers)
|
||||
return upsServers
|
||||
}
|
||||
|
||||
// Stop stops the loadbalancer controller.
|
||||
func (lbc *loadBalancerController) Stop() {
|
||||
// Stop is invoked from the http endpoint.
|
||||
lbc.stopLock.Lock()
|
||||
defer lbc.stopLock.Unlock()
|
||||
|
||||
// Only try draining the workqueue if we haven't already.
|
||||
if !lbc.shutdown {
|
||||
close(lbc.stopCh)
|
||||
glog.Infof("shutting down controller queues")
|
||||
lbc.shutdown = true
|
||||
lbc.syncQueue.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts the loadbalancer controller.
|
||||
func (lbc *loadBalancerController) Run() {
|
||||
glog.Infof("starting NGINX loadbalancer controller")
|
||||
go lbc.nginx.Start()
|
||||
|
||||
go lbc.ingController.Run(lbc.stopCh)
|
||||
go lbc.endpController.Run(lbc.stopCh)
|
||||
go lbc.svcController.Run(lbc.stopCh)
|
||||
|
||||
go lbc.syncQueue.run(time.Second, lbc.stopCh)
|
||||
|
||||
<-lbc.stopCh
|
||||
glog.Infof("shutting down NGINX loadbalancer controller")
|
||||
}
|
||||
6
controllers/nginx/default.conf
Normal file
6
controllers/nginx/default.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# A very simple nginx configuration file that forces nginx to start.
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {}
|
||||
http {}
|
||||
daemon off;
|
||||
47
controllers/nginx/examples/as-daemonset.yaml
Normal file
47
controllers/nginx/examples/as-daemonset.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
apiVersion: extensions/v1beta1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: nginx-ingress-lb
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: nginx-ingress-lb
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/google_containers/nginx-ingress-controller:0.4
|
||||
name: nginx-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10249
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
# use downward API
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 4444
|
||||
# we expose 8080 to access nginx stats in url /nginx_status
|
||||
# this is optional
|
||||
- containerPort: 8080
|
||||
hostPort: 8081
|
||||
args:
|
||||
- /nginx-ingress-controller-lb
|
||||
- --default-backend-service=default/default-http-backend
|
||||
36
controllers/nginx/examples/default-backend.yaml
Normal file
36
controllers/nginx/examples/default-backend.yaml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: default-http-backend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
app: default-http-backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: default-http-backend
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 600
|
||||
containers:
|
||||
- name: default-http-backend
|
||||
# Any image is permissable as long as:
|
||||
# 1. It serves a 404 page at /
|
||||
# 2. It serves 200 on a /healthz endpoint
|
||||
image: gcr.io/google_containers/defaultbackend:1.0
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
resources:
|
||||
limits:
|
||||
cpu: 10m
|
||||
memory: 20Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 20Mi
|
||||
35
controllers/nginx/examples/dhparam.sh
Executable file
35
controllers/nginx/examples/dhparam.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
|
||||
# https://www.openssl.org/docs/manmaster/apps/dhparam.html
|
||||
# this command generates a key used to get "Perfect Forward Secrecy" in nginx
|
||||
# https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
|
||||
openssl dhparam -out dhparam.pem 4096
|
||||
|
||||
cat <<EOF > dhparam-example.yaml
|
||||
{
|
||||
"kind": "Secret",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "dhparam-example"
|
||||
},
|
||||
"data": {
|
||||
"dhparam.pem": "$(cat ./dhparam.pem | base64)"
|
||||
}
|
||||
}
|
||||
|
||||
EOF
|
||||
25
controllers/nginx/examples/ingress.yaml
Normal file
25
controllers/nginx/examples/ingress.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# An Ingress with 2 hosts and 3 endpoints
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: echomap
|
||||
spec:
|
||||
rules:
|
||||
- host: foo.bar.com
|
||||
http:
|
||||
paths:
|
||||
- path: /foo
|
||||
backend:
|
||||
serviceName: echoheaders-x
|
||||
servicePort: 80
|
||||
- host: bar.baz.com
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
serviceName: echoheaders-y
|
||||
servicePort: 80
|
||||
- path: /foo
|
||||
backend:
|
||||
serviceName: echoheaders-x
|
||||
servicePort: 80
|
||||
54
controllers/nginx/examples/rc-custom-configuration.yaml
Normal file
54
controllers/nginx/examples/rc-custom-configuration.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: nginx-ingress-controller
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
k8s-app: nginx-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
name: nginx-ingress-lb
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/google_containers/nginx-ingress-controller:0.4
|
||||
name: nginx-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10249
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
# use downward API
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 4444
|
||||
# we expose 8080 to access nginx stats in url /nginx_status
|
||||
# this is optional
|
||||
- containerPort: 8080
|
||||
hostPort: 8081
|
||||
args:
|
||||
- /nginx-ingress-controller
|
||||
- --default-backend-service=default/default-http-backend
|
||||
- --nginx-configmap=default/nginx-load-balancer-conf
|
||||
53
controllers/nginx/examples/rc-default.yaml
Normal file
53
controllers/nginx/examples/rc-default.yaml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: nginx-ingress-controller
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
k8s-app: nginx-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
name: nginx-ingress-lb
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/google_containers/nginx-ingress-controller:0.4
|
||||
name: nginx-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10249
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
# use downward API
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 4444
|
||||
# we expose 8080 to access nginx stats in url /nginx_status
|
||||
# this is optional
|
||||
- containerPort: 8080
|
||||
hostPort: 8081
|
||||
args:
|
||||
- /nginx-ingress-controller
|
||||
- --default-backend-service=default/default-http-backend
|
||||
60
controllers/nginx/examples/rc-full.yaml
Normal file
60
controllers/nginx/examples/rc-full.yaml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: nginx-ingress-controller
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
k8s-app: nginx-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
name: nginx-ingress-lb
|
||||
spec:
|
||||
volumes:
|
||||
- name: dhparam-example
|
||||
secret:
|
||||
secretName: dhparam-example
|
||||
containers:
|
||||
- image: gcr.io/google_containers/nginx-ingress-controller:0.4
|
||||
name: nginx-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10249
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
# use downward API
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 4444
|
||||
- containerPort: 8080
|
||||
hostPort: 9000
|
||||
volumeMounts:
|
||||
- mountPath: /etc/nginx-ssl/dhparam
|
||||
name: dhparam-example
|
||||
args:
|
||||
- /nginx-ingress-controller
|
||||
- --tcp-services-configmap=default/tcp-configmap-example
|
||||
- --default-backend-service=default/default-http-backend
|
||||
54
controllers/nginx/examples/rc-ssl.yaml
Normal file
54
controllers/nginx/examples/rc-ssl.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: nginx-ingress-controller
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
k8s-app: nginx-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
name: nginx-ingress-lb
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/google_containers/nginx-ingress-controller:0.4
|
||||
name: nginx-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10249
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
# use downward API
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 4444
|
||||
- containerPort: 8080
|
||||
hostPort: 9000
|
||||
# to configure ssl_dhparam http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
||||
# use the dhparam.sh file to generate and mount a secret that containing the key dhparam.pem or
|
||||
# create a configuration with the content of dhparam.pem in the field sslDHParam.
|
||||
args:
|
||||
- /nginx-ingress-controller
|
||||
- --default-backend-service=default/default-http-backend
|
||||
58
controllers/nginx/examples/rc-tcp.yaml
Normal file
58
controllers/nginx/examples/rc-tcp.yaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: nginx-ingress-controller
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
k8s-app: nginx-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: nginx-ingress-lb
|
||||
name: nginx-ingress-lb
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/google_containers/nginx-ingress-controller:0.4
|
||||
name: nginx-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10249
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
# use downward API
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 4444
|
||||
# we expose 8080 to access nginx stats in url /nginx-status
|
||||
# this is optional
|
||||
- containerPort: 8080
|
||||
hostPort: 8081
|
||||
# service echoheaders as TCP service default/echoheaders:9000
|
||||
# 9000 indicates the port used to expose the service
|
||||
- containerPort: 9000
|
||||
hostPort: 9000
|
||||
args:
|
||||
- /nginx-ingress-controller
|
||||
- --default-backend-service=default/default-http-backend
|
||||
- --tcp-services-configmap=default/tcp-configmap-example
|
||||
6
controllers/nginx/examples/tcp-configmap-example.yaml
Normal file
6
controllers/nginx/examples/tcp-configmap-example.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: tcp-configmap-example
|
||||
data:
|
||||
9000: "default/example-go:8080"
|
||||
67
controllers/nginx/lua/error_page.lua
Normal file
67
controllers/nginx/lua/error_page.lua
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
http = require "resty.http"
|
||||
def_backend = "upstream-default-backend"
|
||||
|
||||
local concat = table.concat
|
||||
local upstream = require "ngx.upstream"
|
||||
local get_servers = upstream.get_servers
|
||||
local get_upstreams = upstream.get_upstreams
|
||||
local random = math.random
|
||||
local us = get_upstreams()
|
||||
|
||||
function openURL(status)
|
||||
local httpc = http.new()
|
||||
|
||||
local random_backend = get_destination()
|
||||
local res, err = httpc:request_uri(random_backend, {
|
||||
path = "/",
|
||||
method = "GET",
|
||||
headers = {
|
||||
["X-Code"] = status or "404",
|
||||
["X-Format"] = ngx.var.httpAccept or "html",
|
||||
}
|
||||
})
|
||||
|
||||
if not res then
|
||||
ngx.log(ngx.ERR, err)
|
||||
ngx.exit(500)
|
||||
end
|
||||
|
||||
if ngx.var.http_cookie then
|
||||
ngx.header["Cookie"] = ngx.var.http_cookie
|
||||
end
|
||||
|
||||
ngx.status = tonumber(status)
|
||||
ngx.say(res.body)
|
||||
end
|
||||
|
||||
function get_destination()
|
||||
for _, u in ipairs(us) do
|
||||
if u == def_backend then
|
||||
local srvs, err = get_servers(u)
|
||||
local us_table = {}
|
||||
if not srvs then
|
||||
return "http://127.0.0.1:8181"
|
||||
else
|
||||
for _, srv in ipairs(srvs) do
|
||||
us_table[srv["name"]] = srv["weight"]
|
||||
end
|
||||
end
|
||||
local destination = random_weight(us_table)
|
||||
return "http://"..destination
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function random_weight(tbl)
|
||||
local total = 0
|
||||
for k, v in pairs(tbl) do
|
||||
total = total + v
|
||||
end
|
||||
local offset = random(0, total - 1)
|
||||
for k1, v1 in pairs(tbl) do
|
||||
if offset < v1 then
|
||||
return k1
|
||||
end
|
||||
offset = offset - v1
|
||||
end
|
||||
end
|
||||
78
controllers/nginx/lua/trie.lua
Normal file
78
controllers/nginx/lua/trie.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
-- Simple trie for URLs
|
||||
|
||||
local _M = {}
|
||||
|
||||
local mt = {
|
||||
__index = _M
|
||||
}
|
||||
|
||||
-- http://lua-users.org/wiki/SplitJoin
|
||||
local strfind, tinsert, strsub = string.find, table.insert, string.sub
|
||||
function _M.strsplit(delimiter, text)
|
||||
local list = {}
|
||||
local pos = 1
|
||||
while 1 do
|
||||
local first, last = strfind(text, delimiter, pos)
|
||||
if first then -- found?
|
||||
tinsert(list, strsub(text, pos, first-1))
|
||||
pos = last+1
|
||||
else
|
||||
tinsert(list, strsub(text, pos))
|
||||
break
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
local strsplit = _M.strsplit
|
||||
|
||||
function _M.new()
|
||||
local t = { }
|
||||
return setmetatable(t, mt)
|
||||
end
|
||||
|
||||
function _M.add(t, key, val)
|
||||
local parts = {}
|
||||
-- hack for just /
|
||||
if key == "/" then
|
||||
parts = { "" }
|
||||
else
|
||||
parts = strsplit("/", key)
|
||||
end
|
||||
|
||||
local l = t
|
||||
for i = 1, #parts do
|
||||
local p = parts[i]
|
||||
if not l[p] then
|
||||
l[p] = {}
|
||||
end
|
||||
l = l[p]
|
||||
end
|
||||
l.__value = val
|
||||
end
|
||||
|
||||
function _M.get(t, key)
|
||||
local parts = strsplit("/", key)
|
||||
|
||||
local l = t
|
||||
|
||||
-- this may be nil
|
||||
local val = t.__value
|
||||
for i = 1, #parts do
|
||||
local p = parts[i]
|
||||
if l[p] then
|
||||
l = l[p]
|
||||
local v = l.__value
|
||||
if v then
|
||||
val = v
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- may be nil
|
||||
return val
|
||||
end
|
||||
|
||||
return _M
|
||||
2
controllers/nginx/lua/vendor/lua-resty-http/.gitignore
vendored
Normal file
2
controllers/nginx/lua/vendor/lua-resty-http/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
t/servroot/
|
||||
t/error.log
|
||||
23
controllers/nginx/lua/vendor/lua-resty-http/LICENSE
vendored
Normal file
23
controllers/nginx/lua/vendor/lua-resty-http/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2013, James Hurst
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
20
controllers/nginx/lua/vendor/lua-resty-http/Makefile
vendored
Normal file
20
controllers/nginx/lua/vendor/lua-resty-http/Makefile
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
OPENRESTY_PREFIX=/usr/local/openresty
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
LUA_INCLUDE_DIR ?= $(PREFIX)/include
|
||||
LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION)
|
||||
INSTALL ?= install
|
||||
TEST_FILE ?= t
|
||||
|
||||
.PHONY: all test install
|
||||
|
||||
all: ;
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty
|
||||
$(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/
|
||||
|
||||
test: all
|
||||
PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH TEST_NGINX_NO_SHUFFLE=1 prove -I../test-nginx/lib -r $(TEST_FILE)
|
||||
util/lua-releng
|
||||
|
||||
423
controllers/nginx/lua/vendor/lua-resty-http/README.md
vendored
Normal file
423
controllers/nginx/lua/vendor/lua-resty-http/README.md
vendored
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
# lua-resty-http
|
||||
|
||||
Lua HTTP client cosocket driver for [OpenResty](http://openresty.org/) / [ngx_lua](https://github.com/chaoslawful/lua-nginx-module).
|
||||
|
||||
# Status
|
||||
|
||||
Ready for testing. Probably production ready in most cases, though not yet proven in the wild. Please check the issues list and let me know if you have any problems / questions.
|
||||
|
||||
# Features
|
||||
|
||||
* HTTP 1.0 and 1.1
|
||||
* Streaming interface to reading bodies using coroutines, for predictable memory usage in Lua land.
|
||||
* Alternative simple interface for singleshot requests without manual connection step.
|
||||
* Headers treated case insensitively.
|
||||
* Chunked transfer encoding.
|
||||
* Keepalive.
|
||||
* Pipelining.
|
||||
* Trailers.
|
||||
|
||||
|
||||
# API
|
||||
|
||||
* [new](#name)
|
||||
* [connect](#connect)
|
||||
* [set_timeout](#set_timeout)
|
||||
* [ssl_handshake](#ssl_handshake)
|
||||
* [set_keepalive](#set_keepalive)
|
||||
* [get_reused_times](#get_reused_times)
|
||||
* [close](#close)
|
||||
* [request](#request)
|
||||
* [request_uri](#request_uri)
|
||||
* [request_pipeline](#request_pipeline)
|
||||
* [Response](#response)
|
||||
* [body_reader](#resbody_reader)
|
||||
* [read_body](#resread_body)
|
||||
* [read_trailes](#resread_trailers)
|
||||
* [Proxy](#proxy)
|
||||
* [proxy_request](#proxy_request)
|
||||
* [proxy_response](#proxy_response)
|
||||
* [Utility](#utility)
|
||||
* [parse_uri](#parse_uri)
|
||||
* [get_client_body_reader](#get_client_body_reader)
|
||||
|
||||
|
||||
## Synopsis
|
||||
|
||||
```` lua
|
||||
lua_package_path "/path/to/lua-resty-http/lib/?.lua;;";
|
||||
|
||||
server {
|
||||
|
||||
|
||||
location /simpleinterface {
|
||||
resolver 8.8.8.8; # use Google's open DNS server for an example
|
||||
|
||||
content_by_lua '
|
||||
|
||||
-- For simple singleshot requests, use the URI interface.
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local res, err = httpc:request_uri("http://example.com/helloworld", {
|
||||
method = "POST",
|
||||
body = "a=1&b=2",
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
}
|
||||
})
|
||||
|
||||
if not res then
|
||||
ngx.say("failed to request: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
-- In this simple form, there is no manual connection step, so the body is read
|
||||
-- all in one go, including any trailers, and the connection closed or keptalive
|
||||
-- for you.
|
||||
|
||||
ngx.status = res.status
|
||||
|
||||
for k,v in pairs(res.headers) do
|
||||
--
|
||||
end
|
||||
|
||||
ngx.say(res.body)
|
||||
';
|
||||
}
|
||||
|
||||
|
||||
location /genericinterface {
|
||||
content_by_lua '
|
||||
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
-- The generic form gives us more control. We must connect manually.
|
||||
httpc:set_timeout(500)
|
||||
httpc:connect("127.0.0.1", 80)
|
||||
|
||||
-- And request using a path, rather than a full URI.
|
||||
local res, err = httpc:request{
|
||||
path = "/helloworld",
|
||||
headers = {
|
||||
["Host"] = "example.com",
|
||||
},
|
||||
}
|
||||
|
||||
if not res then
|
||||
ngx.say("failed to request: ", err)
|
||||
return
|
||||
end
|
||||
|
||||
-- Now we can use the body_reader iterator, to stream the body according to our desired chunk size.
|
||||
local reader = res.body_reader
|
||||
|
||||
repeat
|
||||
local chunk, err = reader(8192)
|
||||
if err then
|
||||
ngx.log(ngx.ERR, err)
|
||||
break
|
||||
end
|
||||
|
||||
if chunk then
|
||||
-- process
|
||||
end
|
||||
until not chunk
|
||||
|
||||
local ok, err = httpc:set_keepalive()
|
||||
if not ok then
|
||||
ngx.say("failed to set keepalive: ", err)
|
||||
return
|
||||
end
|
||||
';
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
# Connection
|
||||
|
||||
## new
|
||||
|
||||
`syntax: httpc = http.new()`
|
||||
|
||||
Creates the http object. In case of failures, returns `nil` and a string describing the error.
|
||||
|
||||
## connect
|
||||
|
||||
`syntax: ok, err = httpc:connect(host, port, options_table?)`
|
||||
|
||||
`syntax: ok, err = httpc:connect("unix:/path/to/unix.sock", options_table?)`
|
||||
|
||||
Attempts to connect to the web server.
|
||||
|
||||
Before actually resolving the host name and connecting to the remote backend, this method will always look up the connection pool for matched idle connections created by previous calls of this method.
|
||||
|
||||
An optional Lua table can be specified as the last argument to this method to specify various connect options:
|
||||
|
||||
* `pool`
|
||||
: Specifies a custom name for the connection pool being used. If omitted, then the connection pool name will be generated from the string template `<host>:<port>` or `<unix-socket-path>`.
|
||||
|
||||
## set_timeout
|
||||
|
||||
`syntax: httpc:set_timeout(time)`
|
||||
|
||||
Sets the timeout (in ms) protection for subsequent operations, including the `connect` method.
|
||||
|
||||
## ssl_handshake
|
||||
|
||||
`syntax: session, err = httpc:ssl_handshake(session, host, verify)`
|
||||
|
||||
Performs an SSL handshake on the TCP connection, only availble in ngx_lua > v0.9.11
|
||||
|
||||
See docs for [ngx.socket.tcp](https://github.com/openresty/lua-nginx-module#ngxsockettcp) for details.
|
||||
|
||||
## set_keepalive
|
||||
|
||||
`syntax: ok, err = httpc:set_keepalive(max_idle_timeout, pool_size)`
|
||||
|
||||
Attempts to puts the current connection into the ngx_lua cosocket connection pool.
|
||||
|
||||
You can specify the max idle timeout (in ms) when the connection is in the pool and the maximal size of the pool every nginx worker process.
|
||||
|
||||
Only call this method in the place you would have called the `close` method instead. Calling this method will immediately turn the current http object into the `closed` state. Any subsequent operations other than `connect()` on the current objet will return the `closed` error.
|
||||
|
||||
Note that calling this instead of `close` is "safe" in that it will conditionally close depending on the type of request. Specifically, a `1.0` request without `Connection: Keep-Alive` will be closed, as will a `1.1` request with `Connection: Close`.
|
||||
|
||||
In case of success, returns `1`. In case of errors, returns `nil, err`. In the case where the conneciton is conditionally closed as described above, returns `2` and the error string `connection must be closed`.
|
||||
|
||||
## get_reused_times
|
||||
|
||||
`syntax: times, err = httpc:get_reused_times()`
|
||||
|
||||
This method returns the (successfully) reused times for the current connection. In case of error, it returns `nil` and a string describing the error.
|
||||
|
||||
If the current connection does not come from the built-in connection pool, then this method always returns `0`, that is, the connection has never been reused (yet). If the connection comes from the connection pool, then the return value is always non-zero. So this method can also be used to determine if the current connection comes from the pool.
|
||||
|
||||
## close
|
||||
|
||||
`syntax: ok, err = http:close()`
|
||||
|
||||
Closes the current connection and returns the status.
|
||||
|
||||
In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error.
|
||||
|
||||
|
||||
# Requesting
|
||||
|
||||
## request
|
||||
|
||||
`syntax: res, err = httpc:request(params)`
|
||||
|
||||
Returns a `res` table or `nil` and an error message.
|
||||
|
||||
The `params` table accepts the following fields:
|
||||
|
||||
* `version` The HTTP version number, currently supporting 1.0 or 1.1.
|
||||
* `method` The HTTP method string.
|
||||
* `path` The path string.
|
||||
* `headers` A table of request headers.
|
||||
* `body` The request body as a string, or an iterator function (see [get_client_body_reader](#get_client_body_reader)).
|
||||
* `ssl_verify` Verify SSL cert matches hostname
|
||||
|
||||
When the request is successful, `res` will contain the following fields:
|
||||
|
||||
* `status` The status code.
|
||||
* `headers` A table of headers. Multiple headers with the same field name will be presented as a table of values.
|
||||
* `has_body` A boolean flag indicating if there is a body to be read.
|
||||
* `body_reader` An iterator function for reading the body in a streaming fashion.
|
||||
* `read_body` A method to read the entire body into a string.
|
||||
* `read_trailers` A method to merge any trailers underneath the headers, after reading the body.
|
||||
|
||||
## request_uri
|
||||
|
||||
`syntax: res, err = httpc:request_uri(uri, params)`
|
||||
|
||||
The simple interface. Options supplied in the `params` table are the same as in the generic interface, and will override components found in the uri itself.
|
||||
|
||||
In this mode, there is no need to connect manually first. The connection is made on your behalf, suiting cases where you simply need to grab a URI without too much hassle.
|
||||
|
||||
Additionally there is no ability to stream the response body in this mode. If the request is successful, `res` will contain the following fields:
|
||||
|
||||
* `status` The status code.
|
||||
* `headers` A table of headers.
|
||||
* `body` The response body as a string.
|
||||
|
||||
|
||||
## request_pipeline
|
||||
|
||||
`syntax: responses, err = httpc:request_pipeline(params)`
|
||||
|
||||
This method works as per the [request](#request) method above, but `params` is instead a table of param tables. Each request is sent in order, and `responses` is returned as a table of response handles. For example:
|
||||
|
||||
```lua
|
||||
local responses = httpc:request_pipeline{
|
||||
{
|
||||
path = "/b",
|
||||
},
|
||||
{
|
||||
path = "/c",
|
||||
},
|
||||
{
|
||||
path = "/d",
|
||||
}
|
||||
}
|
||||
|
||||
for i,r in ipairs(responses) do
|
||||
if r.status then
|
||||
ngx.say(r.status)
|
||||
ngx.say(r:read_body())
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Due to the nature of pipelining, no responses are actually read until you attempt to use the response fields (status / headers etc). And since the responses are read off in order, you must read the entire body (and any trailers if you have them), before attempting to read the next response.
|
||||
|
||||
Note this doesn't preclude the use of the streaming response body reader. Responses can still be streamed, so long as the entire body is streamed before attempting to access the next response.
|
||||
|
||||
Be sure to test at least one field (such as status) before trying to use the others, in case a socket read error has occurred.
|
||||
|
||||
# Response
|
||||
|
||||
## res.body_reader
|
||||
|
||||
The `body_reader` iterator can be used to stream the response body in chunk sizes of your choosing, as follows:
|
||||
|
||||
````lua
|
||||
local reader = res.body_reader
|
||||
|
||||
repeat
|
||||
local chunk, err = reader(8192)
|
||||
if err then
|
||||
ngx.log(ngx.ERR, err)
|
||||
break
|
||||
end
|
||||
|
||||
if chunk then
|
||||
-- process
|
||||
end
|
||||
until not chunk
|
||||
````
|
||||
|
||||
If the reader is called with no arguments, the behaviour depends on the type of connection. If the response is encoded as chunked, then the iterator will return the chunks as they arrive. If not, it will simply return the entire body.
|
||||
|
||||
Note that the size provided is actually a **maximum** size. So in the chunked transfer case, you may get chunks smaller than the size you ask, as a remainder of the actual HTTP chunks.
|
||||
|
||||
## res:read_body
|
||||
|
||||
`syntax: body, err = res:read_body()`
|
||||
|
||||
Reads the entire body into a local string.
|
||||
|
||||
|
||||
## res:read_trailers
|
||||
|
||||
`syntax: res:read_trailers()`
|
||||
|
||||
This merges any trailers underneath the `res.headers` table itself. Must be called after reading the body.
|
||||
|
||||
|
||||
# Proxy
|
||||
|
||||
There are two convenience methods for when one simply wishes to proxy the current request to the connected upstream, and safely send it downstream to the client, as a reverse proxy. A complete example:
|
||||
|
||||
```lua
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
httpc:set_timeout(500)
|
||||
local ok, err = httpc:connect(HOST, PORT)
|
||||
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, err)
|
||||
return
|
||||
end
|
||||
|
||||
httpc:set_timeout(2000)
|
||||
httpc:proxy_response(httpc:proxy_request())
|
||||
httpc:set_keepalive()
|
||||
```
|
||||
|
||||
|
||||
## proxy_request
|
||||
|
||||
`syntax: local res, err = httpc:proxy_request(request_body_chunk_size?)`
|
||||
|
||||
Performs a request using the current client request arguments, effectively proxying to the connected upstream. The request body will be read in a streaming fashion, according to `request_body_chunk_size` (see [documentation on the client body reader](#get_client_body_reader) below).
|
||||
|
||||
|
||||
## proxy_response
|
||||
|
||||
`syntax: httpc:proxy_response(res, chunksize?)`
|
||||
|
||||
Sets the current response based on the given `res`. Ensures that hop-by-hop headers are not sent downstream, and will read the response according to `chunksize` (see [documentation on the body reader](#resbody_reader) above).
|
||||
|
||||
|
||||
# Utility
|
||||
|
||||
## parse_uri
|
||||
|
||||
`syntax: local scheme, host, port, path = unpack(httpc:parse_uri(uri))`
|
||||
|
||||
This is a convenience function allowing one to more easily use the generic interface, when the input data is a URI.
|
||||
|
||||
|
||||
## get_client_body_reader
|
||||
|
||||
`syntax: reader, err = httpc:get_client_body_reader(chunksize?, sock?)`
|
||||
|
||||
Returns an iterator function which can be used to read the downstream client request body in a streaming fashion. You may also specify an optional default chunksize (default is `65536`), or an already established socket in
|
||||
place of the client request.
|
||||
|
||||
Example:
|
||||
|
||||
```lua
|
||||
local req_reader = httpc:get_client_body_reader()
|
||||
|
||||
repeat
|
||||
local chunk, err = req_reader(8192)
|
||||
if err then
|
||||
ngx.log(ngx.ERR, err)
|
||||
break
|
||||
end
|
||||
|
||||
if chunk then
|
||||
-- process
|
||||
end
|
||||
until not chunk
|
||||
```
|
||||
|
||||
This iterator can also be used as the value for the body field in request params, allowing one to stream the request body into a proxied upstream request.
|
||||
|
||||
```lua
|
||||
local client_body_reader, err = httpc:get_client_body_reader()
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/helloworld",
|
||||
body = client_body_reader,
|
||||
}
|
||||
```
|
||||
|
||||
If `sock` is specified,
|
||||
|
||||
# Author
|
||||
|
||||
James Hurst <james@pintsized.co.uk>
|
||||
|
||||
Originally started life based on https://github.com/bakins/lua-resty-http-simple. Cosocket docs and implementation borrowed from the other lua-resty-* cosocket modules.
|
||||
|
||||
|
||||
# Licence
|
||||
|
||||
This module is licensed under the 2-clause BSD license.
|
||||
|
||||
Copyright (c) 2013, James Hurst <james@pintsized.co.uk>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
818
controllers/nginx/lua/vendor/lua-resty-http/lib/resty/http.lua
vendored
Normal file
818
controllers/nginx/lua/vendor/lua-resty-http/lib/resty/http.lua
vendored
Normal file
|
|
@ -0,0 +1,818 @@
|
|||
local http_headers = require "resty.http_headers"
|
||||
|
||||
local ngx_socket_tcp = ngx.socket.tcp
|
||||
local ngx_req = ngx.req
|
||||
local ngx_req_socket = ngx_req.socket
|
||||
local ngx_req_get_headers = ngx_req.get_headers
|
||||
local ngx_req_get_method = ngx_req.get_method
|
||||
local str_gmatch = string.gmatch
|
||||
local str_lower = string.lower
|
||||
local str_upper = string.upper
|
||||
local str_find = string.find
|
||||
local str_sub = string.sub
|
||||
local str_gsub = string.gsub
|
||||
local tbl_concat = table.concat
|
||||
local tbl_insert = table.insert
|
||||
local ngx_encode_args = ngx.encode_args
|
||||
local ngx_re_match = ngx.re.match
|
||||
local ngx_re_gsub = ngx.re.gsub
|
||||
local ngx_log = ngx.log
|
||||
local ngx_DEBUG = ngx.DEBUG
|
||||
local ngx_ERR = ngx.ERR
|
||||
local ngx_NOTICE = ngx.NOTICE
|
||||
local ngx_var = ngx.var
|
||||
local co_yield = coroutine.yield
|
||||
local co_create = coroutine.create
|
||||
local co_status = coroutine.status
|
||||
local co_resume = coroutine.resume
|
||||
|
||||
|
||||
-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
local HOP_BY_HOP_HEADERS = {
|
||||
["connection"] = true,
|
||||
["keep-alive"] = true,
|
||||
["proxy-authenticate"] = true,
|
||||
["proxy-authorization"] = true,
|
||||
["te"] = true,
|
||||
["trailers"] = true,
|
||||
["transfer-encoding"] = true,
|
||||
["upgrade"] = true,
|
||||
["content-length"] = true, -- Not strictly hop-by-hop, but Nginx will deal
|
||||
-- with this (may send chunked for example).
|
||||
}
|
||||
|
||||
|
||||
-- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot
|
||||
-- be resumed. This protects user code from inifite loops when doing things like
|
||||
-- repeat
|
||||
-- local chunk, err = res.body_reader()
|
||||
-- if chunk then -- <-- This could be a string msg in the core wrap function.
|
||||
-- ...
|
||||
-- end
|
||||
-- until not chunk
|
||||
local co_wrap = function(func)
|
||||
local co = co_create(func)
|
||||
if not co then
|
||||
return nil, "could not create coroutine"
|
||||
else
|
||||
return function(...)
|
||||
if co_status(co) == "suspended" then
|
||||
return select(2, co_resume(co, ...))
|
||||
else
|
||||
return nil, "can't resume a " .. co_status(co) .. " coroutine"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local _M = {
|
||||
_VERSION = '0.07',
|
||||
}
|
||||
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
|
||||
|
||||
local mt = { __index = _M }
|
||||
|
||||
|
||||
local HTTP = {
|
||||
[1.0] = " HTTP/1.0\r\n",
|
||||
[1.1] = " HTTP/1.1\r\n",
|
||||
}
|
||||
|
||||
local DEFAULT_PARAMS = {
|
||||
method = "GET",
|
||||
path = "/",
|
||||
version = 1.1,
|
||||
}
|
||||
|
||||
|
||||
function _M.new(self)
|
||||
local sock, err = ngx_socket_tcp()
|
||||
if not sock then
|
||||
return nil, err
|
||||
end
|
||||
return setmetatable({ sock = sock, keepalive = true }, mt)
|
||||
end
|
||||
|
||||
|
||||
function _M.set_timeout(self, timeout)
|
||||
local sock = self.sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
return sock:settimeout(timeout)
|
||||
end
|
||||
|
||||
|
||||
function _M.ssl_handshake(self, ...)
|
||||
local sock = self.sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
return sock:sslhandshake(...)
|
||||
end
|
||||
|
||||
|
||||
function _M.connect(self, ...)
|
||||
local sock = self.sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
self.host = select(1, ...)
|
||||
self.keepalive = true
|
||||
|
||||
return sock:connect(...)
|
||||
end
|
||||
|
||||
|
||||
function _M.set_keepalive(self, ...)
|
||||
local sock = self.sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
if self.keepalive == true then
|
||||
return sock:setkeepalive(...)
|
||||
else
|
||||
-- The server said we must close the connection, so we cannot setkeepalive.
|
||||
-- If close() succeeds we return 2 instead of 1, to differentiate between
|
||||
-- a normal setkeepalive() failure and an intentional close().
|
||||
local res, err = sock:close()
|
||||
if res then
|
||||
return 2, "connection must be closed"
|
||||
else
|
||||
return res, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.get_reused_times(self)
|
||||
local sock = self.sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
return sock:getreusedtimes()
|
||||
end
|
||||
|
||||
|
||||
function _M.close(self)
|
||||
local sock = self.sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
return sock:close()
|
||||
end
|
||||
|
||||
|
||||
local function _should_receive_body(method, code)
|
||||
if method == "HEAD" then return nil end
|
||||
if code == 204 or code == 304 then return nil end
|
||||
if code >= 100 and code < 200 then return nil end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function _M.parse_uri(self, uri)
|
||||
local m, err = ngx_re_match(uri, [[^(http[s]*)://([^:/]+)(?::(\d+))?(.*)]],
|
||||
"jo")
|
||||
|
||||
if not m then
|
||||
if err then
|
||||
return nil, "failed to match the uri: " .. err
|
||||
end
|
||||
|
||||
return nil, "bad uri"
|
||||
else
|
||||
if not m[3] then
|
||||
if m[1] == "https" then
|
||||
m[3] = 443
|
||||
else
|
||||
m[3] = 80
|
||||
end
|
||||
end
|
||||
if not m[4] or "" == m[4] then m[4] = "/" end
|
||||
return m, nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function _format_request(params)
|
||||
local version = params.version
|
||||
local headers = params.headers or {}
|
||||
|
||||
local query = params.query or ""
|
||||
if query then
|
||||
if type(query) == "table" then
|
||||
query = "?" .. ngx_encode_args(query)
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize request
|
||||
local req = {
|
||||
str_upper(params.method),
|
||||
" ",
|
||||
params.path,
|
||||
query,
|
||||
HTTP[version],
|
||||
-- Pre-allocate slots for minimum headers and carriage return.
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
}
|
||||
local c = 6 -- req table index it's faster to do this inline vs table.insert
|
||||
|
||||
-- Append headers
|
||||
for key, values in pairs(headers) do
|
||||
if type(values) ~= "table" then
|
||||
values = {values}
|
||||
end
|
||||
|
||||
key = tostring(key)
|
||||
for _, value in pairs(values) do
|
||||
req[c] = key .. ": " .. tostring(value) .. "\r\n"
|
||||
c = c + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Close headers
|
||||
req[c] = "\r\n"
|
||||
|
||||
return tbl_concat(req)
|
||||
end
|
||||
|
||||
|
||||
local function _receive_status(sock)
|
||||
local line, err = sock:receive("*l")
|
||||
if not line then
|
||||
return nil, nil, err
|
||||
end
|
||||
|
||||
return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8))
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function _receive_headers(sock)
|
||||
local headers = http_headers.new()
|
||||
|
||||
repeat
|
||||
local line, err = sock:receive("*l")
|
||||
if not line then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
for key, val in str_gmatch(line, "([^:%s]+):%s*(.+)") do
|
||||
if headers[key] then
|
||||
if type(headers[key]) ~= "table" then
|
||||
headers[key] = { headers[key] }
|
||||
end
|
||||
tbl_insert(headers[key], tostring(val))
|
||||
else
|
||||
headers[key] = tostring(val)
|
||||
end
|
||||
end
|
||||
until str_find(line, "^%s*$")
|
||||
|
||||
return headers, nil
|
||||
end
|
||||
|
||||
|
||||
local function _chunked_body_reader(sock, default_chunk_size)
|
||||
return co_wrap(function(max_chunk_size)
|
||||
local max_chunk_size = max_chunk_size or default_chunk_size
|
||||
local remaining = 0
|
||||
local length
|
||||
|
||||
repeat
|
||||
-- If we still have data on this chunk
|
||||
if max_chunk_size and remaining > 0 then
|
||||
|
||||
if remaining > max_chunk_size then
|
||||
-- Consume up to max_chunk_size
|
||||
length = max_chunk_size
|
||||
remaining = remaining - max_chunk_size
|
||||
else
|
||||
-- Consume all remaining
|
||||
length = remaining
|
||||
remaining = 0
|
||||
end
|
||||
else -- This is a fresh chunk
|
||||
|
||||
-- Receive the chunk size
|
||||
local str, err = sock:receive("*l")
|
||||
if not str then
|
||||
co_yield(nil, err)
|
||||
end
|
||||
|
||||
length = tonumber(str, 16)
|
||||
|
||||
if not length then
|
||||
co_yield(nil, "unable to read chunksize")
|
||||
end
|
||||
|
||||
if max_chunk_size and length > max_chunk_size then
|
||||
-- Consume up to max_chunk_size
|
||||
remaining = length - max_chunk_size
|
||||
length = max_chunk_size
|
||||
end
|
||||
end
|
||||
|
||||
if length > 0 then
|
||||
local str, err = sock:receive(length)
|
||||
if not str then
|
||||
co_yield(nil, err)
|
||||
end
|
||||
|
||||
max_chunk_size = co_yield(str) or default_chunk_size
|
||||
|
||||
-- If we're finished with this chunk, read the carriage return.
|
||||
if remaining == 0 then
|
||||
sock:receive(2) -- read \r\n
|
||||
end
|
||||
else
|
||||
-- Read the last (zero length) chunk's carriage return
|
||||
sock:receive(2) -- read \r\n
|
||||
end
|
||||
|
||||
until length == 0
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function _body_reader(sock, content_length, default_chunk_size)
|
||||
return co_wrap(function(max_chunk_size)
|
||||
local max_chunk_size = max_chunk_size or default_chunk_size
|
||||
|
||||
if not content_length and max_chunk_size then
|
||||
-- We have no length, but wish to stream.
|
||||
-- HTTP 1.0 with no length will close connection, so read chunks to the end.
|
||||
repeat
|
||||
local str, err, partial = sock:receive(max_chunk_size)
|
||||
if not str and err == "closed" then
|
||||
max_chunk_size = tonumber(co_yield(partial, err) or default_chunk_size)
|
||||
end
|
||||
|
||||
max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
|
||||
if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
|
||||
|
||||
if not max_chunk_size then
|
||||
ngx_log(ngx_ERR, "Buffer size not specified, bailing")
|
||||
break
|
||||
end
|
||||
until not str
|
||||
|
||||
elseif not content_length then
|
||||
-- We have no length but don't wish to stream.
|
||||
-- HTTP 1.0 with no length will close connection, so read to the end.
|
||||
co_yield(sock:receive("*a"))
|
||||
|
||||
elseif not max_chunk_size then
|
||||
-- We have a length and potentially keep-alive, but want everything.
|
||||
co_yield(sock:receive(content_length))
|
||||
|
||||
else
|
||||
-- We have a length and potentially a keep-alive, and wish to stream
|
||||
-- the response.
|
||||
local received = 0
|
||||
repeat
|
||||
local length = max_chunk_size
|
||||
if received + length > content_length then
|
||||
length = content_length - received
|
||||
end
|
||||
|
||||
if length > 0 then
|
||||
local str, err = sock:receive(length)
|
||||
if not str then
|
||||
max_chunk_size = tonumber(co_yield(nil, err) or default_chunk_size)
|
||||
end
|
||||
received = received + length
|
||||
|
||||
max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
|
||||
if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
|
||||
|
||||
if not max_chunk_size then
|
||||
ngx_log(ngx_ERR, "Buffer size not specified, bailing")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
until length == 0
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function _no_body_reader()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local function _read_body(res)
|
||||
local reader = res.body_reader
|
||||
|
||||
if not reader then
|
||||
-- Most likely HEAD or 304 etc.
|
||||
return nil, "no body to be read"
|
||||
end
|
||||
|
||||
local chunks = {}
|
||||
local c = 1
|
||||
|
||||
local chunk, err
|
||||
repeat
|
||||
chunk, err = reader()
|
||||
|
||||
if err then
|
||||
return nil, err, tbl_concat(chunks) -- Return any data so far.
|
||||
end
|
||||
if chunk then
|
||||
chunks[c] = chunk
|
||||
c = c + 1
|
||||
end
|
||||
until not chunk
|
||||
|
||||
return tbl_concat(chunks)
|
||||
end
|
||||
|
||||
|
||||
local function _trailer_reader(sock)
|
||||
return co_wrap(function()
|
||||
co_yield(_receive_headers(sock))
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function _read_trailers(res)
|
||||
local reader = res.trailer_reader
|
||||
if not reader then
|
||||
return nil, "no trailers"
|
||||
end
|
||||
|
||||
local trailers = reader()
|
||||
setmetatable(res.headers, { __index = trailers })
|
||||
end
|
||||
|
||||
|
||||
local function _send_body(sock, body)
|
||||
if type(body) == 'function' then
|
||||
repeat
|
||||
local chunk, err, partial = body()
|
||||
|
||||
if chunk then
|
||||
local ok,err = sock:send(chunk)
|
||||
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
elseif err ~= nil then
|
||||
return nil, err, partial
|
||||
end
|
||||
|
||||
until chunk == nil
|
||||
elseif body ~= nil then
|
||||
local bytes, err = sock:send(body)
|
||||
|
||||
if not bytes then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
return true, nil
|
||||
end
|
||||
|
||||
|
||||
local function _handle_continue(sock, body)
|
||||
local status, version, err = _receive_status(sock)
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
-- Only send body if we receive a 100 Continue
|
||||
if status == 100 then
|
||||
local ok, err = sock:receive("*l") -- Read carriage return
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
_send_body(sock, body)
|
||||
end
|
||||
return status, version, err
|
||||
end
|
||||
|
||||
|
||||
function _M.send_request(self, params)
|
||||
-- Apply defaults
|
||||
setmetatable(params, { __index = DEFAULT_PARAMS })
|
||||
|
||||
local sock = self.sock
|
||||
local body = params.body
|
||||
local headers = http_headers.new()
|
||||
|
||||
local params_headers = params.headers
|
||||
if params_headers then
|
||||
-- We assign one by one so that the metatable can handle case insensitivity
|
||||
-- for us. You can blame the spec for this inefficiency.
|
||||
for k,v in pairs(params_headers) do
|
||||
headers[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Ensure minimal headers are set
|
||||
if type(body) == 'string' and not headers["Content-Length"] then
|
||||
headers["Content-Length"] = #body
|
||||
end
|
||||
if not headers["Host"] then
|
||||
headers["Host"] = self.host
|
||||
end
|
||||
if not headers["User-Agent"] then
|
||||
headers["User-Agent"] = _M._USER_AGENT
|
||||
end
|
||||
if params.version == 1.0 and not headers["Connection"] then
|
||||
headers["Connection"] = "Keep-Alive"
|
||||
end
|
||||
|
||||
params.headers = headers
|
||||
|
||||
-- Format and send request
|
||||
local req = _format_request(params)
|
||||
ngx_log(ngx_DEBUG, "\n", req)
|
||||
local bytes, err = sock:send(req)
|
||||
|
||||
if not bytes then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
-- Send the request body, unless we expect: continue, in which case
|
||||
-- we handle this as part of reading the response.
|
||||
if headers["Expect"] ~= "100-continue" then
|
||||
local ok, err, partial = _send_body(sock, body)
|
||||
if not ok then
|
||||
return nil, err, partial
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function _M.read_response(self, params)
|
||||
local sock = self.sock
|
||||
|
||||
local status, version, err
|
||||
|
||||
-- If we expect: continue, we need to handle this, sending the body if allowed.
|
||||
-- If we don't get 100 back, then status is the actual status.
|
||||
if params.headers["Expect"] == "100-continue" then
|
||||
local _status, _version, _err = _handle_continue(sock, params.body)
|
||||
if not _status then
|
||||
return nil, _err
|
||||
elseif _status ~= 100 then
|
||||
status, version, err = _status, _version, _err
|
||||
end
|
||||
end
|
||||
|
||||
-- Just read the status as normal.
|
||||
if not status then
|
||||
status, version, err = _receive_status(sock)
|
||||
if not status then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local res_headers, err = _receive_headers(sock)
|
||||
if not res_headers then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
-- Determine if we should keepalive or not.
|
||||
local ok, connection = pcall(str_lower, res_headers["Connection"])
|
||||
if ok then
|
||||
if (version == 1.1 and connection == "close") or
|
||||
(version == 1.0 and connection ~= "keep-alive") then
|
||||
self.keepalive = false
|
||||
end
|
||||
end
|
||||
|
||||
local body_reader = _no_body_reader
|
||||
local trailer_reader, err = nil, nil
|
||||
local has_body = false
|
||||
|
||||
-- Receive the body_reader
|
||||
if _should_receive_body(params.method, status) then
|
||||
local ok, encoding = pcall(str_lower, res_headers["Transfer-Encoding"])
|
||||
if ok and version == 1.1 and encoding == "chunked" then
|
||||
body_reader, err = _chunked_body_reader(sock)
|
||||
has_body = true
|
||||
else
|
||||
|
||||
local ok, length = pcall(tonumber, res_headers["Content-Length"])
|
||||
if ok then
|
||||
body_reader, err = _body_reader(sock, length)
|
||||
has_body = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if res_headers["Trailer"] then
|
||||
trailer_reader, err = _trailer_reader(sock)
|
||||
end
|
||||
|
||||
if err then
|
||||
return nil, err
|
||||
else
|
||||
return {
|
||||
status = status,
|
||||
headers = res_headers,
|
||||
has_body = has_body,
|
||||
body_reader = body_reader,
|
||||
read_body = _read_body,
|
||||
trailer_reader = trailer_reader,
|
||||
read_trailers = _read_trailers,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.request(self, params)
|
||||
local res, err = self:send_request(params)
|
||||
if not res then
|
||||
return res, err
|
||||
else
|
||||
return self:read_response(params)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.request_pipeline(self, requests)
|
||||
for i, params in ipairs(requests) do
|
||||
if params.headers and params.headers["Expect"] == "100-continue" then
|
||||
return nil, "Cannot pipeline request specifying Expect: 100-continue"
|
||||
end
|
||||
|
||||
local res, err = self:send_request(params)
|
||||
if not res then
|
||||
return res, err
|
||||
end
|
||||
end
|
||||
|
||||
local responses = {}
|
||||
for i, params in ipairs(requests) do
|
||||
responses[i] = setmetatable({
|
||||
params = params,
|
||||
response_read = false,
|
||||
}, {
|
||||
-- Read each actual response lazily, at the point the user tries
|
||||
-- to access any of the fields.
|
||||
__index = function(t, k)
|
||||
local res, err
|
||||
if t.response_read == false then
|
||||
res, err = _M.read_response(self, t.params)
|
||||
t.response_read = true
|
||||
|
||||
if not res then
|
||||
ngx_log(ngx_ERR, err)
|
||||
else
|
||||
for rk, rv in pairs(res) do
|
||||
t[rk] = rv
|
||||
end
|
||||
end
|
||||
end
|
||||
return rawget(t, k)
|
||||
end,
|
||||
})
|
||||
end
|
||||
return responses
|
||||
end
|
||||
|
||||
|
||||
function _M.request_uri(self, uri, params)
|
||||
if not params then params = {} end
|
||||
|
||||
local parsed_uri, err = self:parse_uri(uri)
|
||||
if not parsed_uri then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local scheme, host, port, path = unpack(parsed_uri)
|
||||
if not params.path then params.path = path end
|
||||
|
||||
local c, err = self:connect(host, port)
|
||||
if not c then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if scheme == "https" then
|
||||
local verify = true
|
||||
if params.ssl_verify == false then
|
||||
verify = false
|
||||
end
|
||||
local ok, err = self:ssl_handshake(nil, host, verify)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
local res, err = self:request(params)
|
||||
if not res then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local body, err = res:read_body()
|
||||
if not body then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
res.body = body
|
||||
|
||||
local ok, err = self:set_keepalive()
|
||||
if not ok then
|
||||
ngx_log(ngx_ERR, err)
|
||||
end
|
||||
|
||||
return res, nil
|
||||
end
|
||||
|
||||
|
||||
function _M.get_client_body_reader(self, chunksize, sock)
|
||||
local chunksize = chunksize or 65536
|
||||
if not sock then
|
||||
local ok, err
|
||||
ok, sock, err = pcall(ngx_req_socket)
|
||||
|
||||
if not ok then
|
||||
return nil, sock -- pcall err
|
||||
end
|
||||
|
||||
if not sock then
|
||||
if err == "no body" then
|
||||
return nil
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local headers = ngx_req_get_headers()
|
||||
local length = headers.content_length
|
||||
local encoding = headers.transfer_encoding
|
||||
if length then
|
||||
return _body_reader(sock, tonumber(length), chunksize)
|
||||
elseif encoding and str_lower(encoding) == 'chunked' then
|
||||
-- Not yet supported by ngx_lua but should just work...
|
||||
return _chunked_body_reader(sock, chunksize)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.proxy_request(self, chunksize)
|
||||
return self:request{
|
||||
method = ngx_req_get_method(),
|
||||
path = ngx_re_gsub(ngx_var.uri, "\\s", "%20", "jo") .. ngx_var.is_args .. (ngx_var.query_string or ""),
|
||||
body = self:get_client_body_reader(chunksize),
|
||||
headers = ngx_req_get_headers(),
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function _M.proxy_response(self, response, chunksize)
|
||||
if not response then
|
||||
ngx_log(ngx_ERR, "no response provided")
|
||||
return
|
||||
end
|
||||
|
||||
ngx.status = response.status
|
||||
|
||||
-- Filter out hop-by-hop headeres
|
||||
for k,v in pairs(response.headers) do
|
||||
if not HOP_BY_HOP_HEADERS[str_lower(k)] then
|
||||
ngx.header[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
local reader = response.body_reader
|
||||
repeat
|
||||
local chunk, err = reader(chunksize)
|
||||
if err then
|
||||
ngx_log(ngx_ERR, err)
|
||||
break
|
||||
end
|
||||
|
||||
if chunk then
|
||||
local res, err = ngx.print(chunk)
|
||||
if not res then
|
||||
ngx_log(ngx_ERR, err)
|
||||
break
|
||||
end
|
||||
end
|
||||
until not chunk
|
||||
end
|
||||
|
||||
|
||||
return _M
|
||||
62
controllers/nginx/lua/vendor/lua-resty-http/lib/resty/http_headers.lua
vendored
Normal file
62
controllers/nginx/lua/vendor/lua-resty-http/lib/resty/http_headers.lua
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
local rawget, rawset, setmetatable =
|
||||
rawget, rawset, setmetatable
|
||||
|
||||
local str_gsub = string.gsub
|
||||
local str_lower = string.lower
|
||||
|
||||
|
||||
local _M = {
|
||||
_VERSION = '0.01',
|
||||
}
|
||||
|
||||
|
||||
-- Returns an empty headers table with internalised case normalisation.
|
||||
-- Supports the same cases as in ngx_lua:
|
||||
--
|
||||
-- headers.content_length
|
||||
-- headers["content-length"]
|
||||
-- headers["Content-Length"]
|
||||
function _M.new(self)
|
||||
local mt = {
|
||||
normalised = {},
|
||||
}
|
||||
|
||||
|
||||
mt.__index = function(t, k)
|
||||
local k_hyphened = str_gsub(k, "_", "-")
|
||||
local matched = rawget(t, k)
|
||||
if matched then
|
||||
return matched
|
||||
else
|
||||
local k_normalised = str_lower(k_hyphened)
|
||||
return rawget(t, mt.normalised[k_normalised])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- First check the normalised table. If there's no match (first time) add an entry for
|
||||
-- our current case in the normalised table. This is to preserve the human (prettier) case
|
||||
-- instead of outputting lowercased header names.
|
||||
--
|
||||
-- If there's a match, we're being updated, just with a different case for the key. We use
|
||||
-- the normalised table to give us the original key, and perorm a rawset().
|
||||
mt.__newindex = function(t, k, v)
|
||||
-- we support underscore syntax, so always hyphenate.
|
||||
local k_hyphened = str_gsub(k, "_", "-")
|
||||
|
||||
-- lowercase hyphenated is "normalised"
|
||||
local k_normalised = str_lower(k_hyphened)
|
||||
|
||||
if not mt.normalised[k_normalised] then
|
||||
mt.normalised[k_normalised] = k_hyphened
|
||||
rawset(t, k_hyphened, v)
|
||||
else
|
||||
rawset(t, mt.normalised[k_normalised], v)
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
|
||||
return _M
|
||||
33
controllers/nginx/lua/vendor/lua-resty-http/lua-resty-http-0.07-0.rockspec
vendored
Normal file
33
controllers/nginx/lua/vendor/lua-resty-http/lua-resty-http-0.07-0.rockspec
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package = "lua-resty-http"
|
||||
version = "0.07-0"
|
||||
source = {
|
||||
url = "git://github.com/pintsized/lua-resty-http",
|
||||
tag = "v0.07"
|
||||
}
|
||||
description = {
|
||||
summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.",
|
||||
detailed = [[
|
||||
Features an HTTP 1.0 and 1.1 streaming interface to reading
|
||||
bodies using coroutines, for predictable memory usage in Lua
|
||||
land. Alternative simple interface for singleshot requests
|
||||
without manual connection step. Supports chunked transfer
|
||||
encoding, keepalive, pipelining, and trailers. Headers are
|
||||
treated case insensitively. Probably production ready in most
|
||||
cases, though not yet proven in the wild.
|
||||
Recommended by the OpenResty maintainer as a long-term
|
||||
replacement for internal requests through ngx.location.capture.
|
||||
]],
|
||||
homepage = "https://github.com/pintsized/lua-resty-http",
|
||||
license = "2-clause BSD",
|
||||
maintainer = "James Hurst <james@pintsized.co.uk>"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
["resty.http"] = "lib/resty/http.lua",
|
||||
["resty.http_headers"] = "lib/resty/http_headers.lua"
|
||||
}
|
||||
}
|
||||
231
controllers/nginx/lua/vendor/lua-resty-http/t/01-basic.t
vendored
Normal file
231
controllers/nginx/lua/vendor/lua-resty-http/t/01-basic.t
vendored
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4) + 1;
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Simple default get.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
ngx.status = res.status
|
||||
ngx.print(res:read_body())
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
echo "OK";
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: HTTP 1.0
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
version = 1.0,
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
ngx.status = res.status
|
||||
ngx.print(res:read_body())
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
echo "OK";
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 3: Status code
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
ngx.status = res.status
|
||||
ngx.print(res:read_body())
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.status = 404
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
OK
|
||||
--- error_code: 404
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 4: Response headers
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
ngx.status = res.status
|
||||
ngx.say(res.headers["X-Test"])
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.header["X-Test"] = "x-value"
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
x-value
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 5: Query
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
query = {
|
||||
a = 1,
|
||||
b = 2,
|
||||
},
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
ngx.status = res.status
|
||||
|
||||
for k,v in pairs(res.headers) do
|
||||
ngx.header[k] = v
|
||||
end
|
||||
|
||||
ngx.print(res:read_body())
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
for k,v in pairs(ngx.req.get_uri_args()) do
|
||||
ngx.header["X-Header-" .. string.upper(k)] = v
|
||||
end
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_headers
|
||||
X-Header-A: 1
|
||||
X-Header-B: 2
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 7: HEAD has no body.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
method = "HEAD",
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
|
||||
if body then
|
||||
ngx.print(body)
|
||||
end
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
echo "OK";
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
158
controllers/nginx/lua/vendor/lua-resty-http/t/02-chunked.t
vendored
Normal file
158
controllers/nginx/lua/vendor/lua-resty-http/t/02-chunked.t
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Non chunked.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
|
||||
ngx.say(#body)
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
chunked_transfer_encoding off;
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
32768
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: Chunked. The number of chunks received when no max size is given proves the response was in fact chunked.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
local c = 1
|
||||
repeat
|
||||
local chunk, err = res.body_reader()
|
||||
if chunk then
|
||||
chunks[c] = chunk
|
||||
c = c + 1
|
||||
end
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
|
||||
ngx.say(#body)
|
||||
ngx.say(#chunks)
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
65536
|
||||
2
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 3: Chunked using read_body method.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
|
||||
ngx.say(#body)
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
65536
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
185
controllers/nginx/lua/vendor/lua-resty-http/t/03-requestbody.t
vendored
Normal file
185
controllers/nginx/lua/vendor/lua-resty-http/t/03-requestbody.t
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: POST form-urlencoded
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
body = "a=1&b=2&c=3",
|
||||
path = "/b",
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
|
||||
ngx.say(res:read_body())
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.req.read_body()
|
||||
local args = ngx.req.get_post_args()
|
||||
ngx.say("a: ", args.a)
|
||||
ngx.say("b: ", args.b)
|
||||
ngx.print("c: ", args.c)
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: POST form-urlencoded 1.0
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
method = "POST",
|
||||
body = "a=1&b=2&c=3",
|
||||
path = "/b",
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
},
|
||||
version = 1.0,
|
||||
}
|
||||
|
||||
ngx.say(res:read_body())
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.req.read_body()
|
||||
local args = ngx.req.get_post_args()
|
||||
ngx.say(ngx.req.get_method())
|
||||
ngx.say("a: ", args.a)
|
||||
ngx.say("b: ", args.b)
|
||||
ngx.print("c: ", args.c)
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
POST
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 3: 100 Continue does not end requset
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
body = "a=1&b=2&c=3",
|
||||
path = "/b",
|
||||
headers = {
|
||||
["Expect"] = "100-continue",
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
ngx.say(res.status)
|
||||
ngx.say(res:read_body())
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.req.read_body()
|
||||
local args = ngx.req.get_post_args()
|
||||
ngx.say("a: ", args.a)
|
||||
ngx.say("b: ", args.b)
|
||||
ngx.print("c: ", args.c)
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
200
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
=== TEST 4: Return non-100 status to user
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
headers = {
|
||||
["Expect"] = "100-continue",
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
if not res then
|
||||
ngx.say(err)
|
||||
end
|
||||
ngx.say(res.status)
|
||||
ngx.say(res:read_body())
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
return 417 "Expectation Failed";
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
417
|
||||
Expectation Failed
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
151
controllers/nginx/lua/vendor/lua-resty-http/t/04-trailers.t
vendored
Normal file
151
controllers/nginx/lua/vendor/lua-resty-http/t/04-trailers.t
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Trailers. Check Content-MD5 generated after the body is sent matches up.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
headers = {
|
||||
["TE"] = "trailers",
|
||||
}
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
local hash = ngx.md5(body)
|
||||
res:read_trailers()
|
||||
|
||||
if res.headers["Content-MD5"] == hash then
|
||||
ngx.say("OK")
|
||||
else
|
||||
ngx.say(res.headers["Content-MD5"])
|
||||
end
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
-- We use the raw socket to compose a response, since OpenResty
|
||||
-- doesnt support trailers natively.
|
||||
|
||||
ngx.req.read_body()
|
||||
local sock, err = ngx.req.socket(true)
|
||||
if not sock then
|
||||
ngx.say(err)
|
||||
end
|
||||
|
||||
local res = {}
|
||||
table.insert(res, "HTTP/1.1 200 OK")
|
||||
table.insert(res, "Date: " .. ngx.http_time(ngx.time()))
|
||||
table.insert(res, "Transfer-Encoding: chunked")
|
||||
table.insert(res, "Trailer: Content-MD5")
|
||||
table.insert(res, "")
|
||||
|
||||
local body = "Hello, World"
|
||||
|
||||
table.insert(res, string.format("%x", #body))
|
||||
table.insert(res, body)
|
||||
table.insert(res, "0")
|
||||
table.insert(res, "")
|
||||
|
||||
table.insert(res, "Content-MD5: " .. ngx.md5(body))
|
||||
|
||||
table.insert(res, "")
|
||||
table.insert(res, "")
|
||||
sock:send(table.concat(res, "\\r\\n"))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: Advertised trailer does not exist, handled gracefully.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
headers = {
|
||||
["TE"] = "trailers",
|
||||
}
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
local hash = ngx.md5(body)
|
||||
res:read_trailers()
|
||||
|
||||
ngx.say("OK")
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
-- We use the raw socket to compose a response, since OpenResty
|
||||
-- doesnt support trailers natively.
|
||||
|
||||
ngx.req.read_body()
|
||||
local sock, err = ngx.req.socket(true)
|
||||
if not sock then
|
||||
ngx.say(err)
|
||||
end
|
||||
|
||||
local res = {}
|
||||
table.insert(res, "HTTP/1.1 200 OK")
|
||||
table.insert(res, "Date: " .. ngx.http_time(ngx.time()))
|
||||
table.insert(res, "Transfer-Encoding: chunked")
|
||||
table.insert(res, "Trailer: Content-MD5")
|
||||
table.insert(res, "")
|
||||
|
||||
local body = "Hello, World"
|
||||
|
||||
table.insert(res, string.format("%x", #body))
|
||||
table.insert(res, body)
|
||||
table.insert(res, "0")
|
||||
|
||||
table.insert(res, "")
|
||||
table.insert(res, "")
|
||||
sock:send(table.concat(res, "\\r\\n"))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
566
controllers/nginx/lua/vendor/lua-resty-http/t/05-stream.t
vendored
Normal file
566
controllers/nginx/lua/vendor/lua-resty-http/t/05-stream.t
vendored
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4) - 1;
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Chunked streaming body reader returns the right content length.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
repeat
|
||||
local chunk = res.body_reader()
|
||||
if chunk then
|
||||
table.insert(chunks, chunk)
|
||||
end
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
ngx.say(#body)
|
||||
ngx.say(res.headers["Transfer-Encoding"])
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
32768
|
||||
chunked
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: Non-Chunked streaming body reader returns the right content length.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
repeat
|
||||
local chunk = res.body_reader()
|
||||
if chunk then
|
||||
table.insert(chunks, chunk)
|
||||
end
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
ngx.say(#body)
|
||||
ngx.say(res.headers["Transfer-Encoding"])
|
||||
ngx.say(#chunks)
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
chunked_transfer_encoding off;
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
32768
|
||||
nil
|
||||
1
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2b: Non-Chunked streaming body reader, buffer size becomes nil
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
local buffer_size = 16384
|
||||
repeat
|
||||
local chunk = res.body_reader(buffer_size)
|
||||
if chunk then
|
||||
table.insert(chunks, chunk)
|
||||
end
|
||||
|
||||
buffer_size = nil
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
ngx.say(res.headers["Transfer-Encoding"])
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
chunked_transfer_encoding off;
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
nil
|
||||
--- error_log
|
||||
Buffer size not specified, bailing
|
||||
|
||||
|
||||
=== TEST 3: HTTP 1.0 body reader with no max size returns the right content length.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
version = 1.0,
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
repeat
|
||||
local chunk = res.body_reader()
|
||||
if chunk then
|
||||
table.insert(chunks, chunk)
|
||||
end
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
ngx.say(#body)
|
||||
ngx.say(res.headers["Transfer-Encoding"])
|
||||
ngx.say(#chunks)
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
chunked_transfer_encoding off;
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
32768
|
||||
nil
|
||||
1
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 4: HTTP 1.0 body reader with max chunk size returns the right content length.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
version = 1.0,
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
local size = 8192
|
||||
repeat
|
||||
local chunk = res.body_reader(size)
|
||||
if chunk then
|
||||
table.insert(chunks, chunk)
|
||||
end
|
||||
size = size + size
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
ngx.say(#body)
|
||||
ngx.say(res.headers["Transfer-Encoding"])
|
||||
ngx.say(#chunks)
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
chunked_transfer_encoding off;
|
||||
content_by_lua '
|
||||
local len = 32769
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
32769
|
||||
nil
|
||||
3
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 4b: HTTP 1.0 body reader with no content length, stream works as expected.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
version = 1.0,
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
local size = 8192
|
||||
repeat
|
||||
local chunk = res.body_reader(size)
|
||||
if chunk then
|
||||
table.insert(chunks, chunk)
|
||||
end
|
||||
size = size + size
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
ngx.say(#body)
|
||||
ngx.say(#chunks)
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.req.read_body()
|
||||
local sock, err = ngx.req.socket(true)
|
||||
if not sock then
|
||||
ngx.say(err)
|
||||
end
|
||||
|
||||
local res = {}
|
||||
table.insert(res, "HTTP/1.0 200 OK")
|
||||
table.insert(res, "Date: " .. ngx.http_time(ngx.time()))
|
||||
table.insert(res, "")
|
||||
|
||||
local len = 32769
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
table.insert(res, table.concat(t))
|
||||
sock:send(table.concat(res, "\\r\\n"))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
32769
|
||||
3
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 5: Chunked streaming body reader with max chunk size returns the right content length.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
}
|
||||
|
||||
local chunks = {}
|
||||
local size = 8192
|
||||
repeat
|
||||
local chunk = res.body_reader(size)
|
||||
if chunk then
|
||||
table.insert(chunks, chunk)
|
||||
end
|
||||
size = size + size
|
||||
until not chunk
|
||||
|
||||
local body = table.concat(chunks)
|
||||
ngx.say(#body)
|
||||
ngx.say(res.headers["Transfer-Encoding"])
|
||||
ngx.say(#chunks)
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
local len = 32768
|
||||
local t = {}
|
||||
for i=1,len do
|
||||
t[i] = 0
|
||||
end
|
||||
ngx.print(table.concat(t))
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
32768
|
||||
chunked
|
||||
3
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 6: Request reader correctly reads body
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
lua_need_request_body off;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local reader, err = httpc:get_client_body_reader(8192)
|
||||
|
||||
repeat
|
||||
local chunk, err = reader()
|
||||
if chunk then
|
||||
ngx.print(chunk)
|
||||
end
|
||||
until chunk == nil
|
||||
|
||||
';
|
||||
}
|
||||
|
||||
--- request
|
||||
POST /a
|
||||
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
|
||||
--- response_body: foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
=== TEST 7: Request reader correctly reads body in chunks
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
lua_need_request_body off;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local reader, err = httpc:get_client_body_reader(64)
|
||||
|
||||
local chunks = 0
|
||||
repeat
|
||||
chunks = chunks +1
|
||||
local chunk, err = reader()
|
||||
if chunk then
|
||||
ngx.print(chunk)
|
||||
end
|
||||
until chunk == nil
|
||||
ngx.say("\\n"..chunks)
|
||||
';
|
||||
}
|
||||
|
||||
--- request
|
||||
POST /a
|
||||
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
|
||||
--- response_body
|
||||
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
|
||||
3
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 8: Request reader passes into client
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
lua_need_request_body off;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local reader, err = httpc:get_client_body_reader(64)
|
||||
|
||||
local res, err = httpc:request{
|
||||
method = POST,
|
||||
path = "/b",
|
||||
body = reader,
|
||||
headers = ngx.req.get_headers(100, true),
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
ngx.say(body)
|
||||
httpc:close()
|
||||
|
||||
';
|
||||
}
|
||||
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.req.read_body()
|
||||
local body, err = ngx.req.get_body_data()
|
||||
ngx.print(body)
|
||||
';
|
||||
}
|
||||
|
||||
--- request
|
||||
POST /a
|
||||
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
|
||||
--- response_body
|
||||
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 9: Body reader is a function returning nil when no body is present.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
method = "HEAD",
|
||||
}
|
||||
|
||||
repeat
|
||||
local chunk = res.body_reader()
|
||||
until not chunk
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.exit(200)
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 10: Issue a notice (but do not error) if trying to read the request body in a subrequest
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
echo_location /b;
|
||||
}
|
||||
location = /b {
|
||||
lua_need_request_body off;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local reader, err = httpc:get_client_body_reader(8192)
|
||||
if not reader then
|
||||
ngx.log(ngx.NOTICE, err)
|
||||
return
|
||||
end
|
||||
|
||||
repeat
|
||||
local chunk, err = reader()
|
||||
if chunk then
|
||||
ngx.print(chunk)
|
||||
end
|
||||
until chunk == nil
|
||||
';
|
||||
}
|
||||
|
||||
--- request
|
||||
POST /a
|
||||
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
|
||||
--- response_body:
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
--- error_log
|
||||
attempt to read the request body in a subrequest
|
||||
145
controllers/nginx/lua/vendor/lua-resty-http/t/06-simpleinterface.t
vendored
Normal file
145
controllers/nginx/lua/vendor/lua-resty-http/t/06-simpleinterface.t
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4) + 6;
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Simple URI interface
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local res, err = httpc:request_uri("http://127.0.0.1:"..ngx.var.server_port.."/b?a=1&b=2")
|
||||
|
||||
if not res then
|
||||
ngx.log(ngx.ERR, err)
|
||||
end
|
||||
ngx.status = res.status
|
||||
|
||||
ngx.header["X-Header-A"] = res.headers["X-Header-A"]
|
||||
ngx.header["X-Header-B"] = res.headers["X-Header-B"]
|
||||
|
||||
ngx.print(res.body)
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
for k,v in pairs(ngx.req.get_uri_args()) do
|
||||
ngx.header["X-Header-" .. string.upper(k)] = v
|
||||
end
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_headers
|
||||
X-Header-A: 1
|
||||
X-Header-B: 2
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: Simple URI interface HTTP 1.0
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local res, err = httpc:request_uri(
|
||||
"http://127.0.0.1:"..ngx.var.server_port.."/b?a=1&b=2", {
|
||||
}
|
||||
)
|
||||
|
||||
ngx.status = res.status
|
||||
|
||||
ngx.header["X-Header-A"] = res.headers["X-Header-A"]
|
||||
ngx.header["X-Header-B"] = res.headers["X-Header-B"]
|
||||
|
||||
ngx.print(res.body)
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
for k,v in pairs(ngx.req.get_uri_args()) do
|
||||
ngx.header["X-Header-" .. string.upper(k)] = v
|
||||
end
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_headers
|
||||
X-Header-A: 1
|
||||
X-Header-B: 2
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 3 Simple URI interface, params override
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local res, err = httpc:request_uri(
|
||||
"http://127.0.0.1:"..ngx.var.server_port.."/b?a=1&b=2", {
|
||||
path = "/c",
|
||||
query = {
|
||||
a = 2,
|
||||
b = 3,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ngx.status = res.status
|
||||
|
||||
ngx.header["X-Header-A"] = res.headers["X-Header-A"]
|
||||
ngx.header["X-Header-B"] = res.headers["X-Header-B"]
|
||||
|
||||
ngx.print(res.body)
|
||||
';
|
||||
}
|
||||
location = /c {
|
||||
content_by_lua '
|
||||
for k,v in pairs(ngx.req.get_uri_args()) do
|
||||
ngx.header["X-Header-" .. string.upper(k)] = v
|
||||
end
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_headers
|
||||
X-Header-A: 2
|
||||
X-Header-B: 3
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
182
controllers/nginx/lua/vendor/lua-resty-http/t/07-keepalive.t
vendored
Normal file
182
controllers/nginx/lua/vendor/lua-resty-http/t/07-keepalive.t
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1 Simple interface, Connection: Keep-alive. Test the connection is reused.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local res, err = httpc:request_uri(
|
||||
"http://127.0.0.1:"..ngx.var.server_port.."/b", {
|
||||
}
|
||||
)
|
||||
|
||||
ngx.say(res.headers["Connection"])
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
keep-alive
|
||||
1
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2 Simple interface, Connection: close, test we don't try to keepalive, but also that subsequent connections can keepalive.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local res, err = httpc:request_uri(
|
||||
"http://127.0.0.1:"..ngx.var.server_port.."/b", {
|
||||
version = 1.0,
|
||||
headers = {
|
||||
["Connection"] = "close",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
|
||||
httpc:set_keepalive()
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
0
|
||||
1
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 3 Generic interface, Connection: Keep-alive. Test the connection is reused.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
|
||||
ngx.say(res.headers["Connection"])
|
||||
ngx.say(httpc:set_keepalive())
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
keep-alive
|
||||
1
|
||||
1
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 4 Generic interface, Connection: Close. Test we don't try to keepalive, but also that subsequent connections can keepalive.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
version = 1.0,
|
||||
headers = {
|
||||
["Connection"] = "Close",
|
||||
},
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
local body = res:read_body()
|
||||
|
||||
ngx.say(res.headers["Connection"])
|
||||
local r, e = httpc:set_keepalive()
|
||||
ngx.say(r)
|
||||
ngx.say(e)
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
|
||||
httpc:set_keepalive()
|
||||
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
ngx.say(httpc:get_reused_times())
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
close
|
||||
2
|
||||
connection must be closed
|
||||
0
|
||||
1
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
143
controllers/nginx/lua/vendor/lua-resty-http/t/08-pipeline.t
vendored
Normal file
143
controllers/nginx/lua/vendor/lua-resty-http/t/08-pipeline.t
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1 Test that pipelined reqests can be read correctly.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local responses = httpc:request_pipeline{
|
||||
{
|
||||
path = "/b",
|
||||
},
|
||||
{
|
||||
path = "/c",
|
||||
},
|
||||
{
|
||||
path = "/d",
|
||||
}
|
||||
}
|
||||
|
||||
for i,r in ipairs(responses) do
|
||||
if r.status then
|
||||
ngx.say(r.status)
|
||||
ngx.say(r.headers["X-Res"])
|
||||
ngx.say(r:read_body())
|
||||
end
|
||||
end
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.status = 200
|
||||
ngx.header["X-Res"] = "B"
|
||||
ngx.print("B")
|
||||
';
|
||||
}
|
||||
location = /c {
|
||||
content_by_lua '
|
||||
ngx.status = 404
|
||||
ngx.header["X-Res"] = "C"
|
||||
ngx.print("C")
|
||||
';
|
||||
}
|
||||
location = /d {
|
||||
content_by_lua '
|
||||
ngx.status = 200
|
||||
ngx.header["X-Res"] = "D"
|
||||
ngx.print("D")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
200
|
||||
B
|
||||
B
|
||||
404
|
||||
C
|
||||
C
|
||||
200
|
||||
D
|
||||
D
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: Test we can handle timeouts on reading the pipelined requests.
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
httpc:set_timeout(1)
|
||||
|
||||
local responses = httpc:request_pipeline{
|
||||
{
|
||||
path = "/b",
|
||||
},
|
||||
{
|
||||
path = "/c",
|
||||
},
|
||||
}
|
||||
|
||||
for i,r in ipairs(responses) do
|
||||
if r.status then
|
||||
ngx.say(r.status)
|
||||
ngx.say(r.headers["X-Res"])
|
||||
ngx.say(r:read_body())
|
||||
end
|
||||
end
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.status = 200
|
||||
ngx.header["X-Res"] = "B"
|
||||
ngx.print("B")
|
||||
';
|
||||
}
|
||||
location = /c {
|
||||
content_by_lua '
|
||||
ngx.status = 404
|
||||
ngx.header["X-Res"] = "C"
|
||||
ngx.sleep(1)
|
||||
ngx.print("C")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
200
|
||||
B
|
||||
B
|
||||
--- no_error_log
|
||||
[warn]
|
||||
--- error_log eval
|
||||
[qr/timeout/]
|
||||
59
controllers/nginx/lua/vendor/lua-resty-http/t/09-ssl.t
vendored
Normal file
59
controllers/nginx/lua/vendor/lua-resty-http/t/09-ssl.t
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: parse_uri returns port 443 for https URIs
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local parsed = httpc:parse_uri("https://www.google.com/foobar")
|
||||
ngx.say(parsed[3])
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
443
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
=== TEST 2: parse_uri returns port 80 for http URIs
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
local parsed = httpc:parse_uri("http://www.google.com/foobar")
|
||||
ngx.say(parsed[3])
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
80
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
57
controllers/nginx/lua/vendor/lua-resty-http/t/10-clientbodyreader.t
vendored
Normal file
57
controllers/nginx/lua/vendor/lua-resty-http/t/10-clientbodyreader.t
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Issue a notice (but do not error) if trying to read the request body in a subrequest
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
echo_location /b;
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/c",
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
if not res then
|
||||
ngx.say(err)
|
||||
end
|
||||
ngx.print(res:read_body())
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location /c {
|
||||
echo "OK";
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
152
controllers/nginx/lua/vendor/lua-resty-http/t/11-proxy.t
vendored
Normal file
152
controllers/nginx/lua/vendor/lua-resty-http/t/11-proxy.t
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 5);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Proxy GET request and response
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a_prx {
|
||||
rewrite ^(.*)_prx$ $1 break;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
httpc:proxy_response(httpc:proxy_request())
|
||||
httpc:set_keepalive()
|
||||
';
|
||||
}
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
ngx.status = 200
|
||||
ngx.header["X-Test"] = "foo"
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a_prx
|
||||
--- response_body
|
||||
OK
|
||||
--- response_headers
|
||||
X-Test: foo
|
||||
--- error_code: 200
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: Proxy POST request and response
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a_prx {
|
||||
rewrite ^(.*)_prx$ $1 break;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
httpc:proxy_response(httpc:proxy_request())
|
||||
httpc:set_keepalive()
|
||||
';
|
||||
}
|
||||
location = /a {
|
||||
lua_need_request_body on;
|
||||
content_by_lua '
|
||||
ngx.status = 404
|
||||
ngx.header["X-Test"] = "foo"
|
||||
local args, err = ngx.req.get_post_args()
|
||||
ngx.say(args["foo"])
|
||||
ngx.say(args["hello"])
|
||||
';
|
||||
}
|
||||
--- request
|
||||
POST /a_prx
|
||||
foo=bar&hello=world
|
||||
--- response_body
|
||||
bar
|
||||
world
|
||||
--- response_headers
|
||||
X-Test: foo
|
||||
--- error_code: 404
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 3: Proxy multiple headers
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a_prx {
|
||||
rewrite ^(.*)_prx$ $1 break;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
httpc:proxy_response(httpc:proxy_request())
|
||||
httpc:set_keepalive()
|
||||
';
|
||||
}
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
ngx.status = 200
|
||||
ngx.header["Set-Cookie"] = { "cookie1", "cookie2" }
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a_prx
|
||||
--- response_body
|
||||
OK
|
||||
--- raw_response_headers_like: .*Set-Cookie: cookie1\r\nSet-Cookie: cookie2\r\n
|
||||
--- error_code: 200
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 4: Proxy still works with spaces in URI
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = "/a_ b_prx" {
|
||||
rewrite ^(.*)_prx$ $1 break;
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
httpc:proxy_response(httpc:proxy_request())
|
||||
httpc:set_keepalive()
|
||||
';
|
||||
}
|
||||
location = "/a_ b" {
|
||||
content_by_lua '
|
||||
ngx.status = 200
|
||||
ngx.header["X-Test"] = "foo"
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a_%20b_prx
|
||||
--- response_body
|
||||
OK
|
||||
--- response_headers
|
||||
X-Test: foo
|
||||
--- error_code: 200
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
160
controllers/nginx/lua/vendor/lua-resty-http/t/12-case_insensitive_headers.t
vendored
Normal file
160
controllers/nginx/lua/vendor/lua-resty-http/t/12-case_insensitive_headers.t
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 4);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: Test header normalisation
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http_headers = require "resty.http_headers"
|
||||
|
||||
local headers = http_headers.new()
|
||||
|
||||
headers.x_a_header = "a"
|
||||
headers["x-b-header"] = "b"
|
||||
headers["X-C-Header"] = "c"
|
||||
headers["X_d-HEAder"] = "d"
|
||||
|
||||
ngx.say(headers["X-A-Header"])
|
||||
ngx.say(headers.x_b_header)
|
||||
|
||||
for k,v in pairs(headers) do
|
||||
ngx.say(k, ": ", v)
|
||||
end
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
a
|
||||
b
|
||||
x-b-header: b
|
||||
x-a-header: a
|
||||
X-d-HEAder: d
|
||||
X-C-Header: c
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 2: Test headers can be accessed in all cases
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b"
|
||||
}
|
||||
|
||||
ngx.status = res.status
|
||||
ngx.say(res.headers["X-Foo-Header"])
|
||||
ngx.say(res.headers["x-fOo-heaDeR"])
|
||||
ngx.say(res.headers.x_foo_header)
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.header["X-Foo-Header"] = "bar"
|
||||
ngx.say("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
bar
|
||||
bar
|
||||
bar
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
|
||||
|
||||
=== TEST 3: Test request headers are normalised
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
httpc:connect("127.0.0.1", ngx.var.server_port)
|
||||
|
||||
local res, err = httpc:request{
|
||||
path = "/b",
|
||||
headers = {
|
||||
["uSeR-AgENT"] = "test_user_agent",
|
||||
x_foo = "bar",
|
||||
},
|
||||
}
|
||||
|
||||
ngx.status = res.status
|
||||
ngx.print(res:read_body())
|
||||
|
||||
httpc:close()
|
||||
';
|
||||
}
|
||||
location = /b {
|
||||
content_by_lua '
|
||||
ngx.say(ngx.req.get_headers()["User-Agent"])
|
||||
ngx.say(ngx.req.get_headers()["X-Foo"])
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_body
|
||||
test_user_agent
|
||||
bar
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
=== TEST 4: Test that headers remain unique
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location = /a {
|
||||
content_by_lua '
|
||||
local http_headers = require "resty.http_headers"
|
||||
|
||||
local headers = http_headers.new()
|
||||
|
||||
headers["x-a-header"] = "a"
|
||||
headers["X-A-HEAder"] = "b"
|
||||
|
||||
for k,v in pairs(headers) do
|
||||
ngx.log(ngx.DEBUG, k, ": ", v)
|
||||
ngx.header[k] = v
|
||||
end
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /a
|
||||
--- response_headers
|
||||
x-a-header: b
|
||||
--- no_error_log
|
||||
[error]
|
||||
[warn]
|
||||
[warn]
|
||||
52
controllers/nginx/lua/vendor/lua-resty-http/t/13-default-path.t
vendored
Normal file
52
controllers/nginx/lua/vendor/lua-resty-http/t/13-default-path.t
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# vim:set ft= ts=4 sw=4 et:
|
||||
|
||||
use Test::Nginx::Socket;
|
||||
use Cwd qw(cwd);
|
||||
|
||||
plan tests => repeat_each() * (blocks() * 3);
|
||||
|
||||
my $pwd = cwd();
|
||||
|
||||
our $HttpConfig = qq{
|
||||
lua_package_path "$pwd/lib/?.lua;;";
|
||||
error_log logs/error.log debug;
|
||||
};
|
||||
|
||||
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
|
||||
|
||||
no_long_string();
|
||||
#no_diff();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
=== TEST 1: request_uri (check the default path)
|
||||
--- http_config eval: $::HttpConfig
|
||||
--- config
|
||||
location /lua {
|
||||
content_by_lua '
|
||||
local http = require "resty.http"
|
||||
local httpc = http.new()
|
||||
|
||||
local res, err = httpc:request_uri("http://127.0.0.1:"..ngx.var.server_port)
|
||||
|
||||
if res and 200 == res.status then
|
||||
ngx.say("OK")
|
||||
else
|
||||
ngx.say("FAIL")
|
||||
end
|
||||
';
|
||||
}
|
||||
|
||||
location =/ {
|
||||
content_by_lua '
|
||||
ngx.print("OK")
|
||||
';
|
||||
}
|
||||
--- request
|
||||
GET /lua
|
||||
--- response_body
|
||||
OK
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
63
controllers/nginx/lua/vendor/lua-resty-http/util/lua-releng
vendored
Executable file
63
controllers/nginx/lua/vendor/lua-resty-http/util/lua-releng
vendored
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub file_contains ($$);
|
||||
|
||||
my $version;
|
||||
for my $file (map glob, qw{ *.lua lib/*.lua lib/*/*.lua lib/*/*/*.lua lib/*/*/*/*.lua lib/*/*/*/*/*.lua }) {
|
||||
# Check the sanity of each .lua file
|
||||
open my $in, $file or
|
||||
die "ERROR: Can't open $file for reading: $!\n";
|
||||
my $found_ver;
|
||||
while (<$in>) {
|
||||
my ($ver, $skipping);
|
||||
if (/(?x) (?:_VERSION) \s* = .*? ([\d\.]*\d+) (.*? SKIP)?/) {
|
||||
my $orig_ver = $ver = $1;
|
||||
$found_ver = 1;
|
||||
# $skipping = $2;
|
||||
$ver =~ s{^(\d+)\.(\d{3})(\d{3})$}{join '.', int($1), int($2), int($3)}e;
|
||||
warn "$file: $orig_ver ($ver)\n";
|
||||
|
||||
} elsif (/(?x) (?:_VERSION) \s* = \s* ([a-zA-Z_]\S*)/) {
|
||||
warn "$file: $1\n";
|
||||
$found_ver = 1;
|
||||
last;
|
||||
}
|
||||
|
||||
if ($ver and $version and !$skipping) {
|
||||
if ($version ne $ver) {
|
||||
# die "$file: $ver != $version\n";
|
||||
}
|
||||
} elsif ($ver and !$version) {
|
||||
$version = $ver;
|
||||
}
|
||||
}
|
||||
if (!$found_ver) {
|
||||
warn "WARNING: No \"_VERSION\" or \"version\" field found in `$file`.\n";
|
||||
}
|
||||
close $in;
|
||||
|
||||
print "Checking use of Lua global variables in file $file ...\n";
|
||||
system("luac -p -l $file | grep ETGLOBAL | grep -vE 'require|type|tostring|error|ngx|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|select|rawset|rawget|debug'");
|
||||
#file_contains($file, "attempt to write to undeclared variable");
|
||||
system("grep -H -n -E --color '.{120}' $file");
|
||||
}
|
||||
|
||||
sub file_contains ($$) {
|
||||
my ($file, $regex) = @_;
|
||||
open my $in, $file
|
||||
or die "Cannot open $file fo reading: $!\n";
|
||||
my $content = do { local $/; <$in> };
|
||||
close $in;
|
||||
#print "$content";
|
||||
return scalar ($content =~ /$regex/);
|
||||
}
|
||||
|
||||
if (-d 't') {
|
||||
for my $file (map glob, qw{ t/*.t t/*/*.t t/*/*/*.t }) {
|
||||
system(qq{grep -H -n --color -E '\\--- ?(ONLY|LAST)' $file});
|
||||
}
|
||||
}
|
||||
|
||||
145
controllers/nginx/main.go
Normal file
145
controllers/nginx/main.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx/nginx"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/healthz"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
healthPort = 10249
|
||||
)
|
||||
|
||||
var (
|
||||
flags = pflag.NewFlagSet("", pflag.ExitOnError)
|
||||
|
||||
defaultSvc = flags.String("default-backend-service", "",
|
||||
`Service used to serve a 404 page for the default backend. Takes the form
|
||||
namespace/name. The controller uses the first node port of this Service for
|
||||
the default backend.`)
|
||||
|
||||
nxgConfigMap = flags.String("nginx-configmap", "",
|
||||
`Name of the ConfigMap that containes the custom nginx configuration to use`)
|
||||
|
||||
tcpConfigMapName = flags.String("tcp-services-configmap", "",
|
||||
`Name of the ConfigMap that containes the definition of the TCP services to expose.
|
||||
The key in the map indicates the external port to be used. The value is the name of the
|
||||
service with the format namespace/serviceName and the port of the service could be a number of the
|
||||
name of the port.
|
||||
The ports 80 and 443 are not allowed as external ports. This ports are reserved for nginx`)
|
||||
|
||||
resyncPeriod = flags.Duration("sync-period", 30*time.Second,
|
||||
`Relist and confirm cloud resources this often.`)
|
||||
|
||||
watchNamespace = flags.String("watch-namespace", api.NamespaceAll,
|
||||
`Namespace to watch for Ingress. Default is to watch all namespaces`)
|
||||
|
||||
healthzPort = flags.Int("healthz-port", healthPort, "port for healthz endpoint.")
|
||||
|
||||
buildCfg = flags.Bool("dump-nginx—configuration", false, `Returns a ConfigMap with the default nginx conguration.
|
||||
This can be used as a guide to create a custom configuration.`)
|
||||
|
||||
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
flags.AddGoFlagSet(flag.CommandLine)
|
||||
flags.Parse(os.Args)
|
||||
|
||||
if *buildCfg {
|
||||
fmt.Printf("Example of ConfigMap to customize NGINX configuration:\n%v", nginx.ConfigMapAsString())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *defaultSvc == "" {
|
||||
glog.Fatalf("Please specify --default-backend")
|
||||
}
|
||||
|
||||
kubeClient, err := unversioned.NewInCluster()
|
||||
if err != nil {
|
||||
glog.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
lbInfo, err := getLBDetails(kubeClient)
|
||||
if err != nil {
|
||||
glog.Fatalf("unexpected error getting runtime information: %v", err)
|
||||
}
|
||||
|
||||
err = isValidService(kubeClient, *defaultSvc)
|
||||
if err != nil {
|
||||
glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
|
||||
}
|
||||
|
||||
lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, lbInfo)
|
||||
if err != nil {
|
||||
glog.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
go registerHandlers(lbc)
|
||||
|
||||
lbc.Run()
|
||||
|
||||
for {
|
||||
glog.Infof("Handled quit, awaiting pod deletion")
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// lbInfo contains runtime information about the pod
|
||||
type lbInfo struct {
|
||||
ObjectName string
|
||||
DeployType runtime.Object
|
||||
Podname string
|
||||
PodIP string
|
||||
PodNamespace string
|
||||
}
|
||||
|
||||
func registerHandlers(lbc *loadBalancerController) {
|
||||
mux := http.NewServeMux()
|
||||
healthz.InstallHandler(mux, lbc.nginx)
|
||||
|
||||
http.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||
lbc.Stop()
|
||||
})
|
||||
|
||||
if *profiling {
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%v", *healthzPort),
|
||||
Handler: mux,
|
||||
}
|
||||
glog.Fatal(server.ListenAndServe())
|
||||
}
|
||||
342
controllers/nginx/nginx.tmpl
Normal file
342
controllers/nginx/nginx.tmpl
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
{{ $cfg := .cfg }}
|
||||
daemon off;
|
||||
|
||||
worker_processes {{ $cfg.workerProcesses }};
|
||||
|
||||
pid /run/nginx.pid;
|
||||
|
||||
worker_rlimit_nofile 131072;
|
||||
|
||||
pcre_jit on;
|
||||
|
||||
events {
|
||||
multi_accept on;
|
||||
worker_connections {{ $cfg.maxWorkerConnections }};
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
{{ if $cfg.enableVtsStatus}}vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.vtsStatusZoneSize }};{{ end }}
|
||||
|
||||
# lus sectrion to return proper error codes when custom pages are used
|
||||
lua_package_path '.?.lua;./etc/nginx/lua/?.lua;/etc/nginx/lua/vendor/lua-resty-http/lib/?.lua;';
|
||||
init_by_lua_block {
|
||||
require("error_page")
|
||||
}
|
||||
|
||||
sendfile on;
|
||||
aio threads;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
|
||||
log_subrequest on;
|
||||
|
||||
reset_timedout_connection on;
|
||||
|
||||
keepalive_timeout {{ $cfg.keepAlive }}s;
|
||||
|
||||
types_hash_max_size 2048;
|
||||
server_names_hash_max_size {{ $cfg.serverNameHashMaxSize }};
|
||||
server_names_hash_bucket_size {{ $cfg.serverNameHashBucketSize }};
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
{{ if $cfg.useGzip }}
|
||||
gzip on;
|
||||
gzip_comp_level 5;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types {{ $cfg.gzipTypes }};
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
{{ end }}
|
||||
|
||||
client_max_body_size "{{ $cfg.bodySize }}";
|
||||
|
||||
{{ if $cfg.useProxyProtocol }}
|
||||
set_real_ip_from {{ $cfg.proxyRealIpCidr }};
|
||||
real_ip_header proxy_protocol;
|
||||
{{ end }}
|
||||
|
||||
log_format upstreaminfo '{{ if $cfg.useProxyProtocol }}$proxy_protocol_addr{{ else }}$remote_addr{{ end }} - '
|
||||
'[$proxy_add_x_forwarded_for] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" '
|
||||
'$request_length $request_time $upstream_addr $upstream_response_length $upstream_response_time $upstream_status';
|
||||
|
||||
access_log /var/log/nginx/access.log upstreaminfo;
|
||||
error_log /var/log/nginx/error.log {{ $cfg.errorLogLevel }};
|
||||
|
||||
{{ if not (empty .defResolver) }}# Custom dns resolver.
|
||||
resolver {{ .defResolver }} valid=30s;
|
||||
{{ end }}
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# trust http_x_forwarded_proto headers correctly indicate ssl offloading
|
||||
map $http_x_forwarded_proto $pass_access_scheme {
|
||||
default $http_x_forwarded_proto;
|
||||
'' $scheme;
|
||||
}
|
||||
|
||||
map $http_x_forwarded_proto $pass_forwarded_for {
|
||||
default $http_x_forwarded_for;
|
||||
'' $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
map $pass_access_scheme $sts {
|
||||
'https' 'max-age={{ $cfg.htsMaxAge }}{{ if $cfg.htsIncludeSubdomains }}; includeSubDomains{{ end }}; preload';
|
||||
}
|
||||
|
||||
# Map a response error watching the header Content-Type
|
||||
map $http_accept $httpAccept {
|
||||
default html;
|
||||
application/json json;
|
||||
application/xml xml;
|
||||
text/plain text;
|
||||
}
|
||||
|
||||
map $httpAccept $httpReturnType {
|
||||
default text/html;
|
||||
json application/json;
|
||||
xml application/xml;
|
||||
text text/plain;
|
||||
}
|
||||
|
||||
server_name_in_redirect off;
|
||||
port_in_redirect off;
|
||||
|
||||
ssl_protocols {{ $cfg.sslProtocols }};
|
||||
|
||||
# turn on session caching to drastically improve performance
|
||||
{{ if $cfg.sslSessionCache }}
|
||||
ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.sslSessionCacheSize }};
|
||||
ssl_session_timeout {{ $cfg.sslSessionTimeout }};
|
||||
{{ end }}
|
||||
|
||||
# allow configuring ssl session tickets
|
||||
ssl_session_tickets {{ if $cfg.sslSessionTickets }}on{{ else }}off{{ end }};
|
||||
|
||||
# slightly reduce the time-to-first-byte
|
||||
ssl_buffer_size {{ $cfg.sslBufferSize }};
|
||||
|
||||
{{ if not (empty $cfg.sslCiphers) }}
|
||||
# allow configuring custom ssl ciphers
|
||||
ssl_ciphers '{{ $cfg.sslCiphers }}';
|
||||
ssl_prefer_server_ciphers on;
|
||||
{{ end }}
|
||||
|
||||
{{ if not (empty .sslDHParam) }}
|
||||
# allow custom DH file http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
||||
ssl_dhparam {{ .sslDHParam }};
|
||||
{{ end }}
|
||||
|
||||
# Custom error pages
|
||||
proxy_intercept_errors on;
|
||||
|
||||
error_page 403 = @custom_403;
|
||||
error_page 404 = @custom_404;
|
||||
error_page 405 = @custom_405;
|
||||
error_page 408 = @custom_408;
|
||||
error_page 413 = @custom_413;
|
||||
error_page 501 = @custom_501;
|
||||
error_page 502 = @custom_502;
|
||||
error_page 503 = @custom_503;
|
||||
error_page 504 = @custom_504;
|
||||
|
||||
# In case of errors try the next upstream server before returning an error
|
||||
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
|
||||
|
||||
server {
|
||||
listen 80 default_server{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
|
||||
|
||||
location / {
|
||||
return 200;
|
||||
}
|
||||
|
||||
location /nginx_status {
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
|
||||
access_log off;
|
||||
stub_status on;
|
||||
}
|
||||
|
||||
{{ template "CUSTOM_ERRORS" $cfg }}
|
||||
}
|
||||
|
||||
{{range $name, $upstream := .upstreams}}
|
||||
upstream {{$upstream.Name}} {
|
||||
least_conn;
|
||||
{{range $server := $upstream.Backends}}server {{$server.Address}}:{{$server.Port}};
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{ range $server := .servers }}
|
||||
server {
|
||||
listen 80;
|
||||
{{ if $server.SSL }}listen 443 ssl http2;
|
||||
ssl_certificate {{ $server.SSLCertificate }};
|
||||
ssl_certificate_key {{ $server.SSLCertificateKey }};{{ end }}
|
||||
{{ if $cfg.enableVtsStatus }}
|
||||
vhost_traffic_status_filter_by_set_key {{ $server.Name }} application::*;
|
||||
{{ end }}
|
||||
|
||||
server_name {{ $server.Name }};
|
||||
|
||||
{{ if $server.SSL }}
|
||||
if ($scheme = http) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
{{ end }}
|
||||
{{ range $location := $server.Locations }}
|
||||
location {{ $location.Path }} {
|
||||
proxy_set_header Host $host;
|
||||
|
||||
# Pass Real IP
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
# Allow websocket connections
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
proxy_set_header X-Forwarded-For $pass_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Server $host;
|
||||
proxy_set_header X-Forwarded-Proto $pass_access_scheme;
|
||||
|
||||
proxy_connect_timeout {{ $cfg.proxyConnectTimeout }}s;
|
||||
proxy_send_timeout {{ $cfg.proxySendTimeout }}s;
|
||||
proxy_read_timeout {{ $cfg.proxyReadTimeout }}s;
|
||||
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_pass http://{{ $location.Upstream.Name }};
|
||||
}
|
||||
{{ end }}
|
||||
{{ template "CUSTOM_ERRORS" $cfg }}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
# default server, including healthcheck
|
||||
server {
|
||||
listen 8080 default_server{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }} reuseport;
|
||||
|
||||
location /healthz {
|
||||
access_log off;
|
||||
return 200;
|
||||
}
|
||||
|
||||
location /health-check {
|
||||
access_log off;
|
||||
proxy_pass http://127.0.0.1:10249/healthz;
|
||||
}
|
||||
|
||||
location /nginx_status {
|
||||
{{ if $cfg.enableVtsStatus }}
|
||||
vhost_traffic_status_display;
|
||||
vhost_traffic_status_display_format html;
|
||||
{{ else }}
|
||||
access_log off;
|
||||
stub_status on;
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://upstream-default-backend;
|
||||
}
|
||||
{{ template "CUSTOM_ERRORS" $cfg }}
|
||||
}
|
||||
|
||||
# default server for services without endpoints
|
||||
server {
|
||||
listen 8181;
|
||||
|
||||
location / {
|
||||
content_by_lua_block {
|
||||
openURL(503)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# TCP services
|
||||
stream {
|
||||
{{ range $i, $tcpServer := .tcpUpstreams }}
|
||||
upstream tcp-{{ $tcpServer.Upstream.Name }} {
|
||||
{{ range $server := $tcpServer.Upstream.Backends }}server {{ $server.Address }}:{{ $server.Port }};
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
server {
|
||||
listen {{ $tcpServer.Path }};
|
||||
proxy_connect_timeout {{ $cfg.proxyConnectTimeout }};
|
||||
proxy_timeout {{ $cfg.proxyReadTimeout }};
|
||||
proxy_pass tcp-{{ $tcpServer.Upstream.Name }};
|
||||
}
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
{{/* definition of templates to avoid repetitions */}}
|
||||
{{ define "CUSTOM_ERRORS" }}
|
||||
location @custom_403 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(403)
|
||||
}
|
||||
}
|
||||
|
||||
location @custom_404 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(404)
|
||||
}
|
||||
}
|
||||
|
||||
location @custom_405 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(405)
|
||||
}
|
||||
}
|
||||
|
||||
location @custom_408 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(408)
|
||||
}
|
||||
}
|
||||
|
||||
location @custom_413 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(413)
|
||||
}
|
||||
}
|
||||
|
||||
location @custom_502 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(502)
|
||||
}
|
||||
}
|
||||
|
||||
location @custom_503 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(503)
|
||||
}
|
||||
}
|
||||
|
||||
location @custom_504 {
|
||||
internal;
|
||||
content_by_lua_block {
|
||||
openURL(504)
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
110
controllers/nginx/nginx/command.go
Normal file
110
controllers/nginx/nginx/command.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/healthz"
|
||||
)
|
||||
|
||||
// Start starts a nginx (master process) and waits. If the process ends
|
||||
// we need to kill the controller process and return the reason.
|
||||
func (ngx *Manager) Start() {
|
||||
glog.Info("Starting NGINX process...")
|
||||
cmd := exec.Command("nginx")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
glog.Errorf("nginx error: %v", err)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
glog.Errorf("nginx error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckAndReload verify if the nginx configuration changed and sends a reload
|
||||
//
|
||||
// the master process receives the signal to reload configuration, it checks
|
||||
// the syntax validity of the new configuration file and tries to apply the
|
||||
// configuration provided in it. If this is a success, the master process starts
|
||||
// new worker processes and sends messages to old worker processes, requesting them
|
||||
// to shut down. Otherwise, the master process rolls back the changes and continues
|
||||
// to work with the old configuration. Old worker processes, receiving a command to
|
||||
// shut down, stop accepting new connections and continue to service current requests
|
||||
// until all such requests are serviced. After that, the old worker processes exit.
|
||||
// http://nginx.org/en/docs/beginners_guide.html#control
|
||||
func (ngx *Manager) CheckAndReload(cfg nginxConfiguration, ingressCfg IngressConfig) {
|
||||
ngx.reloadRateLimiter.Accept()
|
||||
|
||||
ngx.reloadLock.Lock()
|
||||
defer ngx.reloadLock.Unlock()
|
||||
|
||||
newCfg, err := ngx.writeCfg(cfg, ingressCfg)
|
||||
|
||||
if err != nil {
|
||||
glog.Errorf("failed to write new nginx configuration. Avoiding reload: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if newCfg {
|
||||
if err := ngx.shellOut("nginx -s reload"); err == nil {
|
||||
glog.Info("change in configuration detected. Reloading...")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shellOut executes a command and returns its combined standard output and standard
|
||||
// error in case of an error in the execution
|
||||
func (ngx *Manager) shellOut(cmd string) error {
|
||||
out, err := exec.Command("sh", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
glog.Errorf("failed to execute %v: %v", cmd, string(out))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check to verify Manager implements HealthzChecker interface
|
||||
var _ healthz.HealthzChecker = Manager{}
|
||||
|
||||
// Name returns the healthcheck name
|
||||
func (ngx Manager) Name() string {
|
||||
return "NGINX"
|
||||
}
|
||||
|
||||
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
|
||||
func (ngx Manager) Check(_ *http.Request) error {
|
||||
res, err := http.Get("http://127.0.0.1:8080/healthz")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("NGINX is unhealthy")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
315
controllers/nginx/nginx/main.go
Normal file
315
controllers/nginx/nginx/main.go
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
||||
// Sets the maximum allowed size of the client request body
|
||||
bodySize = "1m"
|
||||
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
||||
// Configures logging level [debug | info | notice | warn | error | crit | alert | emerg]
|
||||
// Log levels above are listed in the order of increasing severity
|
||||
errorLevel = "notice"
|
||||
|
||||
// HTTP Strict Transport Security (often abbreviated as HSTS) is a security feature (HTTP header)
|
||||
// that tell browsers that it should only be communicated with using HTTPS, instead of using HTTP.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
|
||||
// max-age is the time, in seconds, that the browser should remember that this site is only to be accessed using HTTPS.
|
||||
htsMaxAge = "15724800"
|
||||
|
||||
// If UseProxyProtocol is enabled defIPCIDR defines the default the IP/network address of your external load balancer
|
||||
defIPCIDR = "0.0.0.0/0"
|
||||
|
||||
gzipTypes = "application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component"
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
||||
sslBufferSize = "4k"
|
||||
|
||||
// Enabled ciphers list to enabled. The ciphers are specified in the format understood by the OpenSSL library
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
|
||||
sslCiphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
|
||||
|
||||
// SSL enabled protocols to use
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
sslProtocols = "TLSv1 TLSv1.1 TLSv1.2"
|
||||
|
||||
// Time during which a client may reuse the session parameters stored in a cache.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
sslSessionTimeout = "10m"
|
||||
|
||||
// Size of the SSL shared cache between all worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
sslSessionCacheSize = "10m"
|
||||
|
||||
// Base directory that contains the mounted secrets with SSL certificates, keys and
|
||||
sslDirectory = "/etc/nginx-ssl"
|
||||
)
|
||||
|
||||
type nginxConfiguration struct {
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
||||
// Sets the maximum allowed size of the client request body
|
||||
BodySize string `structs:"body-size,omitempty"`
|
||||
|
||||
// EnableVtsStatus allows the replacement of the default status page with a third party module named
|
||||
// nginx-module-vts - https://github.com/vozlt/nginx-module-vts
|
||||
// By default this is disabled
|
||||
EnableVtsStatus bool `structs:"enable-vts-status,omitempty"`
|
||||
|
||||
VtsStatusZoneSize string `structs:"vts-status-zone-size,omitempty"`
|
||||
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
||||
// Configures logging level [debug | info | notice | warn | error | crit | alert | emerg]
|
||||
// Log levels above are listed in the order of increasing severity
|
||||
ErrorLogLevel string `structs:"error-log-level,omitempty"`
|
||||
|
||||
// Enables or disables the header HTS in servers running SSL
|
||||
UseHTS bool `structs:"use-hts,omitempty"`
|
||||
|
||||
// Enables or disables the use of HTS in all the subdomains of the servername
|
||||
HTSIncludeSubdomains bool `structs:"hts-include-subdomains,omitempty"`
|
||||
|
||||
// HTTP Strict Transport Security (often abbreviated as HSTS) is a security feature (HTTP header)
|
||||
// that tell browsers that it should only be communicated with using HTTPS, instead of using HTTP.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
|
||||
// max-age is the time, in seconds, that the browser should remember that this site is only to be
|
||||
// accessed using HTTPS.
|
||||
HTSMaxAge string `structs:"hts-max-age,omitempty"`
|
||||
|
||||
// Time during which a keep-alive client connection will stay open on the server side.
|
||||
// The zero value disables keep-alive client connections
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
|
||||
KeepAlive int `structs:"keep-alive,omitempty"`
|
||||
|
||||
// Maximum number of simultaneous connections that can be opened by each worker process
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_connections
|
||||
MaxWorkerConnections int `structs:"max-worker-connections,omitempty"`
|
||||
|
||||
// Defines a timeout for establishing a connection with a proxied server.
|
||||
// It should be noted that this timeout cannot usually exceed 75 seconds.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_connect_timeout
|
||||
ProxyConnectTimeout int `structs:"proxy-connect-timeout,omitempty"`
|
||||
|
||||
// If UseProxyProtocol is enabled ProxyRealIPCIDR defines the default the IP/network address
|
||||
// of your external load balancer
|
||||
ProxyRealIPCIDR string `structs:"proxy-real-ip-cidr,omitempty"`
|
||||
|
||||
// Timeout in seconds for reading a response from the proxied server. The timeout is set only between
|
||||
// two successive read operations, not for the transmission of the whole response
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout
|
||||
ProxyReadTimeout int `structs:"proxy-read-timeout,omitempty"`
|
||||
|
||||
// Timeout in seconds for transmitting a request to the proxied server. The timeout is set only between
|
||||
// two successive write operations, not for the transmission of the whole request.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_send_timeout
|
||||
ProxySendTimeout int `structs:"proxy-send-timeout,omitempty"`
|
||||
|
||||
// Configures name servers used to resolve names of upstream servers into addresses
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver
|
||||
Resolver string `structs:"resolver,omitempty"`
|
||||
|
||||
// Maximum size of the server names hash tables used in server names, map directive’s values,
|
||||
// MIME types, names of request header strings, etcd.
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_max_size
|
||||
ServerNameHashMaxSize int `structs:"server-name-hash-max-size,omitempty"`
|
||||
|
||||
// Size of the bucker for the server names hash tables
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_bucket_size
|
||||
ServerNameHashBucketSize int `structs:"server-name-hash-bucket-size,omitempty"`
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
||||
SSLBufferSize string `structs:"ssl-buffer-size,omitempty"`
|
||||
|
||||
// Enabled ciphers list to enabled. The ciphers are specified in the format understood by
|
||||
// the OpenSSL library
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
|
||||
SSLCiphers string `structs:"ssl-ciphers,omitempty"`
|
||||
|
||||
// Base64 string that contains Diffie-Hellman key to help with "Perfect Forward Secrecy"
|
||||
// https://www.openssl.org/docs/manmaster/apps/dhparam.html
|
||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
||||
SSLDHParam string `structs:"ssl-dh-param,omitempty"`
|
||||
|
||||
// SSL enabled protocols to use
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
SSLProtocols string `structs:"ssl-protocols,omitempty"`
|
||||
|
||||
// Enables or disables the use of shared SSL cache among worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCache bool `structs:"ssl-session-cache,omitempty"`
|
||||
|
||||
// Size of the SSL shared cache between all worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCacheSize string `structs:"ssl-session-cache-size,omitempty"`
|
||||
|
||||
// Enables or disables session resumption through TLS session tickets.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_tickets
|
||||
SSLSessionTickets bool `structs:"ssl-session-tickets,omitempty"`
|
||||
|
||||
// Time during which a client may reuse the session parameters stored in a cache.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
SSLSessionTimeout string `structs:"ssl-session-timeout,omitempty"`
|
||||
|
||||
// Enables or disables the use of the PROXY protocol to receive client connection
|
||||
// (real IP address) information passed through proxy servers and load balancers
|
||||
// such as HAproxy and Amazon Elastic Load Balancer (ELB).
|
||||
// https://www.nginx.com/resources/admin-guide/proxy-protocol/
|
||||
UseProxyProtocol bool `structs:"use-proxy-protocol,omitempty"`
|
||||
|
||||
// Enables or disables the use of the nginx module that compresses responses using the "gzip" method
|
||||
// http://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
||||
UseGzip bool `structs:"use-gzip,omitempty"`
|
||||
|
||||
// MIME types in addition to "text/html" to compress. The special value “*” matches any MIME type.
|
||||
// Responses with the “text/html” type are always compressed if UseGzip is enabled
|
||||
GzipTypes string `structs:"gzip-types,omitempty"`
|
||||
|
||||
// Defines the number of worker processes. By default auto means number of available CPU cores
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
|
||||
WorkerProcesses string `structs:"worker-processes,omitempty"`
|
||||
}
|
||||
|
||||
// Manager ...
|
||||
type Manager struct {
|
||||
ConfigFile string
|
||||
|
||||
defCfg nginxConfiguration
|
||||
|
||||
defResolver string
|
||||
|
||||
sslDHParam string
|
||||
|
||||
reloadRateLimiter util.RateLimiter
|
||||
|
||||
// template loaded ready to be used to generate the nginx configuration file
|
||||
template *template.Template
|
||||
|
||||
reloadLock *sync.Mutex
|
||||
}
|
||||
|
||||
// defaultConfiguration returns the default configuration contained
|
||||
// in the file default-conf.json
|
||||
func newDefaultNginxCfg() nginxConfiguration {
|
||||
cfg := nginxConfiguration{
|
||||
BodySize: bodySize,
|
||||
ErrorLogLevel: errorLevel,
|
||||
UseHTS: true,
|
||||
HTSIncludeSubdomains: true,
|
||||
HTSMaxAge: htsMaxAge,
|
||||
GzipTypes: gzipTypes,
|
||||
KeepAlive: 75,
|
||||
MaxWorkerConnections: 16384,
|
||||
ProxyConnectTimeout: 5,
|
||||
ProxyRealIPCIDR: defIPCIDR,
|
||||
ProxyReadTimeout: 60,
|
||||
ProxySendTimeout: 60,
|
||||
ServerNameHashMaxSize: 512,
|
||||
ServerNameHashBucketSize: 64,
|
||||
SSLBufferSize: sslBufferSize,
|
||||
SSLCiphers: sslCiphers,
|
||||
SSLProtocols: sslProtocols,
|
||||
SSLSessionCache: true,
|
||||
SSLSessionCacheSize: sslSessionCacheSize,
|
||||
SSLSessionTickets: true,
|
||||
SSLSessionTimeout: sslSessionTimeout,
|
||||
UseProxyProtocol: false,
|
||||
UseGzip: true,
|
||||
WorkerProcesses: strconv.Itoa(runtime.NumCPU()),
|
||||
VtsStatusZoneSize: "10m",
|
||||
}
|
||||
|
||||
if glog.V(5) {
|
||||
cfg.ErrorLogLevel = "debug"
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// NewManager ...
|
||||
func NewManager(kubeClient *client.Client) *Manager {
|
||||
ngx := &Manager{
|
||||
ConfigFile: "/etc/nginx/nginx.conf",
|
||||
defCfg: newDefaultNginxCfg(),
|
||||
defResolver: strings.Join(getDNSServers(), " "),
|
||||
reloadLock: &sync.Mutex{},
|
||||
reloadRateLimiter: util.NewTokenBucketRateLimiter(0.1, 1),
|
||||
}
|
||||
|
||||
ngx.createCertsDir(sslDirectory)
|
||||
|
||||
ngx.sslDHParam = ngx.SearchDHParamFile(sslDirectory)
|
||||
|
||||
ngx.loadTemplate()
|
||||
|
||||
return ngx
|
||||
}
|
||||
|
||||
func (nginx *Manager) createCertsDir(base string) {
|
||||
if err := os.Mkdir(base, os.ModeDir); err != nil {
|
||||
glog.Fatalf("Couldn't create directory %v: %v", base, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigMapAsString returns a ConfigMap with the default NGINX
|
||||
// configuration to be used a guide to provide a custom configuration
|
||||
func ConfigMapAsString() string {
|
||||
cfg := &api.ConfigMap{}
|
||||
cfg.Name = "custom-name"
|
||||
cfg.Namespace = "a-valid-namespace"
|
||||
cfg.Data = make(map[string]string)
|
||||
|
||||
data := structs.Map(newDefaultNginxCfg())
|
||||
for k, v := range data {
|
||||
cfg.Data[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
out, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
glog.Warningf("Unexpected error creating default configuration: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
108
controllers/nginx/nginx/nginx.go
Normal file
108
controllers/nginx/nginx/nginx.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package nginx
|
||||
|
||||
// IngressConfig describes an NGINX configuration
|
||||
type IngressConfig struct {
|
||||
Upstreams []*Upstream
|
||||
Servers []*Server
|
||||
TCPUpstreams []*Location
|
||||
}
|
||||
|
||||
// Upstream describes an NGINX upstream
|
||||
type Upstream struct {
|
||||
Name string
|
||||
Backends []UpstreamServer
|
||||
}
|
||||
|
||||
// UpstreamByNameServers sorts upstreams by name
|
||||
type UpstreamByNameServers []*Upstream
|
||||
|
||||
func (c UpstreamByNameServers) Len() int { return len(c) }
|
||||
func (c UpstreamByNameServers) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c UpstreamByNameServers) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
}
|
||||
|
||||
// UpstreamServer describes a server in an NGINX upstream
|
||||
type UpstreamServer struct {
|
||||
Address string
|
||||
Port string
|
||||
}
|
||||
|
||||
// UpstreamServerByAddrPort sorts upstream servers by address and port
|
||||
type UpstreamServerByAddrPort []UpstreamServer
|
||||
|
||||
func (c UpstreamServerByAddrPort) Len() int { return len(c) }
|
||||
func (c UpstreamServerByAddrPort) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c UpstreamServerByAddrPort) Less(i, j int) bool {
|
||||
iName := c[i].Address
|
||||
jName := c[j].Address
|
||||
if iName != jName {
|
||||
return iName < jName
|
||||
}
|
||||
|
||||
iU := c[i].Port
|
||||
jU := c[j].Port
|
||||
return iU < jU
|
||||
}
|
||||
|
||||
// Server describes an NGINX server
|
||||
type Server struct {
|
||||
Name string
|
||||
Locations []*Location
|
||||
SSL bool
|
||||
SSLCertificate string
|
||||
SSLCertificateKey string
|
||||
}
|
||||
|
||||
// ServerByName sorts server by name
|
||||
type ServerByName []*Server
|
||||
|
||||
func (c ServerByName) Len() int { return len(c) }
|
||||
func (c ServerByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ServerByName) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
}
|
||||
|
||||
// Location describes an NGINX location
|
||||
type Location struct {
|
||||
Path string
|
||||
Upstream Upstream
|
||||
}
|
||||
|
||||
// LocationByPath sorts location by path
|
||||
type LocationByPath []*Location
|
||||
|
||||
func (c LocationByPath) Len() int { return len(c) }
|
||||
func (c LocationByPath) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c LocationByPath) Less(i, j int) bool {
|
||||
return c[i].Path < c[j].Path
|
||||
}
|
||||
|
||||
// NewDefaultServer return an UpstreamServer to be use as default server that returns 503.
|
||||
func NewDefaultServer() UpstreamServer {
|
||||
return UpstreamServer{Address: "127.0.0.1", Port: "8181"}
|
||||
}
|
||||
|
||||
// NewUpstream creates an upstream without servers.
|
||||
func NewUpstream(name string) *Upstream {
|
||||
return &Upstream{
|
||||
Name: name,
|
||||
Backends: []UpstreamServer{},
|
||||
}
|
||||
}
|
||||
92
controllers/nginx/nginx/ssl.go
Normal file
92
controllers/nginx/nginx/ssl.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// AddOrUpdateCertAndKey creates a .pem file wth the cert and the key with the specified name
|
||||
func (nginx *Manager) AddOrUpdateCertAndKey(name string, cert string, key string) string {
|
||||
pemFileName := sslDirectory + "/" + name + ".pem"
|
||||
|
||||
pem, err := os.Create(pemFileName)
|
||||
if err != nil {
|
||||
glog.Fatalf("Couldn't create pem file %v: %v", pemFileName, err)
|
||||
}
|
||||
defer pem.Close()
|
||||
|
||||
_, err = pem.WriteString(fmt.Sprintf("%v\n%v", key, cert))
|
||||
if err != nil {
|
||||
glog.Fatalf("Couldn't write to pem file %v: %v", pemFileName, err)
|
||||
}
|
||||
|
||||
return pemFileName
|
||||
}
|
||||
|
||||
// CheckSSLCertificate checks if the certificate and key file are valid
|
||||
// returning the result of the validation and the list of hostnames
|
||||
// contained in the common name/s
|
||||
func (nginx *Manager) CheckSSLCertificate(pemFileName string) ([]string, error) {
|
||||
pemCerts, err := ioutil.ReadFile(pemFileName)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
var block *pem.Block
|
||||
block, _ = pem.Decode(pemCerts)
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
cn := []string{cert.Subject.CommonName}
|
||||
if len(cert.DNSNames) > 0 {
|
||||
cn = append(cn, cert.DNSNames...)
|
||||
}
|
||||
|
||||
glog.V(2).Infof("DNS %v %v\n", cn, len(cn))
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
// SearchDHParamFile iterates all the secrets mounted inside the /etc/nginx-ssl directory
|
||||
// in order to find a file with the name dhparam.pem. If such file exists it will
|
||||
// returns the path. If not it just returns an empty string
|
||||
func (nginx *Manager) SearchDHParamFile(baseDir string) string {
|
||||
files, _ := ioutil.ReadDir(baseDir)
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
dhPath := fmt.Sprintf("%v/%v/dhparam.pem", baseDir, file.Name())
|
||||
if _, err := os.Stat(dhPath); err == nil {
|
||||
glog.Infof("using file '%v' for parameter ssl_dhparam", dhPath)
|
||||
return dhPath
|
||||
}
|
||||
}
|
||||
|
||||
glog.Warning("no file dhparam.pem found in secrets")
|
||||
return ""
|
||||
}
|
||||
104
controllers/nginx/nginx/template.go
Normal file
104
controllers/nginx/nginx/template.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"text/template"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
var (
|
||||
camelRegexp = regexp.MustCompile("[0-9A-Za-z]+")
|
||||
|
||||
funcMap = template.FuncMap{
|
||||
"empty": func(input interface{}) bool {
|
||||
check, ok := input.(string)
|
||||
if ok {
|
||||
return len(check) == 0
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (ngx *Manager) loadTemplate() {
|
||||
tmpl, _ := template.New("nginx.tmpl").Funcs(funcMap).ParseFiles("./nginx.tmpl")
|
||||
ngx.template = tmpl
|
||||
}
|
||||
|
||||
func (ngx *Manager) writeCfg(cfg nginxConfiguration, ingressCfg IngressConfig) (bool, error) {
|
||||
fromMap := structs.Map(cfg)
|
||||
toMap := structs.Map(ngx.defCfg)
|
||||
curNginxCfg := merge(toMap, fromMap)
|
||||
|
||||
conf := make(map[string]interface{})
|
||||
conf["upstreams"] = ingressCfg.Upstreams
|
||||
conf["servers"] = ingressCfg.Servers
|
||||
conf["tcpUpstreams"] = ingressCfg.TCPUpstreams
|
||||
conf["defResolver"] = ngx.defResolver
|
||||
conf["sslDHParam"] = ngx.sslDHParam
|
||||
conf["cfg"] = fixKeyNames(curNginxCfg)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
err := ngx.template.Execute(buffer, conf)
|
||||
if err != nil {
|
||||
glog.Infof("NGINX error: %v", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if glog.V(3) {
|
||||
b, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
glog.Infof("NGINX configuration: %v", string(b))
|
||||
}
|
||||
|
||||
changed, err := ngx.needsReload(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
func fixKeyNames(data map[string]interface{}) map[string]interface{} {
|
||||
fixed := make(map[string]interface{})
|
||||
for k, v := range data {
|
||||
fixed[toCamelCase(k)] = v
|
||||
}
|
||||
|
||||
return fixed
|
||||
}
|
||||
|
||||
func toCamelCase(src string) string {
|
||||
byteSrc := []byte(src)
|
||||
chunks := camelRegexp.FindAll(byteSrc, -1)
|
||||
for idx, val := range chunks {
|
||||
if idx > 0 {
|
||||
chunks[idx] = bytes.Title(val)
|
||||
}
|
||||
}
|
||||
return string(bytes.Join(chunks, nil))
|
||||
}
|
||||
173
controllers/nginx/nginx/utils.go
Normal file
173
controllers/nginx/nginx/utils.go
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// getDNSServers returns the list of nameservers located in the file /etc/resolv.conf
|
||||
func getDNSServers() []string {
|
||||
file, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Lines of the form "nameserver 1.2.3.4" accumulate.
|
||||
nameservers := []string{}
|
||||
|
||||
lines := strings.Split(string(file), "\n")
|
||||
for l := range lines {
|
||||
trimmed := strings.TrimSpace(lines[l])
|
||||
if strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(trimmed)
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
if fields[0] == "nameserver" {
|
||||
nameservers = append(nameservers, fields[1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(3).Infof("nameservers to use: %v", nameservers)
|
||||
return nameservers
|
||||
}
|
||||
|
||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||
func (ngx *Manager) ReadConfig(config *api.ConfigMap) nginxConfiguration {
|
||||
if len(config.Data) == 0 {
|
||||
return newDefaultNginxCfg()
|
||||
}
|
||||
|
||||
cfg := newDefaultNginxCfg()
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
TagName: "structs",
|
||||
Result: &cfg,
|
||||
WeaklyTypedInput: true,
|
||||
})
|
||||
|
||||
err = decoder.Decode(config.Data)
|
||||
if err != nil {
|
||||
glog.Infof("%v", err)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (ngx *Manager) needsReload(data *bytes.Buffer) (bool, error) {
|
||||
filename := ngx.ConfigFile
|
||||
in, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
src, err := ioutil.ReadAll(in)
|
||||
in.Close()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
res := data.Bytes()
|
||||
if !bytes.Equal(src, res) {
|
||||
err = ioutil.WriteFile(filename, res, 0644)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
dData, err := diff(src, res)
|
||||
if err != nil {
|
||||
glog.Errorf("error computing diff: %s", err)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if glog.V(2) {
|
||||
glog.Infof("NGINX configuration diff a/%s b/%s\n", filename, filename)
|
||||
glog.Infof("%v", string(dData))
|
||||
}
|
||||
|
||||
return len(dData) > 0, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func diff(b1, b2 []byte) (data []byte, err error) {
|
||||
f1, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
defer f1.Close()
|
||||
|
||||
f2, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
defer f2.Close()
|
||||
|
||||
f1.Write(b1)
|
||||
f2.Write(b2)
|
||||
|
||||
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||
if len(data) > 0 {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func merge(dst, src map[string]interface{}) map[string]interface{} {
|
||||
for key, srcVal := range src {
|
||||
if dstVal, ok := dst[key]; ok {
|
||||
srcMap, srcMapOk := toMap(srcVal)
|
||||
dstMap, dstMapOk := toMap(dstVal)
|
||||
if srcMapOk && dstMapOk {
|
||||
srcVal = merge(dstMap, srcMap)
|
||||
}
|
||||
}
|
||||
dst[key] = srcVal
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func toMap(iface interface{}) (map[string]interface{}, bool) {
|
||||
value := reflect.ValueOf(iface)
|
||||
if value.Kind() == reflect.Map {
|
||||
m := map[string]interface{}{}
|
||||
for _, k := range value.MapKeys() {
|
||||
m[k.String()] = value.MapIndex(k).Interface()
|
||||
}
|
||||
|
||||
return m, true
|
||||
}
|
||||
|
||||
return map[string]interface{}{}, false
|
||||
}
|
||||
176
controllers/nginx/utils.go
Normal file
176
controllers/nginx/utils.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/util/workqueue"
|
||||
)
|
||||
|
||||
// StoreToIngressLister makes a Store that lists Ingress.
|
||||
type StoreToIngressLister struct {
|
||||
cache.Store
|
||||
}
|
||||
|
||||
// taskQueue manages a work queue through an independent worker that
|
||||
// invokes the given sync function for every work item inserted.
|
||||
type taskQueue struct {
|
||||
// queue is the work queue the worker polls
|
||||
queue *workqueue.Type
|
||||
// sync is called for each item in the queue
|
||||
sync func(string)
|
||||
// workerDone is closed when the worker exits
|
||||
workerDone chan struct{}
|
||||
}
|
||||
|
||||
func (t *taskQueue) run(period time.Duration, stopCh <-chan struct{}) {
|
||||
wait.Until(t.worker, period, stopCh)
|
||||
}
|
||||
|
||||
// enqueue enqueues ns/name of the given api object in the task queue.
|
||||
func (t *taskQueue) enqueue(obj interface{}) {
|
||||
key, err := keyFunc(obj)
|
||||
if err != nil {
|
||||
glog.Infof("could not get key for object %+v: %v", obj, err)
|
||||
return
|
||||
}
|
||||
t.queue.Add(key)
|
||||
}
|
||||
|
||||
func (t *taskQueue) requeue(key string, err error) {
|
||||
glog.V(3).Infof("requeuing %v, err %v", key, err)
|
||||
t.queue.Add(key)
|
||||
}
|
||||
|
||||
// worker processes work in the queue through sync.
|
||||
func (t *taskQueue) worker() {
|
||||
for {
|
||||
key, quit := t.queue.Get()
|
||||
if quit {
|
||||
close(t.workerDone)
|
||||
return
|
||||
}
|
||||
glog.V(3).Infof("syncing %v", key)
|
||||
t.sync(key.(string))
|
||||
t.queue.Done(key)
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown shuts down the work queue and waits for the worker to ACK
|
||||
func (t *taskQueue) shutdown() {
|
||||
t.queue.ShutDown()
|
||||
<-t.workerDone
|
||||
}
|
||||
|
||||
// NewTaskQueue creates a new task queue with the given sync function.
|
||||
// The sync function is called for every element inserted into the queue.
|
||||
func NewTaskQueue(syncFn func(string)) *taskQueue {
|
||||
return &taskQueue{
|
||||
queue: workqueue.New(),
|
||||
sync: syncFn,
|
||||
workerDone: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// getLBDetails returns runtime information about the pod (name, IP) and replication
|
||||
// controller or daemonset (namespace and name).
|
||||
// This is required to watch for changes in annotations or configuration (ConfigMap)
|
||||
func getLBDetails(kubeClient *unversioned.Client) (*lbInfo, error) {
|
||||
podIP := os.Getenv("POD_IP")
|
||||
podName := os.Getenv("POD_NAME")
|
||||
podNs := os.Getenv("POD_NAMESPACE")
|
||||
|
||||
pod, _ := kubeClient.Pods(podNs).Get(podName)
|
||||
if pod == nil {
|
||||
return nil, fmt.Errorf("Unable to get POD information")
|
||||
}
|
||||
|
||||
return &lbInfo{
|
||||
PodIP: podIP,
|
||||
Podname: podName,
|
||||
PodNamespace: podNs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isValidService(kubeClient *unversioned.Client, name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("empty string is not a valid service name")
|
||||
}
|
||||
|
||||
parts := strings.Split(name, "/")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid name format (namespace/name) in service '%v'", name)
|
||||
}
|
||||
|
||||
_, err := kubeClient.Services(parts[0]).Get(parts[1])
|
||||
return err
|
||||
}
|
||||
|
||||
func isHostValid(host string, cns []string) bool {
|
||||
for _, cn := range cns {
|
||||
if matchHostnames(cn, host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func matchHostnames(pattern, host string) bool {
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
pattern = strings.TrimSuffix(pattern, ".")
|
||||
|
||||
if len(pattern) == 0 || len(host) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
patternParts := strings.Split(pattern, ".")
|
||||
hostParts := strings.Split(host, ".")
|
||||
|
||||
if len(patternParts) != len(hostParts) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, patternPart := range patternParts {
|
||||
if i == 0 && patternPart == "*" {
|
||||
continue
|
||||
}
|
||||
if patternPart != hostParts[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func parseNsName(input string) (string, string, error) {
|
||||
nsName := strings.Split(input, "/")
|
||||
if len(nsName) != 2 {
|
||||
return "", "", fmt.Errorf("invalid format (namespace/name) found in '%v'", input)
|
||||
}
|
||||
|
||||
return nsName[0], nsName[1], nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue