Line # Revision Author
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;