|<<
<
>
>>|
/
The state of the art of {{#x|nginx.conf}} scripting ☺{{#author|agentzh@gmail.com}}☺ {{#author|章亦春 (agentzh)}} {{#date|2010.10}} ---- {{#v|$}} nginx -c {{#x|/path/to/nginx.conf}} {{img src="images/nginx-logo.png" width="350" height="90"}} ---- {{#v|$}} ps aux | grep nginx root 2003 0.0 0.0 25208 412 ? Ss 10:08 0:00 nginx: {{#x|master process}} nginx nobody 2004 0.0 0.0 25608 1044 ? S 10:08 0:00 nginx: {{#x|worker process}} nobody 2005 0.0 0.0 25608 1044 ? S 10:08 0:00 nginx: {{#x|worker process}} ---- {{#cm|# nginx.conf }} {{#kw|worker_processes}} 2; {{#kw|events}} { {{#kw|worker_connections}} 1024; } {{#kw|http}} { ... {{#kw|server}} { {{#kw|listen}} 80; {{#kw|server_name}} localhost; ... {{#kw|location}} / { {{#kw|root}} /var/www; {{#kw|index}} index.html index.htm; } } } ---- {{#x|♡}} {{#i|Hello World}} on the {{#ci|nginx}} land {{img src="images/hello.png" width="400" height="300"}} ---- {{#cm|# enable the ngx_echo module in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/{{#x|echo-nginx-module}} ---- {{#kw|location}} = '/hello' { {{#kw|echo}} {{#x|"hello, world!"}}; } ---- {{#v|$}} curl 'http://localhost/hello' {{#x|hello, world!}} ---- {{#cm|# enable the ngx_set_misc module and}} {{#cm|# Marcus Clyne's ngx_devel_kit in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/echo-nginx-module \ --add-module=/path/to/{{#x|ngx_devel_kit}} \ --add-module=/path/to/{{#x|set-misc-nginx-module}} ---- {{#kw|location}} = '/hello' { {{#kw|set_unescape_uri}} {{#v|$person}} {{#v|$arg_person}}; {{#kw|set_if_empty}} {{#v|$person}} {{#x|'anonymous'}}; {{#kw|echo}} {{#x|"hello, }}{{#v|$person}}{{#x|!"}}; } ---- {{#v|$}} curl 'http://localhost/hello?{{#x|person=agentzh}}' hello, {{#x|agentzh}}! {{#v|$}} curl 'http://localhost/hello' hello, {{#x|anonymous}}! ---- {{#x|♡}} Using {{#ci|subrequests}} to do mashup ---- {{#kw|location}} = '/merge' { {{#kw|echo}} {{#x|'['}}; {{#kw|echo_location_async}} /moon; {{#kw|echo}} {{#x|','}}; {{#kw|echo_location_async}} /earth; {{#kw|echo}} {{#x|']'}}; } {{#kw|location}} /moon { {{#kw|echo}} {{#x|'"moon"'}}; } {{#kw|location}} /earth { {{#kw|echo}} {{#x|'"earth"'}}; } ---- {{#v|$}} curl 'http://localhost/merge' {{#x|[}} {{#x|"moon"}} {{#x|,}} {{#x|"earth"}} {{#x|]}} ---- {{#cm|# (not quite) REST interface to our memcached server}} {{#cm|# at 127.0.0.1:11211}} {{#kw|location}} = /memc { {{#kw|set}} {{#v|$memc_cmd}} {{#v|$arg_cmd}}; {{#kw|set}} {{#v|$memc_key}} {{#v|$arg_key}}; {{#kw|set}} {{#v|$memc_value}} {{#v|$arg_val}}; {{#kw|set}} {{#v|$memc_exptime}} {{#v|$arg_exptime}}; {{#kw|memc_pass}} {{#x|127.0.0.1:11211}}; } ---- {{#v|$}} curl 'http://localhost/memc?{{#x|cmd=flush_all}}'; {{#x|OK}} {{#v|$}} curl 'http://localhost/memc?{{#x|cmd=replace&key=foo&val=FOO}}'; {{#x|NOT_STORED}} ---- {{#v|$}} curl 'http://localhost/memc?{{#x|cmd=add&key=foo&val=Bar&exptime=60}}'; {{#x|STORED}} {{#v|$}} curl 'http://localhost/memc?{{#x|cmd=replace&key=foo&val=Foo}}'; {{#x|STORED}} {{#v|$}} curl 'http://localhost/memc?{{#x|cmd=set&key=foo&val=Hello}}'; {{#x|STORED}} ---- {{#v|$}} curl 'http://localhost/memc?{{#x|cmd=get&key=foo}}'; {{#x|Hello}} {{#v|$}} curl 'http://localhost/memc?{{#x|cmd=delete&key=foo}}'; {{#x|DELETED}} ---- {{#x|♡}} Memcached {{#x|connection pool}} support {{img src="images/pool2.jpg" width="145" height="109"}} ---- {{#cm|# enable Maxim Dounin's ngx_http_upstream_keepalive module}} {{#cm|# in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/echo-nginx-module \ --add-module=/path/to/memc-nginx-module \ --add-module=/path/to/{{#x|ngx_http_upstream_keepalive}} ---- {{#kw|http}} { ... {{#kw|upstream}} {{#x|my_memc_backend}} { {{#kw|server}} 127.0.0.1:11211; {{#cm|# a connection pool that can cache}} {{#cm|# up to 1024 connections}} {{#kw|keepalive}} {{#x|1024}} single; } ... } ---- {{#kw|location}} = /memc { ... {{#kw|memc_pass}} {{#x|my_memc_backend}}; } ---- {{#x|♡}} Memcached server {{#ci|hashing}} based on user keys (Hey, memcached {{#x|cluster}}!) {{img src="images/cluster-abs.gif" width="124" height="84"}} ---- {{#cm|# enable the ngx_set_misc module and Marcus Clyne's}} {{#cm|# ngx_devel_kit again in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/memc-nginx-module \ --add-module=/path/to/{{#x|ngx_devel_kit}} \ --add-module=/path/to/{{#x|set-misc-nginx-module}} ---- {{#kw|http}} { {{#kw|upstream}} {{#x|A}} { {{#kw|server}} 10.32.110.5:11211; } {{#kw|upstream}} {{#x|B}} { {{#kw|server}} 10.32.110.16:11211; } {{#kw|upstream}} {{#x|C}} { {{#kw|server}} 10.32.110.27:11211; } {{#kw|upstream_list}} {{#x|my_cluster A B C}}; ... } ---- location = /memc { set $memc_cmd $arg_cmd; set $memc_key $arg_key; set $memc_value $arg_val; set $memc_exptime $arg_exptime; {{#cm|# hashing the $arg_key to an upstream backend}} {{#cm|# in the my_cluster upstream list, and set $backend:}} {{#kw|set_hashed_upstream}} {{#v|$backend}} {{#x|my_cluster}} {{#v|$arg_key}}; {{#cm|# pass $backend to memc_pass:}} {{#kw|memc_pass}} {{#v|$backend}}; } ---- {{#x|♡}} Some {{#i|non-blocking}} {{#x|MySQL}} love {{img src="images/mysql.png" width="128" height="130"}} ---- {{#cm|# install libdrizzle first and then}} {{#cm|# enable the ngx_drizzle and ngx_rds_json}} {{#cm|# modules in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/{{#x|drizzle-nginx-module}} \ --add-module=/path/to/{{#x|rds-json-nginx-module}} ---- {{#kw|http}} { {{#kw|upstream}} my_mysql_backend { {{#kw|drizzle_server}} {{#c|127.0.0.1:3306}} dbname={{#c|test}} password={{#c|some_pass}} user={{#c|monty}} protocol={{#c|mysql}}; } ... } ---- {{#kw|location}} = /cats { {{#kw|drizzle_query}} {{#x|'select * from cats'}}; {{#kw|drizzle_pass}} {{#x|my_mysql_backend}}; {{#kw|rds_json}} on; } ---- {{#v|$}} curl 'http://localhost/cats' {{#x|[{"name":"Jerry","age":1},{"name":"Tom","age":3}]}} ---- {{#x|♡}} mysql {{#x|connection pool}} support {{img src="images/pool.jpg" width="130" height="88"}} ---- http { upstream my_mysql_backend { drizzle_server 127.0.0.1:3306 dbname=test password=some_pass user=monty protocol=mysql; {{#cm|# a connection pool that can cache up to}} {{#cm|# 200 mysql TCP connections}} {{#kw|drizzle_keepalive}} max={{#x|200}} overflow={{#x|reject}}; } ... } ---- {{#x|♡}} Mysql {{#x|cluster}} {{#i|hashing}} love {{img src="images/cluster.jpg" width="150" height="107"}} ---- {{#cm|# re-enable the ngx_set_misc module and Marcus Clyne's}} {{#cm|# ngx_devel_kit in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/drizzle-nginx-module \ --add-module=/path/to/rds-json-nginx-module \ --add-module=/path/to/{{#x|ngx_devel_kit}} \ --add-module=/path/to/{{#x|set-misc-nginx-module}} ---- {{#kw|http}} { {{#kw|upstream}} {{#x|A}} { {{#kw|drizzle_server}} ...; } {{#kw|upstream}} {{#x|B}} { {{#kw|drizzle_server}} ...; } {{#kw|upstream}} {{#x|C}} { {{#kw|drizzle_server}} ...; } {{#kw|upstream_list}} {{#x|my_cluster A B C}}; ... } ---- {{#kw|location}} ~ {{#x|'^/cat/(.*)'}} { {{#kw|set}} {{#v|$name}} {{#v|$1}}; {{#kw|set_quote_sql_str}} {{#v|$quoted_name}} {{#v|$name}}; {{#kw|drizzle_query}} {{#x|"select *}} {{#x|from cats}} {{#x|where name=}}{{#v|$quoted_name}}{{#x|"}}; {{#kw|set_hashed_upstream}} {{#v|$backend}} {{#x|my_cluster}} {{#v|$name}}; {{#kw|drizzle_pass}} {{#v|$backend}}; {{#kw|rds_json}} on; } ---- {{#x|♡}} {{#ci|ngx_postgres}} has {{#x|already}} landed. Thanks Piotr Sikora! {{http://github.com/FRiCKLE/ngx_postgres}} {{img src="images/elephant.png" width="143" height="147"}} ---- {{#cm|# configure the PostgreSQL upstream backend}} {{#kw|upstream}} my_pg_backend { {{#kw|postgres_server}} {{#x|10.62.136.3:5432}} dbname={{#x|test}} user={{#x|someone}} password={{#x|123456}}; } ---- {{#kw|location}} /cats { {{#kw|postgres_query}} {{#x|'select * from cats'}}; {{#kw|postgres_pass}} {{#x|my_pg_backend}}; {{#kw|rds_json}} on; } ---- {{#v|$}} curl {{#x|'localhost/cats'}} [{\"name\":\"Marry\",\"age\":32},{\"name\":\"Bob\",\"age\":12}] ---- {{#x|♡}} Everything is also {{#ci|non-blocking}} as ngx_drizzle. Thanks to {{#x|libpq}}'s nonblocking API! ---- {{#x|♡}} Construct fully {{#ci|RESTful}} queries in a {{#x|single}} location ---- {{#kw|location}} ~ {{#x|'^/cat/(\d+)'}} { {{#kw|set}} {{#v|$id}} {{#v|$1}}; {{#kw|set_form_input}} {{#v|$name}}; {{#kw|set_quote_sql_str}} {{#v|$quoted_name}} {{#v|$name}}; {{#kw|postgres_query}} GET {{#x|"select * from cats where id=}}{{#v|$id}}{{#x|"}}; {{#kw|postgres_query}} DELETE {{#x|"delete from cats where id=}}{{#v|$id}}{{#x|"}}; {{#kw|postgres_query}} POST {{#x|"insert into cats (id, name) values(}}{{#v|$id}}{{#x|, }}{{#v|$quoted_name}}{{#x|)"}}; {{#kw|postgres_pass}} my_pg_backend; } ---- {{#x|♡}} Qunar.com is running {{#ci|ngx_postgres}} + {{#ci|ngx_rds_json}} in {{#x|production}}. Thanks Liseen Wan's promotion! ---- {{img src="images/qunar-hotel.png" width="928" height="590"}} ---- {{img src="images/qunar-pinyin.png" width="928" height="590"}} ---- {{#x|♡}} {{#ci|Caching}} database responses using {{#x|memcached}} via ngx_srcache and ngx_memc. {{http://github.com/agentzh/srcache-nginx-module}} ---- {{#cm|# enable the ngx_srcache and other}} {{#cm|# modules in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/{{#x|srcache-nginx-module}} \ --add-module=/path/to/rds-json-nginx-module \ --add-module=/path/to/memc-nginx-module \ --add-module=/path/to/drizzle-nginx-module ---- {{#x|♡}} It's very important to put {{#x|ngx_srcache}} {{#ci|before}} {{#x|ngx_rds_json}} during nginx configure so that we cache the final JSON rather than RDS. ---- {{#cm|# configure the mysql upstream backend}} {{#kw|upstream}} mysql_backend { {{#kw|drizzle_server}} {{#x|127.0.0.1:3306}} dbname={{#x|test}} password={{#x|some_pass}} user={{#x|monty}} protocol={{#x|mysql}}; } ---- {{#cm|# configure the cache storage location}} {{#kw|location}} /memc { {{#kw|internal}}; {{#kw|set}} {{#v|$memc_key}} {{#v|$query_string}}; {{#kw|set}} {{#v|$memc_exptime}} 300; {{#kw|memc_pass}} 127.0.0.1:11211; } ---- {{#kw|location}} /cats { {{#kw|srcache_fetch}} {{#x|GET /memc}} {{#v|$uri}}; {{#kw|srcache_store}} {{#x|PUT /memc}} {{#v|$uri}}; {{#kw|default_type}} {{#x|application/json}}; {{#kw|drizzle_pass}} mysql_backend; {{#kw|drizzle_query}} 'select * from cats'; {{#kw|rds_json}} on; } ---- {{#v|$}} curl {{#x|'localhost/cats'}} [{\"name\":\"Marry\",\"age\":32},{\"name\":\"Bob\",\"age\":12}] ---- {{#cm|# if it is a cache miss}} {{#v|$}} memcached -vvv -p 11211 ... <10 new client connection <10 {{#x|get /cats}} > {{#c|NOT FOUND /cats}} >10 END <10 connection closed. <10 new client connection <10 {{#x|set /cats 0 300 44}} > NOT FOUND /cats >10 {{#c|STORED}} <10 connection closed. ---- {{#cm|# if it is a cache hit}} {{#v|$}} memcached -vvv -p 11211 ... <10 new client connection <10 {{#x|get /cats}} > {{#c|FOUND KEY /cats}} >10 sending key /cats >10 END <10 connection closed. ---- {{#x|♡}} {{#x|ngx_lua}} is quite usable {{#ci|now}}! {{http://github.com/chaoslawful/lua-nginx-module}} {{img src="images/lua.jpg" width="118" height="118"}} ---- {{#x|♡}} chaoslawful is {{#ci|crazy}}! {{img src="images/chaoslawful.jpg" width="120" height="120"}} ---- {{#cm|# first install lua (or even luajit) into your system...}} {{#cm|# enable the ngx_lua module in your nginx build}} {{#v|$}} ./configure --prefix=/opt/nginx \ --add-module=/path/to/ngx_devel_kit \ --add-module=/path/to/echo-nginx-module \ --add-module=/path/to/{{#x|lua-nginx-module}} ---- {{#kw|location}} = /adder { {{#kw|set_by_lua}} {{#v|$res}} {{#x|"local a = tonumber(ngx.arg[1])}} {{#x|local b = tonumber(ngx.arg[2])}} {{#x|return a + b"}} {{#v|$arg_a}} {{#v|$arg_b}}; {{#kw|echo}} {{#v|$res}}; } ---- {{#v|$}} curl {{#x|'localhost/adder?a=25&b=75'}} 100 ---- {{#kw|location}} = /fib { {{#kw|set_by_lua}} {{#v|$res}} {{#x|"}} {{#x|function fib(n)}} {{#x|if n > 2 then}} {{#x|return fib(n-1) + fib(n-2)}} {{#x|else}} {{#x|return 1}} {{#x|end}} {{#x|end}} {{#x|local num = tonumber(ngx.arg[1])}} {{#x|return fib(num)}} {{#x|"}} {{#v|$arg_n}}; {{#kw|echo}} {{#v|$res}}; } ---- {{#v|$}} curl {{#x|'localhost/fib?n=10'}} 55 ---- {{#x|♡}} or use {{#ci|external}} Lua script file... ---- {{#kw|location}} = /fib { {{#kw|set_by_lua_file}} {{#v|$res}} {{#x|"conf/fib.lua"}} {{#v|$arg_n}}; {{#kw|echo}} {{#v|$res}}; } ---- {{#cm|-- conf/fib.lua file}} {{#kw|function}} fib(n) {{#kw|if}} n > 2 {{#kw|then}} {{#kw|return}} fib(n-1) + fib(n-2) {{#kw|else}} {{#kw|return}} 1 {{#kw|end}} {{#kw|end}} {{#kw|local}} num = {{#kw|tonumber}}(ngx.arg[1]) {{#kw|return}} fib(num) ---- {{#v|$}} {{#ci|Complex}} database cluster {{#x|hashing}} can also be done in Lua ---- {{#kw|http}} { {{#kw|upstream}} {{#x|A}} { {{#kw|drizzle_server}} ...; } {{#kw|upstream}} {{#x|B}} { {{#kw|drizzle_server}} ...; } {{#kw|upstream}} {{#x|C}} { {{#kw|drizzle_server}} ...; } ... } ---- {{#kw|location}} ~ {{#x|'^/user/(\d+)'}} { {{#kw|set}} {{#v|$uid}} {{#v|$1}}; {{#kw|set_by_lua_file}} {{#v|$backend}} {{#x|"conf/hash.lua"}} {{#v|$uid}}; {{#kw|if}} ({{#v|$backend}} = {{#x|''}}) { {{#kw|return}} 400; {{#kw|break}}; } {{#kw|drizzle_query}} {{#x|"select * from users}} {{#x|where uid=}}{{#v|$uid}}{{#x|"}}; {{#kw|drizzle_pass}} {{#v|$backend}}; {{#kw|rds_json}} on; } ---- {{#cm|-- hash.lua}} {{#kw|function}} hash(uid) {{#kw|if}} uid > 0 {{#kw|and}} uid <= 1200 {{#kw|then}} {{#kw|return}} {{#x|'A'}} {{#kw|end}} {{#kw|if}} uid > 1200 {{#kw|and}} uid <= 5300 {{#kw|then}} {{#kw|return}} {{#x|'B'}} {{#kw|end}} {{#kw|if}} uid > 5300 {{#kw|and}} uid <= 7100 {{#kw|then}} {{#kw|return}} {{#x|'C'}} {{#kw|end}} {{#kw|return}} '' {{#kw|end}} {{#kw|return}} hash({{#kw|tonumber}}(ngx.arg[1])) ---- {{#x|♡}} Use {{#x|Lua}} to code up nginx {{#ci|content handler}} directly ---- {{#kw|location}} = /lua { {{#kw|content_by_lua}} {{#x|'ngx.say("Hello, Lua!")'}}; } ---- {{#v|$}} curl 'localhost/lua' {{#x|Hello, Lua!}} ---- {{#x|♡}} ...and we can read arbitrary {{#ci|nginx variables}} from within our Lua {{#x|content}} handler! ---- {{#kw|location}} = /hello { {{#kw|content_by_lua}} {{#x|'local who = ngx.var.arg_who}} {{#x|ngx.say("Hello, ", who, "!")';}} } ---- {{#v|$}} curl 'localhost/hello?who={{#x|agentzh}}' Hello, {{#x|agentzh}}! ---- {{#x|♡}} We can also put Lua code into {{#ci|external}} .lua {{#x|file}} to eliminate escaping nightmare. ---- {{#kw|location}} /foo { ... {{#kw|content_by_lua_file}} {{#x|/path/to/your/lua-file.lua}}; } ---- {{#x|♡}} We can also issue nginx {{#ci|subrequests}} diredctly from within Lua content handler {{#i|now}}! ---- {{#kw|location}} /other { {{#kw|echo}} \"hello, world\"; } {{#cm|# transparent non-blocking I/O in Lua}} {{#kw|location}} /lua { {{#kw|content_by_lua}} {{#x|'}} {{#x|local res = ngx.location.capture("/other")}} {{#x|if res.status == 200 then}} {{#x|ngx.print(res.body)}} {{#x|end'}}; } ---- {{#v|$}} curl 'localhost/lua' {{#x|hello, world}} ---- {{#x|♡}} We'd call this whole set of nginx modules {{#ci|ngx_openresty}} and our work is heavily {{#x|funded}} by {{#i|Taobao.com}}. {{img src="images/taobao-logo.png" width="140" height="35"}} ---- {{#x|♡}} It is already {{#x|powering}} lz.taobao.com. {{img src="images/lz-logo.gif" width="234" height="47"}} ---- {{img src="images/lz-login.png" width="1059" height="723"}} ---- {{img src="images/lz-shopsummary.png" width="1059" height="723"}} ---- {{img src="images/lz-itemflow.png" width="1059" height="723"}} ---- {{img src="images/lz-srccomp.png" width="1059" height="723"}} ---- {{#x|♡}} Generate nginx.conf from Perl TT2 templates ---- {{#cm|-- META-conf.lua }} apiproxy = { enable = {{#x|true}}, enable_lightface = {{#x|true}}, enable_admin = {{#x|false}}, {{#cm|-- MUST disable in production}} enable_devel = {{#x|false}}, {{#cm|-- MUST disable in production}} host = {{#x|'api.linezing.com'}}, port = {{#x|80}}, log_path = {{#x|'/opt/apiproxy/logs'}}, conf_path = {{#x|'/opt/apiproxy/conf'}}, }, nginx = { ... ---- {{#x|♡}} {{#ci|Generate}} nginx.conf from Perl TT2 {{#x|templates}} ---- {{#cm|-- nginx.conf.tt}} ... http { default_type text/plain; keepalive_timeout {{#kw|[% nginx.keepalive_timeout %]}}; access_log {{#kw|[% nginx.enable_access_log ?}} {{#kw|apiproxy.log_path _ '/access.log' : 'off' %]}}; gzip {{#kw|[% nginx.enable_gzip ? 'on' : 'off' %]}}; gzip_min_length 1000; gzip_types application/x-javascript text/css application/json; gzip_disable \"msie6\"; ... ---- {{#x|♡}} {{#ci|Generate}} Lua code by our LZSQL {{#ci|compiler}} ---- {{#v|$}} {{#x|lzsql-compile}} -c -O2 -n src/*.lzsql {{#v|$}} {{#x|lzsql-link}} -m lightface.core -o lightface/core.lua src/*.oul ---- {{#cm|--/=/view/itemdailyflow/type/trend}} {{#kw|int}} {{#v|$uid}}; {{#kw|text}} {{#v|$begin}}, {{#v|$end}}, {{#v|$today}}, {{#v|$url_index}}; {{#kw|symbol}} {{#v|$db}}; {{#kw|location}} {{#v|$lz_report}}; {{#v|@hist}} := {{#kw|select}} ... {{#kw|from}} LZDB.dpunit_purl_result({{#v|$db}}, {{#v|$begin}}, {{#v|$end}}, {{#v|$uid}}) as a ... at {{#v|$lz_report}}; {{#v|@rt}} := {{#kw|select}} name, count(name) {{#kw|from}} LZRTI.getPurl({{#v|$end}} as day, {{#v|$uid}}) {{#kw|group by}} name ... {{#kw|return}} {{#kw|select}} ... {{#kw|from}} {{#v|@hist}} union all {{#v|@rt}} ... ---- {{#x|♡}} {{#ci|Join}} us at the {{#x|OpenResty}} Google Group {{http://groups.google.com/group/OpenResty}} and the {{#x|nginx-devel}} mailing list {{http://nginx.org/mailman/listinfo/nginx-devel}} ---- {{#x|♡}} or just {{#ci|catch}} us on IRC: irc.freenode.net {{#x|#nginx}} {{#x|#openresty}} ---- {{#ci|☺}} {{#i|Any questions}}? {{#ci|☺}}