1 |
8 |
ahitrov@rambler.ru |
package Contenido::Apache; |
2 |
|
|
|
3 |
|
|
# ---------------------------------------------------------------------------- |
4 |
|
|
# Здесь будут храниться все хэндлеры Apache... |
5 |
|
|
# ---------------------------------------------------------------------------- |
6 |
|
|
|
7 |
|
|
use strict; |
8 |
|
|
use warnings; |
9 |
|
|
use locale; |
10 |
|
|
|
11 |
|
|
use vars qw($VERSION); |
12 |
|
|
$VERSION = '7.0'; |
13 |
|
|
|
14 |
|
|
use Apache::Constants; |
15 |
|
|
use Apache::Request; |
16 |
|
|
|
17 |
|
|
use Contenido::Globals; |
18 |
|
|
use Contenido::Project; |
19 |
|
|
use Contenido::Request; |
20 |
|
|
use Contenido::State; |
21 |
|
|
|
22 |
|
|
use Contenido::Keeper; |
23 |
|
|
use Contenido::Object; |
24 |
|
|
use Contenido::User; |
25 |
|
|
use Contenido::Document; |
26 |
|
|
use Contenido::Section; |
27 |
|
|
use Contenido::Link; |
28 |
|
|
|
29 |
|
|
use Contenido::Init; |
30 |
|
|
use Contenido::Logger; |
31 |
|
|
|
32 |
|
|
# ---------------------------------------------------------------------------- |
33 |
|
|
# Хэндлер, который запускается при старте каждого нового дочернего процесса |
34 |
|
|
# web-сервера. |
35 |
|
|
# ---------------------------------------------------------------------------- |
36 |
|
|
sub child_init { |
37 |
|
|
my $r = shift; |
38 |
|
|
|
39 |
|
|
$log->info("Обрабатываем рождение дочернего процесса Apache") if $DEBUG; |
40 |
|
|
|
41 |
|
|
my $project_keeper_module; |
42 |
|
|
eval { |
43 |
|
|
$project_keeper_module=$state->project().'::Keeper'; |
44 |
|
|
$keeper = $project_keeper_module->new($state); |
45 |
|
|
}; |
46 |
|
|
if ($@) { |
47 |
|
|
$log->error("Не могу инициализировать $project_keeper_module из-за: $@"); |
48 |
|
|
die; |
49 |
|
|
} |
50 |
|
|
|
51 |
|
|
for my $plugin ($state->project, split(/\s+/, $state->plugins)) { |
52 |
|
|
my $class = $plugin.'::Apache'; |
53 |
|
|
eval { $class->child_init($r); }; |
54 |
|
|
if ( $@ ) { |
55 |
|
|
$log->error("Не могу выполнить метод child_init плагина $plugin ($class) по причине '$@'"); |
56 |
|
|
} |
57 |
|
|
} |
58 |
|
|
|
59 |
|
|
return OK; |
60 |
|
|
} |
61 |
|
|
|
62 |
|
|
# ---------------------------------------------------------------------------- |
63 |
|
|
# Хэндлер, который запускается в самом начале обработки запроса пользователя. |
64 |
|
|
# ---------------------------------------------------------------------------- |
65 |
|
|
sub request_init { |
66 |
|
|
my $r = shift; |
67 |
|
|
|
68 |
|
|
$r = ref($r) eq 'Apache::Request' ? $r : Apache::Request->instance($r); |
69 |
|
|
|
70 |
|
|
my $URI = $r->uri; |
71 |
|
|
my $ARGS = $r->args; |
72 |
|
|
|
73 |
|
|
$log->info("Начало обработки запроса ".$URI.($ARGS ? "?$ARGS":'')) if $DEBUG; |
74 |
|
|
|
75 |
|
|
$state->_refresh_(); |
76 |
|
|
|
77 |
|
|
$request = Contenido::Request->new($state); |
78 |
|
|
|
79 |
|
|
if ($DEBUG) { |
80 |
|
|
$request->{_start} = Time::HiRes::time(); |
81 |
|
|
$Contenido::Globals::DB_TIME = 0; |
82 |
|
|
$Contenido::Globals::CORE_TIME = 0; |
83 |
|
|
$Contenido::Globals::DB_COUNT = 0; |
84 |
|
|
$Contenido::Globals::RPC_TIME = 0; |
85 |
|
|
} |
86 |
|
|
|
87 |
|
|
if (ref($r)) { |
88 |
|
|
|
89 |
|
|
my %headers = $r->headers_in(); |
90 |
|
|
my $ip = $headers{'X-Real-IP'} ? $headers{'X-Real-IP'} : $r->connection->remote_ip(); |
91 |
|
|
my @ips = split(/\s*,\s*/, $ip); |
92 |
|
|
$ip = $ips[ $#ips ]; |
93 |
|
|
|
94 |
|
|
$request->set_properties ( |
95 |
|
|
'uri' => $URI||'', |
96 |
|
|
'query' => $r->args()||'', |
97 |
|
|
'ip' => $ip||'', |
98 |
|
|
'user' => $r->connection->user()||'', |
99 |
|
|
'http_host' => $ENV{HTTP_HOST}, |
100 |
|
|
'r' => $r, |
101 |
|
|
) |
102 |
|
|
} |
103 |
|
|
$project->restore($keeper); |
104 |
|
|
|
105 |
|
|
for my $plugin ($state->project, split(/\s+/, $state->plugins)) { |
106 |
|
|
my $class = $plugin.'::Apache'; |
107 |
|
|
eval { $class->request_init($r); }; |
108 |
|
|
if ( $@ ) { |
109 |
|
|
$log->error("Не могу выполнить метод request_init плагина $plugin ($class) по причине '$@'"); |
110 |
|
|
} |
111 |
|
|
} |
112 |
|
|
|
113 |
|
|
return OK; |
114 |
|
|
} |
115 |
|
|
|
116 |
|
|
sub is_valid_request { |
117 |
|
|
my $r = shift; |
118 |
|
|
if ($r->uri =~ /^(?:\/i\/|\/images\/|\/binary\/)/ or ($r->content_type && $r->content_type !~ m#(?:^text/|javascript|json|^httpd/unix-directory)#i)) { |
119 |
|
|
return 0; |
120 |
|
|
} else { |
121 |
|
|
return 1; |
122 |
|
|
} |
123 |
|
|
} |
124 |
|
|
#завершение запроса (отрабатывает ПОСЛЕ отдачи ответа... так что можно буз ущерба интерактивности тут всяие полезности повызывать |
125 |
|
|
sub cleanup { |
126 |
|
|
my $r = shift; |
127 |
|
|
|
128 |
|
|
#сброс глобальных переменных запроса |
129 |
|
|
$user = undef; |
130 |
|
|
$session = undef; |
131 |
|
|
|
132 |
|
|
return Apache::Constants::DECLINED unless Contenido::Apache::is_valid_request($r); |
133 |
|
|
|
134 |
|
|
#установка отложенных данных в memcached |
135 |
|
|
if ($state->{memcached_enable} and $request->{_to_memcache}) { |
136 |
|
|
while ( my ($key, $values) = each(%{$request->{_to_memcache}}) ) { |
137 |
|
|
my ($value, $expire, $mode) = @$values; |
138 |
|
|
if (ref($key) or !$key) { |
139 |
|
|
$log->warning("bad key value in set ($key)"); |
140 |
|
|
next; |
141 |
|
|
} |
142 |
|
|
$mode ||= 'set'; |
143 |
|
|
if ($mode eq 'add') { |
144 |
|
|
$keeper->{MEMD}->add($key, $value, $expire); |
145 |
|
|
} elsif ($mode eq 'delete') { |
146 |
|
|
$keeper->{MEMD}->delete($key); |
147 |
|
|
#по умолчанию set |
148 |
|
|
} else { |
149 |
|
|
$keeper->{MEMD}->set($key, $value, $expire); |
150 |
|
|
} |
151 |
|
|
} |
152 |
|
|
} |
153 |
|
|
|
154 |
|
|
for my $plugin ($state->project, split(/\s+/, $state->plugins)) { |
155 |
|
|
my $class = $plugin.'::Apache'; |
156 |
|
|
next unless ($class->can('cleanup')); |
157 |
|
|
eval { $class->cleanup($r); }; |
158 |
|
|
if ( $@ ) { |
159 |
|
|
$log->error("Не могу выполнить метод request_init плагина $plugin ($class) по причине '$@'"); |
160 |
|
|
} |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
# отсоединяемся от базы проекта и плагинов |
164 |
|
|
# (если не используются постоянные соденинения) |
165 |
|
|
# также сбрасываем потенциально зависшие транзакционные соединения |
166 |
|
|
unless ($state->db_type eq 'none') { |
167 |
|
|
$keeper->shutdown unless $state->db_keepalive; |
168 |
|
|
$keeper->{_connect_ok} = 0; |
169 |
|
|
$keeper->t_shutdown(1); |
170 |
|
|
} |
171 |
|
|
for (split /\s+/, $state->plugins) { |
172 |
|
|
next unless $keeper->{$_}; |
173 |
|
|
next if $state->{$_}->db_type eq 'none'; |
174 |
|
|
$keeper->{$_}->shutdown unless $state->{$_}->db_keepalive; |
175 |
|
|
$keeper->{$_}->{_connect_ok} = 0; |
176 |
|
|
$keeper->{$_}->t_shutdown(1); |
177 |
|
|
} |
178 |
|
|
|
179 |
|
|
if ($DEBUG) { |
180 |
|
|
my $finish = Time::HiRes::time(); |
181 |
|
|
my $time = int(10000*($finish-$request->{_start}))/10; |
182 |
|
|
my $db_time = int(10000*($Contenido::Globals::DB_TIME)/10); |
183 |
|
|
my $core_time = int(10000*($Contenido::Globals::CORE_TIME)/10); |
184 |
|
|
my $rpc_time = int(10000*($Contenido::Globals::RPC_TIME)/10); |
185 |
|
|
$log->info("DEBUG: $$ ".__PACKAGE__." ".scalar(localtime())." ".($r->uri || '').($r->args ? '?'.$r->args : '')." worked $time ms, database time $db_time ms, rpc time $rpc_time ms, core time: $core_time ms, db requests count: $Contenido::Globals::DB_COUNT"); |
186 |
|
|
} |
187 |
|
|
|
188 |
|
|
$request = undef; |
189 |
|
|
|
190 |
|
|
return OK; |
191 |
|
|
} |
192 |
|
|
|
193 |
|
|
# ---------------------------------------------------------------------------- |
194 |
|
|
# Хэндлер, который запускается при окончании работы дочернего процесса |
195 |
|
|
# web-сервера... |
196 |
|
|
# ---------------------------------------------------------------------------- |
197 |
|
|
sub child_exit { |
198 |
|
|
my $r = shift; |
199 |
|
|
|
200 |
|
|
for my $plugin ($state->project, split(/\s+/, $state->plugins)) { |
201 |
|
|
my $class = $plugin.'::Apache'; |
202 |
|
|
eval { $class->child_exit($r); }; |
203 |
|
|
if ( $@ ) { |
204 |
|
|
$log->error("Не могу выполнить метод child_exit плагина $plugin ($class) по причине '$@'"); |
205 |
|
|
} |
206 |
|
|
} |
207 |
|
|
unless ($state->db_type eq 'none') { |
208 |
|
|
$keeper->shutdown; |
209 |
|
|
} |
210 |
|
|
|
211 |
|
|
$log->info("Обрабатываем смерть дочернего процесса Apache.") if ($DEBUG); |
212 |
|
|
|
213 |
|
|
return OK; |
214 |
|
|
} |
215 |
|
|
|
216 |
|
|
|
217 |
|
|
# ---------------------------------------------------------------------------- |
218 |
|
|
# Хэндлер для аутентификации. |
219 |
|
|
# - Если это делается для редакторского интерфейса, то группа |
220 |
|
|
# пользователей должна быть группой 1 (редакторы) |
221 |
|
|
# ---------------------------------------------------------------------------- |
222 |
|
|
sub authentication { |
223 |
|
|
my $r = shift; |
224 |
|
|
|
225 |
|
|
return Apache::Constants::DECLINED unless Contenido::Apache::is_valid_request($r); |
226 |
|
|
return FORBIDDEN if $state->db_type eq 'none'; |
227 |
|
|
|
228 |
|
|
my ($res, $sent_pw) = $r->get_basic_auth_pw(); |
229 |
|
|
return $res if $res != OK; |
230 |
|
|
|
231 |
|
|
my $username = $r->connection->user(); |
232 |
|
|
|
233 |
|
|
$keeper->db_connect unless $keeper->is_connected; |
234 |
|
|
$user = $keeper->get_user_by_login($username); |
235 |
|
|
$keeper->shutdown unless $state->db_keepalive; |
236 |
|
|
|
237 |
|
|
if (ref($user) && ($user->login() eq $username) && ($user->passwd() eq $sent_pw) && $user->passwd() && $user->status == 1) { |
238 |
|
|
return OK; |
239 |
|
|
} else { |
240 |
|
|
$log->warning("Попытка авторизации с неверной парой логин/пароль ($username, $sent_pw)"); |
241 |
|
|
$r->note_basic_auth_failure(); |
242 |
|
|
return AUTH_REQUIRED; |
243 |
|
|
} |
244 |
|
|
} |
245 |
|
|
|
246 |
|
|
1; |