OpenResty: turning NGINX into a full-fledged application server

OpenResty: turning NGINX into a full-fledged application serverWe re-publish the transcript of the report from the conference HighLoad++ 2016, which was held in Skolkovo near Moscow on November 7-8 last year. Vladimir Protasov tells how to extend NGINX functionality with OpenResty and Lua.

Hello everyone, my name is Vladimir Protasov, I work for Parallels. I'll tell you a little about myself. I spend three quarters of my life writing code. I became a programmer to the core in the literal sense: I sometimes see code in my dreams. A quarter of life is industrial development, writing code that goes straight into production. Code that some of you use but don't know it.

To let you know how bad it was. When I was a little junior, I came in and they gave me these two terabyte databases. It is now here for everyone highload. I went to conferences and asked: “Guys, tell me, do you have big data, is everything cool? How many bases do you have there? They answered me: “We have 100 gigabytes!” I said: “Cool, 100 gigabytes!” And I thought to myself how to neatly save the poker face. You think, yes, the guys are cool, and then you come back and tinker with these multi-terabyte databases. And this is being a junior. Can you imagine what a hit it is?

I know more than 20 programming languages. This is what I had to figure out in the course of work. They give you code in Erlang, in C, in C++, in Lua, in Python, in Ruby, in something else, and you have to cut it all. In general, I had to. It was not possible to calculate the exact number, but somewhere around 20 the number was lost.

Since everyone here knows what Parallels is and what we do, I will not talk about how cool we are and what we do. I will only tell you that we have 13 offices around the world, more than 300 employees, development in Moscow, Tallinn and Malta. If you wish, you can take and move to Malta, if it is cold in winter and you need to warm your back.

Specifically, our department writes in Python 2. We are in business and we have no time to introduce fashionable technologies, so we suffer. We have Django, because it has everything, and we took the excess and threw it away. Also MySQL, Redis and NGINX. We also have a lot of other cool stuff. We have MongoDB, we have rabbits running around, we just don’t have anything - but it’s not mine, and I don’t do it.

OpenResty

I told about myself. Let's see what I'm going to talk about today:

  • What is OpenResty and what is it eaten with?
  • Why reinvent the wheel when we have Python, NodeJS, PHP, Go and other cool stuff that everyone is happy with?
  • And a few real life examples. I had to cut the report down a lot, because I got it for 3,5 hours, so there will be few examples.

OpenResty is NGINX. Thanks to him, we have a full-fledged web server, which is well written, it works fast. I think most of us use NGINX in production. You all know that he is fast and cool. They made cool synchronous I / O in it, so we don’t need to cycle anything in the same way that gevent was cycled in Python. Gevent is cool, cool, but if you write C-code and something goes wrong with gevent, you will go crazy debugging it. I had experience: it took two whole days to figure out what went wrong there. If someone hadn't dug for a few weeks before, found the problem, wrote it on the Internet, and Google didn't find it, then we would have gone completely crazy.

NGINX already does caching and static content. You do not need to worry about how to do it humanly, so that you don’t slow down somewhere, so that you don’t lose descriptors somewhere. Nginx is very convenient to deploy, you don't need to think about what to take - WSGI, PHP-FPM, Gunicorn, Unicorn. Nginx was installed, given to the admins, they know how to work with it. Nginx handles requests in a structured way. I will talk about this a little later. In short, he has a phase when he just accepted the request, when he processed and when he gave the content to the user.

Nginx is cool, but there is one problem: it is not flexible enough even with all those cool features that the guys pushed into the config, despite the fact that it can be customized. This power is not enough. Therefore, the guys from Taobao once upon a time, I think about eight years ago, built Lua into it. What does he give?

  • Size. It is small. LuaJIT gives somewhere around 100-200 kilobytes of memory overhead and minimal performance overhead.
  • Speed. The LuaJIT interpreter is close to C in many situations, in some situations it loses to Java, in some it overtakes it. For a while, it was considered state of the art, the coolest JIT compiler. Now there are cooler ones, but they are very heavy, for example, the same V8. Some JS interpreters and Java HotSpot are faster at some points, but still lose at some points.
  • Easy to learn. If you have, say, a Perl codebase and you're not Booking, you won't find Perl programmers. Because they are not there, they were all taken away, and it is long and difficult to teach them. If you want programmers for something else, they may also have to be retrained or found. In the case of Lua, everything is simple. Lua can be learned by any junior in three days. It took me about two hours to figure it out. Two hours later, I was already writing code in production. About a week later, he went straight to production and left.

As a result, it looks like this:

OpenResty: turning NGINX into a full-fledged application server

There's a lot here. OpenResty has assembled a bunch of modules, both luash and engins. And you have everything ready - deployed and working.

Examples

Enough of the lyrics, let's move on to the code. Here is a little Hello World:

OpenResty: turning NGINX into a full-fledged application server

What is there? this is the engins location. We don’t worry, we don’t write our own routing, we don’t take some ready-made one - we already have it in NGINX, we live well and lazily.

content_by_lua_block is a block that says that we are serving content using a Lua script. We take an engins variable remote_addr and slip it into string.format... This is the same as sprintf, only in Lua, only correct. And we give it to the client.

As a result, it will look like this:

OpenResty: turning NGINX into a full-fledged application server

But back to the real world. In production, no one deploys Hello World. Our application usually goes to the database or somewhere else and most of the time it waits for a response.

OpenResty: turning NGINX into a full-fledged application server

Just sits and waits. It's not very good. When 100.000 users come, it's very hard for us. Therefore, let's use a simple application as an example. We will look for pictures, for example, cats. Only we will not just search, we will expand the keywords and, if the user searched for "kittens", we will find cats, fluffies, and so on. First we need to get the request data on the backend. It looks like this:

OpenResty: turning NGINX into a full-fledged application server

Two lines allow you to pick up GET parameters, no complications. Then we, for example, get this information from a database with a table by keyword and extension using a regular SQL query. Everything is simple. It looks like this:

OpenResty: turning NGINX into a full-fledged application server

We connect the library resty.mysql, which we already have in the kit. We don't need to install anything, everything is ready. Specify how to connect and make an SQL query:

OpenResty: turning NGINX into a full-fledged application server

It's a little scary, but it works. Here 10 is the limit. We pull out 10 records, we are lazy, we don't want to show more. In SQL, I forgot about the limit.

Then we find images for all queries. We collect a bunch of requests and fill in a Lua table called reqs, and do ngx.location.capture_multi.

OpenResty: turning NGINX into a full-fledged application server

All these requests go in parallel, and the answers are returned to us. The running time is equal to the response time of the slowest one. If we all shoot back in 50 milliseconds, and we sent a hundred requests, then we will receive a response in 50 milliseconds.

Since we are lazy and don't want to write HTTP handling and caching, we will make NGINX do everything for us. As you saw, there was a request for url/fetch, here he is:

OpenResty: turning NGINX into a full-fledged application server

We make simple proxy_pass, specify where to cache, how to do it, and everything works for us.

But this is not enough, we still need to give the data to the user. The simplest idea is to serialize everything to JSON, easily, in two lines. We give Content-Type, we give JSON.

But there is one difficulty: the user does not want to read JSON. We need to attract front-end developers. Sometimes we don't feel like doing it at first. Yes, and SEO specialists will say that if we are looking for pictures, then they do not care. And if we give them some content, they will say that our search engines do not index anything.

What to do with it? Of course, we will give the user HTML. Generating with handles is not comme il faut, so we want to use templates. There is a library for this lua-resty-template.

OpenResty: turning NGINX into a full-fledged application server

You must have seen the three dreaded letters OPM. OpenResty comes with its own package manager, through which you can install a bunch of different modules, in particular, lua-resty-template. It's a simple template engine similar to Django templates. There you can write code and do variable substitution.

As a result, everything will look something like this:

OpenResty: turning NGINX into a full-fledged application server

We took the data and rendered the template again in two lines. The user is happy, got cats. Since we expanded the request, he also received a fur seal for kittens. You never know, maybe he was looking for it, but he could not formulate his request correctly.

Everything is cool, but we are in development, and we don’t want to show users yet. Let's do an authorization. To do this, let's see how NGINX handles the request in terms of OpenResty:

  • First phase - access, when the user just came, and we looked at him by headers, by IP address, by other data. You can immediately chop it off if we do not like it. This can be used for authorization, or if we receive a lot of requests, we can easily chop them at this phase.
  • rewrite. Rewriting some request data.
  • happy. We give content to the user.
  • header filter. Change the response headers. If we used proxy_pass, we can rewrite some headers before giving it to the user.
  • body filter. We can change the body.
  • log - logging. It is possible to write logs in elasticsearch without an additional layer.

Our authorization will look something like this:

OpenResty: turning NGINX into a full-fledged application server

We'll add it to that location, which we described before, and put the following code there:

OpenResty: turning NGINX into a full-fledged application server

We look to see if we have a cookie token. If not, then we throw on authorization. Users are cunning and may guess that a cookie token needs to be set. Therefore, we will also put it in Redis:

OpenResty: turning NGINX into a full-fledged application server

The code for working with Redis is very simple and no different from other languages. At the same time, all input / output, what is there, what is here, it is not blocking. If you write synchronous code, then it works asynchronously. Like with gevent, only done well.

OpenResty: turning NGINX into a full-fledged application server

Let's do the authorization itself:

OpenResty: turning NGINX into a full-fledged application server

We say that we need to read the request body. We receive POST arguments, check that the login and password are correct. If incorrect, then we throw on authorization. And if they are correct, then we write the token to Redis:

OpenResty: turning NGINX into a full-fledged application server

Do not forget to set the cookie, this is also done in two lines:

OpenResty: turning NGINX into a full-fledged application server

The example is simple, speculative. Of course, we will not make a service that shows cats to people. But who knows us. So let's go over what can be done in production.

  • Minimalist backend. Sometimes we need to give out quite a bit of data to the backend: somewhere we need to substitute the date, somewhere we need to display some kind of list, say how many users are on the site now, screw on a counter or statistics. Something so small. Some minimal pieces can be made very easily. This will be fast, easy and great.
  • Data preprocessing. Sometimes we want to embed ads in our page, and we take these ads with API requests. This is very easy to do here. We do not load our backend, which is already working hard. You can pick up and collect here. We can mold some JS or, on the contrary, unstick, preprocess something before giving it to the user.
  • Facade for microservice. This is also a very good case, I implemented it. Before that, I worked for Tenzor, an electronic reporting company that provides reporting for about half of the legal entities in the country. We have made a service, many things are done there using the same mechanism: routing, authorization, and more.
    OpenResty can be used as the glue for your microservices to provide a single access to everything and a single interface. Since microservices can be written in such a way that you have Node.js here, you have PHP here, you have Python here, there is some Erlang thing here, we understand that we don’t want to rewrite the same code everywhere. Therefore, OpenResty can be plugged into the front.
  • Statistics and analytics. Usually NGINX is at the entrance, and all requests go through it. It is in this place that it is very convenient to collect. You can immediately calculate something and throw it somewhere, for example, the same Elasticsearch, Logstash, or just write it to the log and then send it somewhere.
  • Multi-User Systems. For example, online games are also very good to do. Today in Cape Town Alexander Gladysh will tell you how to quickly prototype a multiplayer game using OpenResty.
  • Request Filtering (WAF). Now it is fashionable to make all sorts of web application firewalls, there are many services that provide them. With the help of OpenResty, you can make yourself a web application firewall, which will simply and easily filter requests according to your requirements. If you have Python, then you understand that PHP will definitely not be injected to you, unless, of course, you spawn it anywhere from the console. You know you have MySQL and Python. Probably, here they can try to do some kind of directory traversal and inject something into the database. Therefore, you can filter out dumb requests quickly and cheaply right at the front.
  • Community. Since OpenResty is based on NGINX, it has a bonus - this is NGINX community. It's very large, and a lot of the questions you'll have at first have already been answered by the NGINX community.

    Lua developers. Yesterday I talked with the guys who came to the HighLoad ++ training day and heard that only Tarantool is written in Lua. This is not so, a lot of things are written in Lua. Examples: OpenResty, Prosody XMPP server, Love2D game engine, Lua is scripted in Warcraft and elsewhere. There are a lot of Lua developers, they have a large and responsive community. All my Lua questions were answered within a few hours. When you write to the mailing list, literally in a few minutes there are already a bunch of answers, they describe what and how, what's what. It's great. Unfortunately, such a kind sincere community is not everywhere.
    OpenResty has GitHub, where you can open an issue if something breaks. There is a mailing list on Google Groups where you can discuss general issues, there is a mailing list in Chinese - you never know, maybe you don’t speak English, but you have knowledge of Chinese.

Conclusion

  • I hope I was able to convey that OpenResty is a very convenient framework tailored for the web.
  • It has a low threshold of entry, since the code is similar to what we write, the language is quite simple and minimalistic.
  • It provides asynchronous I/O without callbacks, we will not have noodles as we can sometimes write in NodeJS.
  • It has an easy deployment, because we only need NGINX with the right module and our code, and everything immediately works.
  • Large and responsive community.

I did not tell in detail how routing is done, it turned out to be a very long story.

Thank you for attention!

Play Video

Vladimir Protasov - OpenResty: turning NGINX into a full-fledged application server

Source: habr.com

Add a comment