github - nginx/njs-examples: nginx javascript examples


本站和网页 http://nginx.org/en/docs/njs/examples.html 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

GitHub - nginx/njs-examples: NGINX JavaScript examples
Skip to content
Navigation Menu
Toggle navigation
Sign in Product Actions
Automate any workflow
Packages
Host and manage packages
Security
Find and fix vulnerabilities
Codespaces
Instant dev environments
Copilot
Write better code with AI
Code review
Manage code changes
Issues
Plan and track work
Discussions
Collaborate outside of code
Explore
All features
Documentation
GitHub Skills
Blog
Solutions
For
Enterprise
Teams
Startups
Education
By Solution
CI/CD & Automation
DevOps
DevSecOps
Resources
Learning Pathways
White papers, Ebooks, Webinars
Customer Stories
Partners
Open Source
GitHub Sponsors
Fund open source developers
The ReadME Project
GitHub community articles
Repositories
Topics
Trending
Collections
Pricing
">
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
Name
Query
To see all available qualifiers, see our
documentation
Create saved search
Sign up
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
You switched accounts on another tab or window.
Dismiss alert
{{ message }}
nginx
njs-examples
Public
Notifications
Fork
77
Star
546
NGINX JavaScript examples
stars
forks
Branches
Tags
Activity
Code
Pull requests
Projects
Insights
Additional navigation options
nginx/njs-examples
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
master
Go to file
Folders and files
Last commit message
Last commit date
Latest commit
History
103 Commits
conf
njs
README.rst
View all files
Repository files navigation
README
Intro
This repo contains complete examples for various use cases where
is useful. The document as well as
njs documentation
expects some familiarity with and understanding of nginx. Beginners should refer to the official
admin guide
Note: the examples below work with njs >=
0.7.0
. To see the current version run the following command:
docker run -i -t nginx:latest /usr/bin/njs -V
Running inside Docker
Public nginx docker image contains open source version of nginx. To run examples for NGINX-PLUS, you have to
build
your own docker image.
git clone https://github.com/nginx/njs-examples
cd
EXAMPLE=
http/hello
docker run --rm --name njs_example
-v
$(
pwd
/conf/
$EXAMPLE
.conf:/etc/nginx/nginx.conf:ro -v
/njs/:/etc/nginx/njs/:ro -p 80:80 -p 443:443 -d nginx
for NGINX-PLUS examples,
docker run ... -d mynginxplus
Stopping.
docker stop njs_example
Status
While njs is in active development it is production ready. Its reliability has been proven by extensive test coverage as well as a good track record with our customers.
nginx compatibility
As njs is a
native nginx module
its compatibility with nginx is high. While it is developed as a separate project, it is routinely tested with latest nginx versions on various platforms and architectures.
Presentation at nginx.conf 2018
https://youtu.be/Jc_L6UffFOs
Extending NGINX with Custom Code
https://youtu.be/0CVhq4AUU7M
Installation
njs is available as a part of official nginx docker image as well as an officially supported
packet
for major linux distributions.
Repository
Please ask questions, report issues, and send patches via official
Github mirror
HTTP
Hello world example [http/hello]
nginx.conf:
load_module
modules/ngx_http_js_module.so;
events
{}
http
js_path
"/etc/nginx/njs/"
js_import
utils.js;
main from http/hello.js;
server
listen
80
location
= /version
js_content
utils.version;
main.hello;
example.js:
function
hello
return
200
"Hello world!\n"
export
default
Checking:
curl http://localhost/
Hello world
curl http://localhost/version
0.4.1
Setting nginx var as a result of async operation
js_set
handler does not support asynchronous operation (r.subrequest(), ngx.fetch()) because it is invoked in a synchronous context by nginx and is expected to return its result right away. Fortunately there are ways to overcome this limitation using other nginx modules.
The examples in this section is provided in order from simple to more advanced. The simplest method are preferred because generally they are more efficient.
Using auth_request [http/async_var/auth_request]
In simple cases
auth_request
is enough and njs is not required.
Simple case criteria:
request body is not needed to be forwarded
external service returns the desired value extractable as an nginx variable (for example as a response header)
The following example illustrates this use case using njs ONLY as a fake service. $backend variable is populated by auth_request module from a response header of a subrequest.
...
main from http/async_var/auth_request.js;
/secure/
/fetch_upstream;
auth_request_set
$backend
$upstream_http_x_backend
proxy_pass
http://
/fetch_upstream
internal
http://127.0.0.1:8079;
proxy_pass_request_body
off
proxy_set_header
Content-Length
""
X-Original-URI
$request_uri
127.0.0.1:8079;
main.choose_upstream;
127.0.0.1:8081;
"BACKEND A:
$uri
\n"
127.0.0.1:8082;
"BACKEND B:
import
qs
from
"querystring"
choose_upstream
let
backend
args
parse
headersIn
'X-Original-URI'
split
'?'
switch
token
case
'A'
'127.0.0.1:8081'
break
'B'
'127.0.0.1:8082'
404
headersOut
'X-backend'
curl http://localhost/secure/abc
token=A
BACKEND A:/secure/abc
curl http://localhost/secure/abcde
token=B
BACKEND B:/secure/abcde
Using auth_request and js_header_filter [http/async_var/js_header_filter]
js_header_filter
can be used to modify the service response and set an appropriate response header of an auth_request subrequest. This case is applicable when a service returns a value which cannot be used directly.
main from http/async_var/js_header_filter.js;
$sent_http_x_backend
main.set_upstream;
'B1'
'B2'
set_upstream
if
Authorization
Getting arbitrary field from JWT as a nginx variable [http/authorization/jwt]
main from http/authorization/jwt.js;
$jwt_payload_sub
main.jwt_payload_sub;
/jwt
jwt
data
var
parts
'.'
slice
map
=>
Buffer
'base64url'
toString
JSON
headers
payload
jwt_payload_sub
sub
curl
http://localhost/jwt
-H
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU4NDcyMzA4NX0.eyJpc3MiOiJuZ2lueCIsInN1YiI6ImFsaWNlIiwiZm9vIjoxMjMsImJhciI6InFxIiwienl4IjpmYWxzZX0.Kftl23Rvv9dIso1RuZ8uHaJ83BkKmMtTwch09rJtwgk
alice
Generating JWT token [http/authorization/gen_hs_jwt]
env
JWT_GEN_KEY;
main from http/authorization/gen_hs_jwt.js;
$jwt
main.jwt;
async
generate_hs256_jwt
init_claims
key
valid
header
typ
"JWT"
alg
"HS256"
claims
Object
assign
exp
Math
floor
Date
now
1000
stringify
join
wc_key
await
crypto
subtle
importKey
'raw'
name
'HMAC'
hash
'SHA-256'
false
'sign'
sign
iss
"nginx"
"alice"
foo
123
bar
"qq"
zyx
jwtv
process
JWT_GEN_KEY
600
setReturnValue
docker run --rm --name njs_example -e JWT_GEN_KEY=
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU4NDcyMjk2MH0.eyJpc3MiOiJuZ2lueCIsInN1YiI6ImFsaWNlIiwiZm9vIjoxMjMsImJhciI6InFxIiwienl4IjpmYWxzZX0.GxfKkJSWI4oq5sGBg4aKRAcFeKmiA6v4TR43HbcP2X8
Secure link [http/authorization/secure_link_hash]
Protecting
location from simple bots and web crawlers.
SECRET_KEY;
main from http/authorization/secure_link_hash.js;
$new_foo
main.create_secure_link;
$secret_key
key main.secret_key;
error_page
403
= @login;
secure_link
$cookie_foo
secure_link_md5
$uri$secret_key
$secure_link
) {
http://localhost:8080;
@login
add_header
Set-Cookie
"foo=
; Max-Age=60"
302
secret_key
SECRET_KEY
create_secure_link
require
'crypto'
createHash
'md5'
update
uri
digest
docker run --rm --name njs_example -e SECRET_KEY=
mykey
curl http://127.0.0.1/secure/r
curl http://127.0.0.1/secure/r -L
curl: (47) Maximum (50) redirects followed
curl http://127.0.0.1/secure/r --cookie-jar cookie.txt
curl http://127.0.0.1/secure/r --cookie cookie.txt
PASSED
Authorizing requests using auth_request [http/authorization/auth_request]
is generic nginx modules which implements client authorization based on the result of a subrequest. Combination of auth_request and njs allows to implement arbitrary authorization logic.
main from http/authorization/auth_request.js;
upstream
/validate;
http://backend;
/validate
main.authorize;
"BACKEND:
authorize
signature
Signature
error
"No signature"
401
method
!=
'GET'
`Unsupported method:
${
variables
createHmac
'sha1'
req_sig
"base64"
`Invalid signature:
\n`
curl http://localhost/secure/B
<
html
>
head><title
401 Authorization Required
/title></head
body
center><h
1>
/h1></center
hr><center
nginx/1.19.
0<
/center
/body
/html
-H Signature:fk9WRmw7Rl+NwVAA759+H2Uq
-H Signature:fk9WRmw7Rl+NwVAA759+H2UqxNs=
BACKEND:/secure/B
docker logs njs_example
172.17.0.1 - - [03/Aug/2020:18:22:30 +0000]
GET /secure/B HTTP/1.1
401 179
curl/7.58.0
2020/08/03 18:22:47 [error] 28#28:
3 js: No signature
172.17.0.1 - - [03/Aug/2020:18:22:47 +0000]
2020/08/03 18:22:54 [error] 28#28:
4 js: Invalid signature: fk9WRmw7Rl+NwVAA759+H2UqxNs=
172.17.0.1 - - [03/Aug/2020:18:22:54 +0000]
127.0.0.1 - - [03/Aug/2020:18:23:00 +0000]
GET /secure/B HTTP/1.0
200 18
172.17.0.1 - - [03/Aug/2020:18:23:00 +0000]
Authorizing requests based on request body content [http/authorization/request_body]
cannot inspect client request body. Sometimes inspecting client request body is required, for example to validate POST arguments (application/x-www-form-urlencoded).
main from http/authorization/request_body.js;
@app-backend
"No signature\n"
'POST'
requestText
'Content-Type'
'application/x-www-form-urlencoded'
||
length
"Unsupported method\n"
internalRedirect
'@app-backend'
No signature
a=1 -H Signature:A
Invalid signature: YC5iL6aKDnv7XOjknEeDL+P58iw=
a=1 -H Signature:YC5iL6aKDnv7XOjknEeDL+P58iw=
curl http://localhost/secure/B -d
a=1
-X POST -H Signature:YC5iL6aKDnv7XOjknEeDL+P58iw=
Certificates
Reading subject alternative from client certificate [http/certs/subject_alternative]
Accessing arbitrary fields in client certificates.
Certificates are created using the following
guide
main from http/certs/js/subject_alternative.js;
$san
main.san;
443
ssl;
server_name
www.example.com;
ssl_password_file
/etc/nginx/njs/http/certs/ca/password;
ssl_certificate
/etc/nginx/njs/http/certs/ca/intermediate/certs/www.example.com.cert.pem;
ssl_certificate_key
/etc/nginx/njs/http/certs/ca/intermediate/private/www.example.com.key.pem;
ssl_client_certificate
/etc/nginx/njs/http/certs/ca/intermediate/certs/ca-chain.cert.pem;
ssl_verify_client
on
x509
'x509.js'
san
pem_cert
ssl_client_raw_cert
'{"error": "no client certificate"}'
cert
parse_pem_cert
// subjectAltName oid 2.5.29.17
get_oid_value
"2.5.29.17"
openssl x509 -noout -text -in njs/http/certs/ca/intermediate/certs/client.cert.pem
grep
X509v3 Subject Alternative Name
-A1
X509v3 Subject Alternative Name:
IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, DNS:example.com, DNS:www2.example.com
curl https://localhost/ --insecure --key njs/http/certs/ca/intermediate/private/client.key.pem --cert njs/http/certs/ca/intermediate/certs/client.cert.pem
--pass secretpassword
7f000001
00000000000000000000000000000001
example.com
www2.example.com
Securely serve encrypted traffic without server restarts when certificate or key changes occur. [http/certs/dynamic] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Configure NGINX to serve encrypted traffic without server restarts when certificate or key changes occur by using
js_shared_dict_zone
as a cache.
Note: this example below work with njs >=
0.8.0
This example demonstrates:
Use of
in combination with
ssl_certificate data:$var;
to use NJS to resolve value of cert/key during handshake.
to store cert/key in memory.
Implementation a simple RESTful API to manage
shared_dict
to get/set certificate/key files.
How to deal with
Content-Disposition
while handling file uploads in NJS.
error_log
/dev/stdout
debug
main from http/certs/js/dynamic.js;
zone=kv:1m;
js_var
$shared_dict_zone_name
kv;
$cert_folder
'/tmp/'
$dynamic_ssl_cert
main.js_cert;
$dynamic_ssl_key
main.js_key;
data:
= /
main.info;
/kv
main.kv;
= /clear
main.clear_cache;
Here we would implement
handlers that reads cert/key from a FS or from
` (used as a cache here):
js_cert
'ssl_server_name'
read_cert_or_key
'.cert.pem'
else
''
js_key
'.key.pem'
joinPaths
'/'
replace
\/+
fileExtension
path
const
zone
'shared_dict_zone_name'
certName
ssl_server_name
prefix
'cert_folder'
'/etc/nginx/certs/'
log
`Resolving
'certs'
':'
cache
&&
ngx
shared
get
`Read
from cache`
try
fs
readFileSync
'utf8'
'Read from cache'
catch
`Error reading from file:',
, . Error=
set
'Persisted in cache'
errMsg
`Error writing to shared dict zone:
. Error=
The rest of code can be found in the
njs/http/certs/js/dynamic.js
when started and there is no cert/key it fails to serve HTTPS
curl -k --resolve www.example.com:443:127.0.0.1 https://www.example.com:443
Upload cert/key files. file name would be used to form a key for shared_dict
curl -iv http://localhost:80/kv -F cert=@njs/http/certs/ca/intermediate/certs/www.example.com.cert.pem -F key=@njs/http/certs/ca/intermediate/private/www.example.com.key.pem
Get Certificate from shared_dict:
curl http://localhost/kv/www.example.com.cert.pem
Get Private Key from shared_dict:
curl http://localhost/kv/www.example.com.key.pem
now we can test HTTPS again
curl -k --resolve www.example.com:443:127.0.0.1 https://www.example.com
Clear shared_dict
curl http://localhost/clear
Fetch
HTTPS fetch example [http/certs/fetch_https]
main from http/certs/js/fetch_https.js;
resolver
1.1.1.1
main.fetch;
js_fetch_trusted_certificate
/etc/nginx/njs/http/certs/ISRG_Root_X1.pem;
fetch
reply
'https://nginx.org/'
text
footer
"----------NGINX.ORG-----------"
\n
substring
left...\n
Proxying
Subrequests join [http/join_subrequests]
Combining the results of several subrequests asynchronously into a single JSON reply.
main from http/join_subrequests.js;
/join
main.join;
/foo
/bar
http://localhost:8090;
join_subrequests
'/foo'
'/bar'
subs
results
Promise
all
subrequest
response
code
status
responseText
curl http://localhost/join
[{
:200,
FOO
},{
BAR
}]
Subrequests chaining [http/subrequests_chaining]
Subrequests chaining.
main from http/subrequests_chaining.js;
main.process;
= /auth
= /backend
'/auth'
'token'
throw
new
Error
"token is not available"
backend_reply
'/backend'
`token=
500
authenticate
auth
===
'secret'
"OK"
42
"INVALID"
curl http://localhost/start -H
Authorization: Bearer secret
Token is 42
curl http://localhost/start
Error: token is not available
Authorization: Bearer secre
Modifying response
Modifying or deleting cookies sent by the upstream server [http/response/modify_set_cookie]
main from http/response/modify_set_cookie.js;
/modify_cookies
main.cookies_filter;
8080
"XXXXXX"
"BB"
"YYYYYYY"
cookies_filter
cookies
'Set-Cookie'
filter
Number
len
curl http://localhost/modify_cookies
len=1 -v
Set-Cookie: XXXXXX
Set-Cookie: BB
Set-Cookie: YYYYYYY
len=3 -v
Converting response body characters to lower case [http/response/to_lower_case]
main from http/response/to_lower_case.js;
js_body_filter
main.to_lower_case;
'Hello World'
to_lower_case
flags
sendBuffer
toLowerCase
hello world
Logging
Logging the Number of Requests Per Client [http/logging/num_requests]
Note
The
keyval
and
keyval_zone
directives are available as part of our
commercial subscription
In this example
is used to count (accross all nginx workers) the incoming requests from the same ip address.
main from http/logging/num_requests.js;
$num_requests
http.num_requests;
zone=foo:10m;
$remote_addr
$foo
zone=foo;
log_format
$time_local
access_log
logs/access.log bar;
num_requests
curl http://localhost/aa
curl --interface 127.0.0.2 http://localhost/aa
127.0.0.1 [22/Nov/2021:16:55:06 +0000] 1
127.0.0.1 [22/Nov/2021:16:55:07 +0000] 2
127.0.0.1 [22/Nov/2021:16:55:29 +0000] 3
127.0.0.2 [22/Nov/2021:18:20:24 +0000] 1
127.0.0.2 [22/Nov/2021:18:20:25 +0000] 2
Shared Dictionary
HTTP Rate limit[http/rate-limit/simple]
is used to implement a simple rate limit and can be set in different contexts. The rate limit is implemented using a shared dictionary zone and a simple javascript function that is called for each request and increments the counter for the current window. If the counter exceeds the limit, the function returns the number of seconds until the end of the window. The function is called using
and the result is stored in a variable that is used to return a 429 response if the limit is exceeded.
main from http/rate-limit/simple.js;
# optionally set timeout so NJS resets and deletes all data for ratelimit counters
zone=kv:1M timeout=3600s evict;
# access_log off;
$rl_zone_name
# shared dict zone name; requred variable
$rl_windows_ms
30000
# optional window in miliseconds; default 1 minute window if not set
$rl_limit
10
# optional limit for the window; default 10 requests if not set
$rl_key
# rate limit key; default remote_addr if not set
$rl_result
main.ratelimit;
# call ratelimit function that returns retry-after value if limit is exceeded
# test rate limit result
"0"
Retry-After
always;
429
"Too Many Requests."
# Your normal processing here
"hello world"
defaultResponse
ratelimit
'rl_zone_name'
kv
`ratelimit:
js_shared_dict_zone not found`
'rl_key'
'remote_addr'
window
'rl_windows_ms'
60000
limit
'rl_limit'
requestData
undefined
timestamp
count
>=
++
elapsed
`limit:
window:
elapsed:
count:
timestamp:
retryAfter
ceil
curl http://localhost
200 hello world
3rd request should fail according to the rate limit $rl_limit=2
429 rate limit exceeded
NGINX-PLUS API
Setting keyval using a subrequest [http/api/set_keyval]
api
main from http/api/set_keyval.js;
/keyval
main.set_keyval;
/api
write=on;
/api/ro
api;
set_keyval
res
'/api/7/http/keyvals/foo'
300
curl http://localhost/api/ro/7/http/keyvals/foo
curl http://localhost:8000/keyval -d
{"a":1}
OK
{"a":2}
:{
:409,
\"
already exists
KeyvalKeyExists
},
request_id
cbec775883f6b10f2fe79e27d3f249ce
href
https://nginx.org/en/docs/http/ngx_http_api_module.html
curl http://localhost:8000/keyval
method=PATCH -d
curl http://localhost:8000/api/ro/7/http/keyvals/foo
Stream
Authorizing connections using ngx.fetch() as auth_request [stream/auth_request]
The example illustrates the usage of ngx.fetch() as an
auth request
analog in stream with a very simple TCP-based protocol: a connection starts with a magic prefix "MAGiK" followed by a secret 2 bytes. The preread_verify handler reads the first part of a connection and sends the secret bytes for verification to a HTTP endpoint. Later it decides based upon the endpoint reply whether forward the connection to an upstream or reject the connection.
stream
main from stream/auth_request.js;
js_preread
main.preread_verify;
8081
BACKEND\n;
aaa
main.validate;
preread_verify
collect
'upload'
+=
startsWith
'MAGiK'
'http://127.0.0.1:8080/validate'
Host
'aaa'
==
done
deny
validate
'QZ'
telnet 127.0.0.1 80
Hi
Connection closed by foreign host.
MAGiKQZ
BACKEND
MAGiKQQ
Routing
Choosing upstream in stream based on the underlying protocol [stream/detect_http]
main from stream/detect_http.js;
$upstream
main.upstream_type;
httpback
127.0.0.1:8080;
tcpback
127.0.0.1:3001;
main.detect_http;
is_http
detect_http
indexOf
'\r\n'
substr
endsWith
" HTTP/1."
last
upstream_type
"httpback"
"tcpback"
HTTPBACK
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is
^]
TEST
TCPBACK
Misc
File IO [misc/file_io]
main from misc/file_io.js;
/version
/push
main.push;
/flush
main.flush;
/read
main.read;
'fs'
STORAGE
"/tmp/njs_storage"
push
appendFileSync
flush
writeFileSync
read
curl http://localhost/read
empty reply
curl http://localhost/push -X POST --data
AAA
BBB
CCC
200 AAABBBCCC
curl http://localhost/flush -X POST
Webcrypto (AES-GSM) [misc/aes_gsm]
main from misc/aes_gsm.js;
/encrypt
main.encrypt;
/decrypt
main.decrypt;
encryptUAM
key_in
iv
'AES-GCM'
'hex'
getRandomValues
Uint8Array
12
sha256
TextEncoder
encode
'encrypt'
cipher
encrypt
btoa
String
fromCharCode
apply
null
decryptUAM
value
ERR
dump
'base64'
'decrypt'
decrypt
TextDecoder
decode
encrypted
`encryption failed with
message
decrypted
`decryption failed with
http://localhost/encrypt?key=mySecret&iv=000000000000000000000001
-d TEXT-TO-BE-ENCODED
kLKXeb/h1inwXYlP7M504xCD+/1sF4yesCSUc7/OJiyPyw==
AAAAAAAAAAAAAAAB
http://localhost/decrypt?key=mySecret
-d
{"cipher":"kLKXeb/h1inwXYlP7M504xCD+/1sF4yesCSUc7/OJiyPyw==","iv":"AAAAAAAAAAAAAAAA"}
decryption failed with
EVP_DecryptFinal_ex
() failed
http://localhost/decrypt?key=mySecre
{"cipher":"kLKXeb/h1inwXYlP7M504xCD+/1sF4yesCSUc7/OJiyPyw==","iv":"AAAAAAAAAAAAAAAB"}
TEXT-TO-BE-ENCODED
Command line interface
docker run -i -t nginx:latest /usr/bin/njs
interactive njs 0.4.1
v.<Tab> -> the properties and prototype methods of v.
>> globalThis
global {
console: Console {
log: [Function: native],
dump: [Function: native],
time: [Function: native],
timeEnd: [Function: native]
njs: njs {
version: '0.4.1'
print: [Function: native],
global: [Circular],
process: process {
argv: [
'/usr/bin/njs',
],
env: {
HOSTNAME: '483ac20bb33f',
HOME: '/root',
PKG_RELEASE: '1~buster',
TERM: 'xterm',
NGINX_VERSION: '1.19.0',
PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
NJS_VERSION: '0.4.1',
PWD: '/'
Additional learning materials
soulteary/njs-learning-materials
4141done/talks-njs_for_fun
About
javascript
Readme
Custom properties
Stars
Watchers
19
watching
Forks
Report repository
Releases
No releases published
No packages published
Contributors
Languages
JavaScript
97.5%
Python
2.5%
Footer
© 2024 GitHub, Inc.
Footer navigation
Terms
Privacy
Docs
Contact
Manage cookies
Do not share my personal information
You can’t perform that action at this time.