<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CodeMirror nginx-renewed mode</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.css"
integrity="sha512-xIf9AdJauwKIVtrVRZ0i4nHP61Ogx9fSRAkCLecmE2dL/U8ioWpDvFCAy4dcfecN72HHB9+7FfQj3aiO68aaaw=="
crossorigin="anonymous"
referrerpolicy="no-referrer" />
<link
rel="stylesheet"
href="https://xavierog.github.io/codemirror-mode-pcre/src/pcre.css">
<style>
.CodeMirror {
border-top: 1px solid black;
border-bottom: 1px solid black;
height: 100%;
}
</style>
</head>
<body>
<h1>CodeMirror nginx-renewed mode</h1>
<p>
This is a <a href="https://codemirror.net/">CodeMirror</a> mode that brings
syntax highlighting for nginx configuration files.
</p>
<p>
<strong>Features:</strong>
<ul>
<li>recognize more than 870 nginx directives, including those from OpenResty and nginx-plus;</li>
<li>unrecognized directives remain accepted (they are shown in italic)</li>
<li>highlight directives that are recognized but should not occur in the current scope;</li>
<li>highlight regular expressions -- requires <em>codemirror-mode-pcre</em><br>
supported in: locations, maps, if conditions, rewrite statements and some proxy_* directives</li>
<li>highlight variables in strings -- requires <em>codemirror-mode-variables</em>;</li>
<li>highlight IP addresses -- requires <em>codemirror-mode-ipaddr</em>;<br>
supported in: geo, set_real_ip_from</li>
<li>
highlight Lua code -- requires <a href="https://codemirror.net/mode/lua/index.html">CodeMirror's Lua mode</a>; without it, strings and comments are still highlighted;<br>
supported in: blocks (<code>*_by_lua_block</code>)<br>
not supported in: strings (<code>*_by_lua</code>)<br>
</li>
</ul>
</p>
<p>
<strong>MIME types defined:</strong>
<ul>
<li>text/x-nginx-conf</li>
</ul>
</p>
<p>
<strong>Options:</strong>
<ul>
<li>initial_scope: string; default value: "main"</li>
<li>check_directive_scope: boolean; default value: true</li>
</ul>
</p>
<p>
<strong>MIME types used to invoke nested modes:</strong>
<ul>
<li>text/x-regex</li>
<li>text/x-variables</li>
<li>text/x-ip-address</li>
<li>text/x-lua</li>
</ul>
</p>
<h2 id="h2direct">Direct use</h2>
<p>
By default, codemirror-mode-nginx-renewed:
<ol>
<li>assumes the textarea reflects nginx's main scope (i.e. the same scope as nginx.conf);</li>
<li>checks whether each directive is allowed in the current scope.</li>
</ol>
The textareas below showcase codemirror-mode-nginx-renewed's capabilities.
</p>
<p>
Useful commands:
<ul>
<li>Ctrl+d: toggle comments</li>
<li>Ctrl+]: indent</li>
<li>Ctrl+[: unindent</li>
<li>Tab: auto-indent the current line or selection</li>
<li>Shift+Tab: if something is selected, indent it by one indent unit; otherwise, insert a tab character</li>
</ul>
</p>
<textarea data-mime="text/x-nginx-conf">
user www www;
worker_processes 5;
error_log logs/error.log;
pid logs/nginx.pid;
worker_rlimit_nofile 8192;
events {
worker_connections 4096; ## Default: 1024
}
http {
types {
text/html html htm shtml;
text/xml xml rss;
image/gif gif;
}
map "${http_user_agent}" $mobile {
default "${http_user_agent}";
hostnames;
volatile;
"Opera Mini" 1;
"~(?:Opera Mini)" 1;
"~*(?:Opera Mini)" 1;
include "/path/to/map/file.conf";
}
geo $country {
default ZZ;
include conf/geo.conf;
delete 127.0.0.0/16;
proxy 192.168.100.0/24;
proxy 2001:0db8::/32;
127.0.0.0/24 US;
127.0.0.1/32 RU;
10.1.0.0/16 RU;
192.168.1.0/24 UK;
1.2.3.4 TEST;
2001:0db8:: TEST;
2001:0db8::/32 TEST;
}
geo $dummy_range {
ranges;
1.2.3.4-1.2.3.6 "IPv4 range";
# nginx's IP ranges are IPv4-only; IPv6 ranges yield "[emerg] invalid network"
2001:0db8::-2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff "IPv6 range";
}
set_real_ip_from 10.20.30.40;
set_real_ip_from 10.20.30.0/24;
set_real_ip_from 2001:0db8::1234;
set_real_ip_from 2001:0db8::/64;
set_real_ip_from trustme.company.com;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
upstream bunch_of_servers {
server server00001:80 resolve;
server server00002:80 resolve;
server server00003:80 resolve;
server server00004:80 resolve;
server server00005:80 resolve;
}
server {
server_name "nginx.syntax-highlighting.demo.example";
server_name "~^nginx[^.]+\.syntax-highlighting\.demo\.example$";
# Examples from http://nginx.org/en/docs/http/server_names.html:
server_name example.org www.example.org;
server_name *.example.org;
server_name mail.*;
server_name ~^(?<user>.+)\.example\.net$;
server_name ~^www\d+\.example\.net$;
server_name "~^(?<name>\w+\d{1,3})\.example\.net$";
server_name ~^(www\.)?(?<domain>.+)$;
server_name ~^(www\.)?(.+)$;
server_name example.org www.example.org "";
# Locations, using double-quotes, single-quotes and unquoted strings:
location = "/exact-match" {} location = '/exact-match' {} location = /exact-match {}
location "/prefix-match-wi-re" {} location '/prefix-match-wi-re' {} location /prefix-match-wi-re {}
location ^~ "/prefix-match-wo-re" {} location ^~ '/prefix-match-wo-re' {} location ^~ /prefix-match-wo-re {}
location ~ "(cs|regex|match)" {} location ~ '(cs|regex|match)' {} location ~ (cs|regex|match) {}
location ~* "(ci|regex|match)" {} location ~* '(ci|regex|match)' {} location ~* (ci|regex|match) {}
# Conditions, using double-quotes, single-quotes and unquoted strings:
if ($variable = "${text}/x-variables") {} if ($variable = '${text}/x-variables') {} if ($variable = ${text}/x-variables) {}
if ($variable != "${text}/x-variables") {} if ($variable != '${text}/x-variables') {} if ($variable != ${text}/x-variables) {}
if ($variable ~ "^text/(?x:-[regex])") {} if ($variable ~ '^text/(?x:-[regex])') {} if ($variable ~ ^text/(?x:-[regex])) {}
if ($variable ~* "^text/(?x:-[regex])") {} if ($variable ~* '^text/(?x:-[regex])') {} if ($variable ~* ^text/(?x:-[regex])) {}
if ($variable !~ "^text/(?x:-[regex])") {} if ($variable !~ '^text/(?x:-[regex])') {} if ($variable !~ ^text/(?x:-[regex])) {}
if ($variable !~* "^text/(?x:-[regex])") {} if ($variable !~* '^text/(?x:-[regex])') {} if ($variable !~* ^text/(?x:-[regex])) {}
if ( -f "${text}/x-variables") {} if ( -f '${text}/x-variables') {} if ( -f ${text}/x-variables) {}
if (!-f "${text}/x-variables") {} if (!-f '${text}/x-variables') {} if (!-f ${text}/x-variables) {}
if ( -d "${text}/x-variables") {} if ( -d '${text}/x-variables') {} if ( -d ${text}/x-variables) {}
if (!-d "${text}/x-variables") {} if (!-d '${text}/x-variables') {} if (!-d ${text}/x-variables) {}
if ( -e "${text}/x-variables") {} if ( -e '${text}/x-variables') {} if ( -e ${text}/x-variables) {}
if (!-e "${text}/x-variables") {} if (!-e '${text}/x-variables') {} if (!-e ${text}/x-variables) {}
if ( -x "${text}/x-variables") {} if ( -x '${text}/x-variables') {} if ( -x ${text}/x-variables) {}
if (!-x "${text}/x-variables") {} if (!-x '${text}/x-variables') {} if (!-x ${text}/x-variables) {}
# without forgetting the simplest case:
if ($variable) {}
# Unquoted strings in conditions:
if ( # condition comment
$variable !~* ^text/(?x:-[regex]) # condition comment
) # condition comment
# post-condition, pre-scope comment
{ # scope comment
# scope comment
} # post-scope comment
# Rewrites:
rewrite "^/foo(?<tail>.*)" "/bar${tail}";
rewrite '^/foo(?<tail>.*)' '/bar${tail}' break;
rewrite ^/foo(?<tail>.*) /bar${tail} last;
# Other mod_rewrite directives:
rewrite_log on;
uninitialized_variable_warn off;
break;
location / {
limit_except GET PROPFIND LOCK {
deny all;
}
proxy_pass http://bunch_of_servers;
proxy_redirect off;
proxy_redirect default;
proxy_redirect http://$proxy_host:8000/ /;
proxy_redirect ~^(http://[^:]+):\d+(/.+)$ $1$2;
proxy_redirect ~*/user/([^/]+)/(.+)$ http://$1.example.com/$2;
location /foo {
proxy_cookie_domain off;
proxy_cookie_domain localhost example.org;
proxy_cookie_domain www.$host $host;
proxy_cookie_domain "~\.(?<domain_and_tld>[a-z]+\.[a-z]+)$" "${domain_and_tld}";
location /foo/bar {
proxy_cookie_path off;
proxy_cookie_path /two/ /;
proxy_cookie_path $uri "/some${uri}";
proxy_cookie_path "~*^/user/(?<username>[^/]+)" "/u/${username}";
location /foo/bar/baz {
proxy_cookie_flags off;
proxy_cookie_flags one httponly;
proxy_cookie_flags ${my_cookie} nohttponly;
proxy_cookie_flags ${app_name}_session secure "samesite=${my_map}" httponly;
proxy_cookie_flags ~^(?i:alpha_cookie|cookie_beta)$ nosecure samesite=strict;
}
}
}
}
# Enforce HTTPS for domain.tld subdomains:
set $scheme_and_domain "${map_real_scheme}://${host}";
if ($scheme_and_domain ~ "^http://.*\.domain\.tld\.?$") {
return 308 "https://${host}${request_uri}";
}
location /redirect { return "https://somewhere.else/"; }
location /close { return 444; }
location /prefix {
root /path/to/document/root/;
}
location ~ "(?x) # Enable PCRE extended mode
^
/user
/(?<action>login|logout|profile) # Also include 'profile' because...
/(?<tail>.*)
"
{
if ($tail ~ "^(some|[^/]+/really|compl(?:ex|icated)|stuff|t?here)$") {
return 307 "${scheme}://${host}/somewhere/else/${tail}${is_args}${args}";
}
# ...
}
}
}</textarea>
<p>
This textarea is a copy of <a href="https://github.com/openresty/lua-nginx-module#synopsis">lua-nginx-module's synopsis</a>:
</p>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http">
# set search paths for pure Lua external libraries (';;' is the default path):
lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';
# set search paths for Lua external libraries written in C (can also use ';;'):
lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';
server {
location /lua_content {
# MIME type determined by default_type:
default_type 'text/plain';
content_by_lua_block {
ngx.say('Hello,world!')
}
}
location /nginx_var {
# MIME type determined by default_type:
default_type 'text/plain';
# try access /nginx_var?a=hello,world
content_by_lua_block {
ngx.say(ngx.var.arg_a)
}
}
location = /request_body {
client_max_body_size 50k;
client_body_buffer_size 50k;
content_by_lua_block {
ngx.req.read_body() -- explicitly read the req body
local data = ngx.req.get_body_data()
if data then
ngx.say("body data:")
ngx.print(data)
return
end
-- body may get buffered in a temp file:
local file = ngx.req.get_body_file()
if file then
ngx.say("body is in file ", file)
else
ngx.say("no body found")
end
}
}
# transparent non-blocking I/O in Lua via subrequests
# (well, a better way is to use cosockets)
location = /lua {
# MIME type determined by default_type:
default_type 'text/plain';
content_by_lua_block {
local res = ngx.location.capture("/some_other_location")
if res then
ngx.say("status: ", res.status)
ngx.say("body:")
ngx.print(res.body)
end
}
}
location = /foo {
rewrite_by_lua_block {
res = ngx.location.capture("/memc",
{ args = { cmd = "incr", key = ngx.var.uri } }
)
}
proxy_pass http://blah.blah.com;
}
location = /mixed {
rewrite_by_lua_file /path/to/rewrite.lua;
access_by_lua_file /path/to/access.lua;
content_by_lua_file /path/to/content.lua;
}
# use nginx var in code path
# CAUTION: contents in nginx var must be carefully filtered,
# otherwise there'll be great security risk!
location ~ ^/app/([-_a-zA-Z0-9/]+) {
set $path $1;
content_by_lua_file /path/to/lua/app/root/$path.lua;
}
location / {
client_max_body_size 100k;
client_body_buffer_size 100k;
access_by_lua_block {
-- check the client IP address is in our black list
if ngx.var.remote_addr == "132.5.72.3" then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- check if the URI contains bad words
if ngx.var.uri and
string.match(ngx.var.request_body, "evil")
-- Alteration to test double square bracket strings:
page = [==[
<HTML>
<HEAD>
<TITLE>An HTML Page</TITLE>
</HEAD>
<BODY>
<A HREF="http://www.lua.org">Lua</A>
[[a text between double brackets]]
</BODY>
</HTML>]==]
then
return ngx.redirect("/terms_of_use.html")
end
-- tests passed
}
# proxy_pass/fastcgi_pass/etc settings
}
}</textarea>
<h2 id="h2customgeneric">Custom use without a specific scope</h2>
<p>
Sometimes, it is hard, if not impossible, to predict the scope of an nginx snippet.
Typical cases:
</p>
<ul>
<li>include files, which may contain directives suited for various scopes;</li>
<li>the output of "nginx -T", which may combine many files and thus many scopes.</li>
</ul>
<p>
To deal with these situations, state "check_directive_scope: false" when configuring codemirror-mode-nginx-renewed.
</p>
<textarea data-mime="text/x-nginx-conf" data-scope="unknown">
# configuration file /etc/nginx/nginx.conf:
worker_rlimit_nofile 8192; # main
# configuration file /etc/nginx/event.conf:
worker_connections 4096; # events
# configuration file /etc/nginx/include/enforce_org_tld.conf:
uninitialized_variable_warn off; # http, server or location?
if ($host ~* "^(?<domain_head>.+)\.net$") { # if in server or if in location?
return 308 "${scheme}://${domain_head}.org${request_uri}";
}</textarea>
<h2 id="h2customspecific">Custom use with specific scopes</h2>
<p>
By playing with the "initial_scope" setting, it is possible to change the nginx scope of the textarea.
initial_scope defaults to "main" and can take various values reflecting the desired scope.
Examples:
<ul>
<li>main/events</li>
<li>main/http/server/location/limit_except</li>
<li>main/http/server/location/if</li>
<li>main/http/server/location/types</li>
<li>main/http/server/if</li>
<li>main/mail/server</li>
<li>main/stream/upstream</li>
</ul>
Other examples and demonstrations follow.
</p>
<h3 id="h3mainhttpmap">main/http/map</h3>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http/map">
# Everything in this textarea is considered inside an HTTP map:
"~*^[a-m]" "alphabet-1st-half";
"~*^[n-z]" "alphabet-2nd-half";
"~*^[0-9]" "start-with-digit";
default "other";</textarea>
<h3 id="h3mainhttpgeo">main/http/geo</h3>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http/geo">
# Everything in this textarea is considered inside an HTTP geo:
0.0.0.0/1 ipv4-1st-half;
128.0.0.0/1 ipv4-2nd-half;
0000::/1 ipv6-1st-half;
8000::/1 ipv6-2nd-half;
default no-clue;</textarea>
<h3 id="h3mainhttptypes">main/http/types</h3>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http/types">
# Everything in this textarea is considered inside a types {} directive:
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;</textarea>
<h3 id="h3mainhttpserver">main/http/server</h3>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http/server">
# Everything in this textarea is considered inside an HTTP server:
listen 80;
listen 443 ssl;
ssl_certificate "/path/to/myapp.mycompany.com.cert.pem";
ssl_certificate_key "/path/to/myapp.mycompany.com.key.pem";
server_name myapp.mycompany.com;
if ($real_scheme = "http") {
return 308 "https://${server_name}${request_uri}";
}
location / {
return 200 "Under construction";
}
# proxy_pass cannot be used directly in server, which is why it is shown in bold red:
proxy_pass https://target_upstream;</textarea>
<h3 id="h3mainhttpserverlocation">main/http/server/location</h3>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http/server/location">
# Everything in this textarea is considered inside an HTTP location:
rewrite "^/prefix(?<tail>.*)" "${tail}" break;
proxy_pass https://target_upstream;
proxy_set_header "X-Forwarded-Prefix" "/prefix";
# listen does not make sense in location, which is why it is shown in bold red:
listen 42;</textarea>
<h3 id="mainhttpupstream">main/http/upstream</h3>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http/upstream">
# Everything in this textarea is considered inside an HTTP upstream:
zone target_upstream_zone 64k;
server mybackend1.mycompany.com:443 resolve;
server mybackend2.mycompany.com:443 resolve;
server mybackend3.mycompany.com:443 resolve backup;
sticky cookie myappsession path=/;
# proxy_ssl_name cannot be set directly in upstream, which is why it is shown in bold red:
proxy_ssl_name mybackendX.mycompany.com;</textarea>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.js"
integrity="sha512-6Q5cHfb86ZJ3qWx47Pw7P5CN1/pXcBMmz3G0bXLIQ67wOtRF7brCaK5QQLPz2CWLBqjWRNH+/bV5MwwWxFGxww=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/addon/comment/comment.min.js"
integrity="sha512-U0udq4Tvwzb+OiL6f7aak/n07mlIutTl+pEPtKoK0oOTF4Q9SkLOQYvKpIaCIM8CKTufd7szgeN9VDcgXDVvWg=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/mode/lua/lua.min.js"
integrity="sha512-MXR/wu8WxkFikybMYGuaR9O0SgRrcSReZUNuherC0XZ7SJN/db3W+qQCh+4rAiBBeNk/yd/NdnQd/s2nO4q4fA=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<script src="https://xavierog.github.io/codemirror-mode-pcre/src/pcre.js"></script>
<script src="https://xavierog.github.io/codemirror-mode-ipaddr/src/ipaddr.js"></script>
<script src="https://xavierog.github.io/codemirror-mode-variables/src/variables.js"></script>
<script src="src/nginx-renewed.js"></script>
<script>
function demo(textarea) {
var mime = textarea.attributes['data-mime'];
if (!mime) return;
var scope = textarea.attributes['data-scope'];
scope = scope ? scope.value : false;
var mode = { name: mime.value };
if (scope === 'unknown') mode.check_directive_scope = false;
else if (scope) mode.initial_scope = scope;
var conf = {
lineNumbers: true,
indentWithTabs: true,
indentUnit: 4,
showCursorWhenSelecting: true,
extraKeys: {
'Ctrl-D': 'toggleComment',
},
mode: mode,
};
CodeMirror.fromTextArea(textarea, conf);
}
var i, textareas = document.getElementsByTagName('textarea');
for (i = 0; i < textareas.length; ++ i) demo(textareas[i]);
</script>
</body>
</html>