📜 ⬆️ ⬇️

Generating a unique user ID using Nginx

I welcome you, habrachiteli!

I'll tell you about one problem that confronted me, and how I solved it.

Immediately make a reservation - an hour search in G and in I did not bring a satisfying result, but in the next hour my own solution was implemented.
')
All this is nothing more than an experiment - there are white spots both in ideas and in implementation, at this stage it is necessary to understand whether to live or not to live.


The essence of the task was reduced to the fact that I needed to uniquely identify a visitor regardless of the nature and religion of the components of the system (Web-project). And to make it as simple as possible, quickly and without a large overhead projector in speed.
It is important to note that the user is not authorized by login / password or else .

As a web server and primary load balancer, I have Nginx.

In my system for php, php-fpm is used via fastcgi, c ++ business logic server also works via fastcgi.



Here is an approximate scheme:
image

I came to the problem of global identification after I was added to the system with a ++ server that catches some requests for myself.

Since the request to the plus server can be earlier than the request to the php backend, I could not come up with / find a convenient, fast, and most importantly a beautiful implementation of user identification by PHPSESSID.

I decided to implement all this by means of Nginx, or rather, by its perl module.
This is so far the only significant minus of the solution - this module is not compiled by default, that is, you have to rebuild nginx:

$ ./configure --with-http_perl_module

Now the task is reduced to several stages:

Step 1. Write a pearl-barley module that generates and checks for the presence of an identifier

1. Believe for the presence of a set identifier, for example, in cookies.
The identifier can be transmitted not only in cookies, in my case it is there.

2a Check for valid identifier
The ID is checked for validity, i.e. it is checked whether it is generated by the module or an error has crept in.
This is required for the further work of my components.
There are also some ideas on how to use this identifier for load balancing on the client side.

If the identifier is valid, complete the generation procedure.
If we don’t generate a new one (p. 2b), then perhaps we should somehow react differently, we need to think.

2b. Generate an identifier
Here an identifier is generated using randomly sequences + some data from the user.
Now the identifier consists of 32 bytes (hexstr) of a random sequence and 32 bytes (hexstr) of the digest (see below).
Of course, this can and will be reduced.

 package session; use strict; use Digest::MD5 qw(md5_hex); my $secret_key = '__TOP_SECRET__KEEP_IT_IN_BANK__'; # ,     my $cookie_name = 'SID'; #      my $rand_len = 16; #    my $hex_length = $rand_len * 2; my $hex_mask = "H".$hex_length; my $digest_length = 32; #    hexstr - 32 . #    sub hash { # data -  , ng - nginx . my ($data,$ng) = @_; #      ip  return md5_hex($data."_".$secret_key."_".$ng->header_in("User-Agent")."_".$ng->remote_addr); } #    .  MAN, , /dev/random  . #           nginx. #     . open(my $rand, '<', "/dev/random"); sub gen { # ng - nginx  my $ng = shift; #       #  32  (hexstr)   #  32  (hexstr)  (. sub hash) if ($ng->header_in("Cookie")=~/$cookie_name=(\w{$hex_length})(\w{$digest_length});?/) { if ($2 eq hash($1, $ng)) { return "$1$2"; } } #    read($rand, my $data, $rand_len); #    hexstr my $h = unpack($hex_mask, $data); #     (. sub hash) my $id = $h.hash($h, $ng); #   $ng->header_out("Set-Cookie","$cookie_name=$id;"); #   nginx return $id; } 1; __END__ 


Stage 2. Make friends system components with an identifier

In order to connect this module, we need nginx.conf
     http {
         ...
         perl_modules conf / perl;  # directory where our module is stored
         perl_require session.pm;  # module file
         perl_set $ sid session :: gen;  # variable in which the identifier will be saved
         ...
 
         server { 
             ..
             location ~ * \. php $ {
                 root html / www;
                 fastcgi_pass http: // backend_upstreams;
                 fastcgi_index index.php;
                 fastcgi_param SCRIPT_FILENAME $ document_root / $ fastcgi_script_name;
                 include fastcgi_params;
                 fastcgi_param SID $ sid;  # pass the identifier via FastCGI to the backend
             }
            
             location ~ * \. tst $ {            
                 fastcgi_pass unix: / tmp / cpp_server;
                 include fastcgi_params;
                 fastcgi_param SID $ sid;  # pass the identifier by FastCGI to the backend
             }
         }
         ...
     }


Stage 3. Checking that we do not overwhelm all nafig

A small load test was written. I used the standard barley benchmark.
 #!/usr/bin/perl use strict; use Benchmark; use Digest::MD5 qw(md5_hex); my $secret_key = '__TOP_SECRET__KEEP_IT_IN_BANK__'; my $cookie_name = 'SID'; my $rand_length = 16; my $hex_mask = "H".($rand_length * 2); open(my $rand, '<', "/dev/random"); sub hash { my ($data) = @_; my $hash = md5_hex($data."_".$secret_key."_Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7_127.0.0.1"); return $hash; } sub gen { read($rand, my $data, $rand_length); my $h = unpack($hex_mask,$data); my $id = $h.hash($h); my $ng = "$cookie_name=$id;"; return $ng; } my $t0 = new Benchmark; for (my $i =0; $i < 1000000;++$i) { gen(); } my $t1 = new Benchmark; my $td = timediff($t1, $t0); print "Total:".timestr($td)."\n"; 


The result of the work on 600Mhz VDS:
Total: 6 wallclock secs (5.75 usr + 0.30 sys = 6.05 CPU)
Those. it takes ~ 6 * 10 -6 seconds to generate one identifier.
Suppose the worst case test + generation per request = 12 * 10 -6 seconds.
I didn’t test the rest, but of course there is some where to try.

Php


Access from php-backend to the identifier - $ _SERVER ['SID'];
You can also set this identifier as session_id
 <? session_id($_SERVER['SID']); session_name('SID'); //         PHPSESSID, SID session_start(); ?> 


In this case, if the session is stored for example in the database or Memcache, then all system components will have access to the session data (although I see a problem about locking the session recording).

UPD:

Why not ngx_http_userid_module


Thanks to Demetros for the right questions.

There are two very significant reasons why this module does not fit ( more ):
  1. No control of the validity of the user ID
  2. At the first request it is not possible to transfer uid to backend

Source: https://habr.com/ru/post/135777/


All Articles