line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Apache::Session::Counted; |
2
|
1
|
|
|
1
|
|
797
|
use Apache::Session::Serialize::Storable; |
|
1
|
|
|
|
|
3392
|
|
|
1
|
|
|
|
|
24
|
|
3
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
20
|
|
5
|
1
|
|
|
1
|
|
4
|
use vars qw(@ISA); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
39
|
|
6
|
|
|
|
|
|
|
@ISA = qw(Apache::Session); |
7
|
1
|
|
|
1
|
|
4
|
use vars qw($VERSION $RELEASE_DATE); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
42
|
|
8
|
|
|
|
|
|
|
$VERSION = '1.119'; |
9
|
|
|
|
|
|
|
$RELEASE_DATE = q$Date: 2002/04/15 12:39:07 $; |
10
|
|
|
|
|
|
|
|
11
|
1
|
|
|
1
|
|
454
|
use Apache::Session 1.50; |
|
1
|
|
|
|
|
1509
|
|
|
1
|
|
|
|
|
28
|
|
12
|
1
|
|
|
1
|
|
384
|
use File::CounterFile; |
|
1
|
|
|
|
|
4142
|
|
|
1
|
|
|
|
|
47
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
{ |
15
|
|
|
|
|
|
|
package Apache::Session::CountedStore; |
16
|
1
|
|
|
1
|
|
7
|
use Symbol qw(gensym); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
36
|
|
17
|
|
|
|
|
|
|
|
18
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
1487
|
|
19
|
|
|
|
|
|
|
|
20
|
0
|
|
|
0
|
|
|
sub new { bless {}, shift } |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# write. Note that we alias insert and update |
23
|
|
|
|
|
|
|
sub update { |
24
|
0
|
|
|
0
|
|
|
my $self = shift; |
25
|
0
|
|
|
|
|
|
my $session = shift; |
26
|
0
|
|
|
|
|
|
my $storefile = $self->storefilename($session); |
27
|
0
|
|
|
|
|
|
my $fh = gensym; |
28
|
0
|
0
|
|
|
|
|
unless ( open $fh, ">$storefile\0" ) { |
29
|
0
|
|
|
|
|
|
warn qq{A:S:Counted: Could not open file $storefile for writing: $! |
30
|
|
|
|
|
|
|
Maybe you haven't initialized the storage directory with |
31
|
|
|
|
|
|
|
use Apache::Session::Counted; |
32
|
|
|
|
|
|
|
Apache::Session::CountedStore->tree_init("$session->{args}{Directory}","$session->{args}{DirLevels}"); |
33
|
|
|
|
|
|
|
I'm trying to band-aid by creating this directory}; |
34
|
0
|
|
|
|
|
|
require File::Basename; |
35
|
0
|
|
|
|
|
|
my $dir = File::Basename::dirname($storefile); |
36
|
0
|
|
|
|
|
|
require File::Path; |
37
|
0
|
|
|
|
|
|
File::Path::mkpath($dir); |
38
|
0
|
|
|
|
|
|
warn "A:S:Counted: mkdir on directory $dir successfully done."; |
39
|
|
|
|
|
|
|
} |
40
|
0
|
0
|
|
|
|
|
if ( open $fh, ">$storefile\0" ) { |
41
|
0
|
|
|
|
|
|
print $fh $session->{serialized}; # $fh->print might fail in some perls |
42
|
0
|
|
|
|
|
|
close $fh; |
43
|
|
|
|
|
|
|
} else { |
44
|
0
|
|
|
|
|
|
die "Giving up. Could not open file $storefile for writing: $!"; |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
*insert = \&update; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# retrieve |
50
|
|
|
|
|
|
|
sub materialize { |
51
|
0
|
|
|
0
|
|
|
my $self = shift; |
52
|
0
|
|
|
|
|
|
my $session = shift; |
53
|
0
|
0
|
|
|
|
|
my $sessionID = $session->{data}{_session_id} or die "Got no session ID"; |
54
|
0
|
|
|
|
|
|
my($host) = $sessionID =~ /(?:([^:]+)(?::))/; |
55
|
0
|
|
|
|
|
|
my($content); |
56
|
|
|
|
|
|
|
|
57
|
0
|
0
|
0
|
|
|
|
if ($host && |
|
|
|
0
|
|
|
|
|
58
|
|
|
|
|
|
|
$session->{args}{HostID} && |
59
|
|
|
|
|
|
|
$session->{args}{HostID} ne $host |
60
|
|
|
|
|
|
|
) { |
61
|
|
|
|
|
|
|
# warn sprintf("configured hostID[%s]host from argument[%s]", |
62
|
|
|
|
|
|
|
# $session->{args}{HostID}, |
63
|
|
|
|
|
|
|
# $host); |
64
|
0
|
|
|
|
|
|
my $surl; |
65
|
0
|
0
|
|
|
|
|
if (exists $session->{args}{HostURL}) { |
66
|
0
|
|
|
|
|
|
$surl = $session->{args}{HostURL}->($host,$sessionID); |
67
|
|
|
|
|
|
|
} else { |
68
|
0
|
|
|
|
|
|
$surl = sprintf "http://%s/?SESSIONID=%s", $host, $sessionID; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
# warn "surl[$surl]"; |
71
|
0
|
0
|
|
|
|
|
if ($surl) { |
72
|
0
|
|
|
|
|
|
require LWP::UserAgent; |
73
|
0
|
|
|
|
|
|
require HTTP::Request::Common; |
74
|
0
|
|
|
|
|
|
my $ua = LWP::UserAgent->new; |
75
|
0
|
|
0
|
|
|
|
$ua->timeout($session->{args}{Timeout} || 10); |
76
|
0
|
|
|
|
|
|
my $req = HTTP::Request::Common::GET $surl; |
77
|
0
|
|
|
|
|
|
my $result = $ua->request($req); |
78
|
0
|
0
|
|
|
|
|
if ($result->is_success) { |
79
|
0
|
|
|
|
|
|
$content = $result->content; |
80
|
|
|
|
|
|
|
} else { |
81
|
0
|
|
|
|
|
|
$content = Storable::nfreeze {}; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
} else { |
84
|
0
|
|
|
|
|
|
$content = Storable::nfreeze {}; |
85
|
|
|
|
|
|
|
} |
86
|
0
|
|
|
|
|
|
$session->{serialized} = $content; |
87
|
0
|
|
|
|
|
|
return; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
0
|
|
|
|
|
|
my $storefile = $self->storefilename($session); |
91
|
0
|
|
|
|
|
|
my $fh = gensym; |
92
|
0
|
0
|
|
|
|
|
if ( open $fh, "<$storefile\0" ) { |
93
|
0
|
|
|
|
|
|
local $/; |
94
|
0
|
|
|
|
|
|
$session->{serialized} = <$fh>; |
95
|
0
|
0
|
|
|
|
|
close $fh or die $!; |
96
|
0
|
0
|
0
|
|
|
|
if ($content && $content ne $session->{serialized}) { |
97
|
0
|
|
|
|
|
|
warn "A:S:Counted: content and serialized are NOT equal"; |
98
|
0
|
|
|
|
|
|
require Dumpvalue; |
99
|
0
|
|
|
|
|
|
my $dumper = Dumpvalue->new; |
100
|
0
|
|
|
|
|
|
$dumper->set(unctrl => "quote"); |
101
|
|
|
|
|
|
|
warn sprintf "A:S:Counted: content[%s]serialized[%s]", |
102
|
|
|
|
|
|
|
$dumper->stringify($content), |
103
|
0
|
|
|
|
|
|
$dumper->stringify($session->{serialized}); |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
} else { |
106
|
0
|
|
|
|
|
|
warn "A:S:Counted: Could not open file $storefile for reading: $!"; |
107
|
0
|
|
|
|
|
|
$session->{data} = {}; |
108
|
0
|
|
|
|
|
|
$session->{serialized} = $session->{serialize}->($session); |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
sub remove { |
113
|
0
|
|
|
0
|
|
|
warn "A:S:Counted: remove not implemented"; # doesn't make sense |
114
|
|
|
|
|
|
|
# for our concept of a |
115
|
|
|
|
|
|
|
# session |
116
|
0
|
|
|
|
|
|
return; |
117
|
|
|
|
|
|
|
|
118
|
0
|
|
|
|
|
|
my $self = shift; |
119
|
0
|
|
|
|
|
|
my $session = shift; |
120
|
0
|
|
|
|
|
|
my $storefile = $self->storefilename($session); |
121
|
0
|
0
|
|
|
|
|
unlink $storefile or |
122
|
|
|
|
|
|
|
warn "A:S:Counted: Object $storefile does not exist in the data store"; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub tree_init { |
126
|
0
|
|
|
0
|
|
|
my $self = shift; |
127
|
0
|
|
|
|
|
|
my $dir = shift; |
128
|
0
|
|
|
|
|
|
my $levels = shift; |
129
|
0
|
|
|
|
|
|
my $n = 0x100 ** $levels; |
130
|
|
|
|
|
|
|
# warn "A:S:Counted: Creating directory $dir |
131
|
|
|
|
|
|
|
# and $n subdirectories in $levels level(s)\n"; |
132
|
|
|
|
|
|
|
# warn "A:S:Counted: This may take a while\n" if $levels>1; |
133
|
0
|
|
|
|
|
|
require File::Path; |
134
|
0
|
|
|
|
|
|
$|=1; |
135
|
|
|
|
|
|
|
my $feedback = |
136
|
|
|
|
|
|
|
sub { |
137
|
0
|
|
|
0
|
|
|
$n--; |
138
|
0
|
0
|
|
|
|
|
printf "\r$n directories left " unless $n % 256; |
139
|
0
|
0
|
|
|
|
|
print "\n" unless $n; |
140
|
0
|
|
|
|
|
|
}; |
141
|
0
|
|
|
|
|
|
File::Path::mkpath($dir); |
142
|
0
|
|
|
|
|
|
make_dirs($dir,$levels,$feedback); # function for speed |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub make_dirs { |
146
|
0
|
|
|
0
|
|
|
my($dir, $levels, $feedback) = @_; |
147
|
0
|
|
|
|
|
|
$levels--; |
148
|
0
|
|
|
|
|
|
for (my $i=0; $i<256; $i++) { |
149
|
0
|
|
|
|
|
|
my $subdir = sprintf "%s/%02x", $dir, $i; |
150
|
0
|
0
|
0
|
|
|
|
-d $subdir or mkdir $subdir, 0755 or die "Couldn't mkdir $subdir: $!"; |
151
|
0
|
|
|
|
|
|
$feedback->(); |
152
|
0
|
0
|
|
|
|
|
make_dirs($subdir, $levels, $feedback) if $levels; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub storefilename { |
157
|
0
|
|
|
0
|
|
|
my $self = shift; |
158
|
0
|
|
|
|
|
|
my $session = shift; |
159
|
|
|
|
|
|
|
die "The argument 'Directory' for object storage must be passed as an argument" |
160
|
0
|
0
|
|
|
|
|
unless defined $session->{args}{Directory}; |
161
|
0
|
|
|
|
|
|
my $dir = $session->{args}{Directory}; |
162
|
0
|
|
0
|
|
|
|
my $levels = $session->{args}{DirLevels} || 0; |
163
|
|
|
|
|
|
|
# here we depart from TreeStore: |
164
|
0
|
0
|
|
|
|
|
my $sessionID = $session->{data}{_session_id} or die "Got no session ID"; |
165
|
0
|
|
|
|
|
|
my($host,$file) = $sessionID =~ /(?:([^:]+)(?::))?([\da-f]+)/; |
166
|
0
|
0
|
|
|
|
|
die "Too short ID part '$file' in session ID'" if length($file)<8; |
167
|
0
|
|
|
|
|
|
while ($levels) { |
168
|
0
|
|
|
|
|
|
$file =~ s|((..){$levels})|$1/|; |
169
|
0
|
|
|
|
|
|
$levels--; |
170
|
|
|
|
|
|
|
} |
171
|
0
|
|
|
|
|
|
"$dir/$file"; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Counted is locked by definition |
176
|
|
|
|
|
|
|
sub release_all_locks { |
177
|
0
|
|
|
0
|
0
|
|
return; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
*get_lock_manager = \&release_all_locks; |
181
|
|
|
|
|
|
|
*release_read_lock = \&release_all_locks; |
182
|
|
|
|
|
|
|
*release_write_lock = \&release_all_locks; |
183
|
|
|
|
|
|
|
*acquire_read_lock = \&release_all_locks; |
184
|
|
|
|
|
|
|
*acquire_write_lock = \&release_all_locks; |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub TIEHASH { |
187
|
0
|
|
|
0
|
|
|
my $class = shift; |
188
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
|
my $session_id = shift; |
190
|
0
|
|
0
|
|
|
|
my $args = shift || {}; |
191
|
|
|
|
|
|
|
|
192
|
0
|
|
|
|
|
|
my $self = { |
193
|
|
|
|
|
|
|
args => $args, |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
data => { _session_id => $session_id }, |
196
|
|
|
|
|
|
|
# we always *have* read and write lock and need not care |
197
|
|
|
|
|
|
|
lock => Apache::Session::READ_LOCK|Apache::Session::WRITE_LOCK, |
198
|
|
|
|
|
|
|
status => 0, |
199
|
|
|
|
|
|
|
lock_manager => undef, |
200
|
|
|
|
|
|
|
generate => undef, |
201
|
|
|
|
|
|
|
serialize => \&Apache::Session::Serialize::Storable::serialize, |
202
|
|
|
|
|
|
|
unserialize => \&Apache::Session::Serialize::Storable::unserialize, |
203
|
|
|
|
|
|
|
}; |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
|
|
|
bless $self, $class; |
206
|
0
|
|
|
|
|
|
$self->{object_store} = Apache::Session::CountedStore->new($self); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
#If a session ID was passed in, this is an old hash. |
209
|
|
|
|
|
|
|
#If not, it is a fresh one. |
210
|
|
|
|
|
|
|
|
211
|
0
|
0
|
|
|
|
|
if (defined $session_id) { |
212
|
0
|
|
|
|
|
|
$self->make_old; |
213
|
0
|
|
|
|
|
|
$self->restore; # calls materialize and unserialize via Apache::Session |
214
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
215
|
|
|
|
|
|
|
exists $self->{data} && |
216
|
|
|
|
|
|
|
exists $self->{data}{_session_id} && |
217
|
|
|
|
|
|
|
defined $self->{data}{_session_id} && # protect agains unini warning |
218
|
|
|
|
|
|
|
$session_id eq $self->{data}{_session_id} |
219
|
|
|
|
|
|
|
) { |
220
|
|
|
|
|
|
|
# Fine. Validated. Kind of authenticated. |
221
|
|
|
|
|
|
|
# ready for a new session ID, keeping state otherwise. |
222
|
0
|
0
|
|
|
|
|
$self->make_modified if $self->{args}{AlwaysSave}; |
223
|
|
|
|
|
|
|
} else { |
224
|
|
|
|
|
|
|
# oops, somebody else tried this ID, don't show him data. |
225
|
0
|
|
|
|
|
|
delete $self->{data}; |
226
|
0
|
|
|
|
|
|
$self->make_new; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
# if we have no counterfile, we cannot generate an ID, that's OK: |
230
|
|
|
|
|
|
|
# this session will not need to be written. |
231
|
|
|
|
|
|
|
$self->{data}->{_session_id} = $self->generate_id() if |
232
|
0
|
0
|
|
|
|
|
$self->{args}{CounterFile}; |
233
|
|
|
|
|
|
|
# no make_new here, session-ID doesn't count as data |
234
|
|
|
|
|
|
|
|
235
|
0
|
|
|
|
|
|
return $self; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
sub generate_id { |
239
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
240
|
|
|
|
|
|
|
# wants counterfile |
241
|
|
|
|
|
|
|
my $cf = $self->{args}{CounterFile} or |
242
|
0
|
0
|
|
|
|
|
die "Argument CounterFile needed in the attribute hash to the tie"; |
243
|
0
|
|
|
|
|
|
my $c; |
244
|
0
|
|
|
|
|
|
eval { $c = File::CounterFile->new($cf,"0"); }; |
|
0
|
|
|
|
|
|
|
245
|
0
|
0
|
|
|
|
|
if ($@) { |
246
|
0
|
|
|
|
|
|
warn "A:S:Counted: Counterfile problem, trying to repair..."; |
247
|
0
|
0
|
|
|
|
|
if (-e $cf) { |
248
|
0
|
|
|
|
|
|
warn "A:S:Counted: Retrying after removing $cf."; |
249
|
0
|
|
|
|
|
|
unlink $cf; # May fail. stupid enough that we are here. |
250
|
0
|
|
|
|
|
|
$c = File::CounterFile->new($cf,"0"); |
251
|
|
|
|
|
|
|
} else { |
252
|
0
|
|
|
|
|
|
require File::Basename; |
253
|
0
|
|
|
|
|
|
my $dirname = File::Basename::dirname($cf); |
254
|
0
|
|
|
|
|
|
my @mkdir; |
255
|
0
|
|
|
|
|
|
while (! -d $dirname) { |
256
|
0
|
|
|
|
|
|
push @mkdir, $dirname; |
257
|
0
|
|
|
|
|
|
$dirname = File::Basename::dirname($dirname); |
258
|
|
|
|
|
|
|
} |
259
|
0
|
|
|
|
|
|
while (@mkdir) { |
260
|
0
|
|
|
|
|
|
my $dirname = pop @mkdir; |
261
|
0
|
0
|
|
|
|
|
mkdir $dirname, 0755 or die "Couldn't mkdir $dirname. Please create it with appropriate permissions"; |
262
|
|
|
|
|
|
|
} |
263
|
0
|
|
|
|
|
|
$c = File::CounterFile->new($cf,"0"); |
264
|
|
|
|
|
|
|
} |
265
|
0
|
|
|
|
|
|
warn "A:S:Counted: Counterfile problem successfully reapired."; |
266
|
|
|
|
|
|
|
} |
267
|
0
|
|
|
|
|
|
my $rhexid = sprintf "%08x", $c->inc; |
268
|
0
|
|
|
|
|
|
my $hexid = scalar reverse $rhexid; # optimized for treestore. Not |
269
|
|
|
|
|
|
|
# everything in one directory |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# we have entropy as bad as rand(). Typically not very good. |
272
|
0
|
|
|
|
|
|
my $password = sprintf "%08x%08x", rand(0xffffffff), rand(0xffffffff); |
273
|
|
|
|
|
|
|
|
274
|
0
|
0
|
|
|
|
|
if (exists $self->{args}{HostID}) { |
275
|
0
|
|
|
|
|
|
return sprintf "%s:%s_%s", $self->{args}{HostID}, $hexid, $password; |
276
|
|
|
|
|
|
|
} else { |
277
|
0
|
|
|
|
|
|
return $hexid . "_" . $password; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
1; |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=head1 NAME |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Apache::Session::Counted - Session management via a File::CounterFile |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head1 SYNOPSIS |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
tie %s, 'Apache::Session::Counted', $sessionid, { |
290
|
|
|
|
|
|
|
Directory => , |
291
|
|
|
|
|
|
|
DirLevels => , |
292
|
|
|
|
|
|
|
CounterFile => , |
293
|
|
|
|
|
|
|
AlwaysSave => , |
294
|
|
|
|
|
|
|
HostID => , |
295
|
|
|
|
|
|
|
HostURL => , |
296
|
|
|
|
|
|
|
Timeout => , |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=head1 DESCRIPTION |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
This session module is based on Apache::Session, but it persues a |
302
|
|
|
|
|
|
|
different notion of a session, so you probably have to adjust your |
303
|
|
|
|
|
|
|
expectations a little. |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
The dialog that is implemented within an HTTP based application is a |
306
|
|
|
|
|
|
|
nonlinear chain of events. The user can decide to use the back button |
307
|
|
|
|
|
|
|
at any time without informing the application about it. A proper |
308
|
|
|
|
|
|
|
session management must be prepared for this and must maintain the |
309
|
|
|
|
|
|
|
state of every single event. For handling the notion of a session and |
310
|
|
|
|
|
|
|
the notion of a registered user, the application has to differentiate |
311
|
|
|
|
|
|
|
carefully between global state of user data and a user's session |
312
|
|
|
|
|
|
|
related state. Some data may expire after a day, others may be |
313
|
|
|
|
|
|
|
regarded as unexpirable. This module is solely responsible for |
314
|
|
|
|
|
|
|
handling session related data. Saving unexpirable user related data |
315
|
|
|
|
|
|
|
must be handled by the calling application. |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
In Apache::Session::Counted, a session-ID only lasts from one request |
318
|
|
|
|
|
|
|
to the next at which point a new session-ID is computed by the |
319
|
|
|
|
|
|
|
File::CounterFile module. Thus what you have to treat differently than |
320
|
|
|
|
|
|
|
in Apache::Session are those parts that rely on the session-ID as a |
321
|
|
|
|
|
|
|
fixed token per user. Accordingly, there is no option to delete a |
322
|
|
|
|
|
|
|
session. The remove method is simply disabled as old session data will |
323
|
|
|
|
|
|
|
be overwritten as soon as the counter is reset to zero. |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
The usage of the module is via a tie as described in the synopsis. The |
326
|
|
|
|
|
|
|
arguments have the following meaning: |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=over |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
=item Directory, DirLevels |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
Works similar to filestore but as most file systems are slow on large |
333
|
|
|
|
|
|
|
directories, works in a tree of subdirectories. |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=item CounterFile |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
A filename to be used by the File::CounterFile module. By changing |
338
|
|
|
|
|
|
|
that file or the filename periodically, you can achieve arbitrary |
339
|
|
|
|
|
|
|
patterns of key generation. If you do not specify a CounterFile, you |
340
|
|
|
|
|
|
|
promise that in this session there is no need to generate a new ID and |
341
|
|
|
|
|
|
|
that the whole purpose of this object is to retrieve previously stored |
342
|
|
|
|
|
|
|
session data. Thus no new session file will be written. If you break |
343
|
|
|
|
|
|
|
your promise and write something to the session hash, the retrieved |
344
|
|
|
|
|
|
|
session file will be overwritten. |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
=item AlwaysSave |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
A boolean which, if true, forces storing of session data in any case. |
349
|
|
|
|
|
|
|
If false, only a STORE, DELETE or CLEAR trigger that the session file |
350
|
|
|
|
|
|
|
will be written when the tied hash goes out of scope. This has the |
351
|
|
|
|
|
|
|
advantage that you can retrieve an old session without storing its |
352
|
|
|
|
|
|
|
state again. |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=item HostID |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
A string that serves as an identifier for the host we are running on. |
357
|
|
|
|
|
|
|
This string will become part of the session-ID and must not contain a |
358
|
|
|
|
|
|
|
colon. This can be used in a cluster environment so that a load |
359
|
|
|
|
|
|
|
balancer or other interested parties can retrieve the session data |
360
|
|
|
|
|
|
|
again. |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=item HostURL |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
A callback that returns the service URL that can be called to get at |
365
|
|
|
|
|
|
|
the session data from another host. This is needed in a cluster |
366
|
|
|
|
|
|
|
environment. Two arguments are passed to this callback: HostID and |
367
|
|
|
|
|
|
|
Session-ID. The URL must return the serialized data in Storable's |
368
|
|
|
|
|
|
|
nfreeze format. The Apache::Session::Counted module can be used to set |
369
|
|
|
|
|
|
|
such an URL up. If HostURL is not defined, the default is |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
sprintf "http://%s/?SESSIONID=%s", , ; |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
The callback can return false to signal that there is no session to |
374
|
|
|
|
|
|
|
retrieve (e.g. when the host or id argument is illegal). |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=item Timeout |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
Sets the timeout for LWP::UserAgent for retrieving a session from a |
379
|
|
|
|
|
|
|
different host. Default is 10 seconds. |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
=back |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head2 What this model buys you |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
=over |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=item storing state selectively |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
You need not store session data for each and every request of a |
390
|
|
|
|
|
|
|
particular user. There are so many CGI requests that can easily be |
391
|
|
|
|
|
|
|
handled with two hidden fields and do not need any session support on |
392
|
|
|
|
|
|
|
the server side, and there are others where you definitely need |
393
|
|
|
|
|
|
|
session support. Both can appear within the same application. |
394
|
|
|
|
|
|
|
Apache::Session::Counted allows you to switch session writing on and |
395
|
|
|
|
|
|
|
off during your application without effort. (In fact, this advantage |
396
|
|
|
|
|
|
|
is shared with the clean persistence model of Apache::Session) |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
=item keeping track of transactions |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
As each request of a single user remains stored until you restart the |
401
|
|
|
|
|
|
|
counter, there are all previous states of a single session close at |
402
|
|
|
|
|
|
|
hand. The user presses the back button 5 times and changes a decision |
403
|
|
|
|
|
|
|
and simply opens a new branch of the same session. This can be an |
404
|
|
|
|
|
|
|
advantage and a disadvantage. I tend to see it as a very strong |
405
|
|
|
|
|
|
|
feature. Your milage may vary. |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=item counter |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
You get a counter for free which you can control just like |
410
|
|
|
|
|
|
|
File::CounterFile (because it B File::CounterFile). |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=item cleanup |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
Your data storage area cleans up itself automatically. Whenever you |
415
|
|
|
|
|
|
|
reset your counter via File::CounterFile, the storage area in use is |
416
|
|
|
|
|
|
|
being reused. Old files are being overwritten in the same order they |
417
|
|
|
|
|
|
|
were written, giving you a lot of flexibility to control session |
418
|
|
|
|
|
|
|
storage time and session storage disk space. |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=item performance |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
The notion of daisy-chained sessions simplifies the code of the |
423
|
|
|
|
|
|
|
session handler itself quite a bit and it is likely that this |
424
|
|
|
|
|
|
|
simplification results in an improved performance (not tested yet due |
425
|
|
|
|
|
|
|
to lack of benchmarking apps for sessions). There are less file stats |
426
|
|
|
|
|
|
|
and less sections that need locking, but without real world figures, |
427
|
|
|
|
|
|
|
it's hard to tell. |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=back |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
As with other modules in the Apache::Session collection, the tied hash |
432
|
|
|
|
|
|
|
contains a key C<_session_id>. You must be aware that the value of this |
433
|
|
|
|
|
|
|
hash entry is not the same as the one you passed in when you retrieved |
434
|
|
|
|
|
|
|
the session (if you retrieved a session at all). So you have to make |
435
|
|
|
|
|
|
|
sure that you send your users a new session-id in each response, and |
436
|
|
|
|
|
|
|
that this is never the old one. |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
As an implemenation detail it may be of interest to you, that the |
439
|
|
|
|
|
|
|
session ID in Apache::Session::Counted consists of two or three parts: |
440
|
|
|
|
|
|
|
an optional host alias given by the HostID paramter, followed by a |
441
|
|
|
|
|
|
|
colon. Then an ordinary number which is a simple counter which is |
442
|
|
|
|
|
|
|
followed by an underscore. And finally a session-ID like the one in |
443
|
|
|
|
|
|
|
Apache::Session. The number part is used as an identifier of the |
444
|
|
|
|
|
|
|
session and the ID part is used as a password. The number part is |
445
|
|
|
|
|
|
|
easily predictable, but the second part is reasonable unpredictable. |
446
|
|
|
|
|
|
|
We use the first part for implementation details like storage on the |
447
|
|
|
|
|
|
|
disk and the second part to verify the ownership of that token. |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=head1 PREREQUISITES |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
Apache::Session::Counted needs Apache::Session and File::CounterFile, |
452
|
|
|
|
|
|
|
all available from the CPAN. The HostID and HostURL parameters for a |
453
|
|
|
|
|
|
|
cluster solution need LWP installed. |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=head1 EXAMPLES |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
The following example resets the counter every 24 hours and keeps the |
458
|
|
|
|
|
|
|
totals of every day as a side effect: |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
my(@t) = localtime; |
461
|
|
|
|
|
|
|
tie %session, 'Apache::Session::Counted', $sid, |
462
|
|
|
|
|
|
|
{ |
463
|
|
|
|
|
|
|
Directory => ..., |
464
|
|
|
|
|
|
|
DirLevels => ..., |
465
|
|
|
|
|
|
|
CounterFile => sprintf("/some/dir/%04d-%02d-%02d", $t[5]+1900,$t[4]+1,$t[3]) |
466
|
|
|
|
|
|
|
}; |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
The same effect can be accomplished with a fixed filename and an |
470
|
|
|
|
|
|
|
external cronjob that resets the counter like so: |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
use File::CounterFile; |
473
|
|
|
|
|
|
|
$c=File::CounterFile->new("/usr/local/apache/data/perl/sessiondemo/counter"); |
474
|
|
|
|
|
|
|
$c->lock; |
475
|
|
|
|
|
|
|
$c-- while $c>0; |
476
|
|
|
|
|
|
|
$c->unlock; |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=head1 AUTHOR |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
Andreas Koenig |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=head1 COPYRIGHT |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
This software is copyright(c) 1999-2002 Andreas Koenig. It is free |
486
|
|
|
|
|
|
|
software and can be used under the same terms as perl, i.e. either the |
487
|
|
|
|
|
|
|
GNU Public Licence or the Artistic License. |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=cut |
490
|
|
|
|
|
|
|
|