File: webdot.cgi

package info (click to toggle)
graphviz 2.8-3%2Betch1
  • links: PTS
  • area: main
  • in suites: etch
  • size: 20,480 kB
  • ctags: 22,071
  • sloc: ansic: 163,260; cpp: 36,565; sh: 25,024; yacc: 2,358; tcl: 1,808; makefile: 1,745; cs: 805; perl: 801; ml: 649; awk: 160; lex: 153; python: 105; ruby: 32; php: 6
file content (273 lines) | stat: -rwxr-xr-x 8,383 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/perl -w

# This is a perl front end to run dot as a web service.
# To install, set the perl path above, and configuration paths below:
# $Tdir, $SigCommand, $GraphvizBinDir

# This script takes as an argument the URL of a dot (graph) file with
# the name of a graphviz layout server and an output type as suffixes.
# The argument can be passed in the PATH_INFO environment variable as
# in a typical Apache setup, or as a command line argument for manual
# testing. 
# 
# The server must be: dot, neato, or twopi.
# The output type must be one of dot's output types.  The server
# returns a layout of the requested type as an HTTP stream.  The dot
# output type is mapped to an appropriate MIME type.
# For example, if yourhost.company.com/unix.dot is a dot graph file, try
# webdot.cgi http://yourhost.company.com/unix.dot.dot.ps
# webdot.cgi http://yourhost.company.com/unix.dot.neato.gif
# webdot.cgi http://yourhost.company.com/unix.dot.twopi.pdf
#
# More details:
# PDF and EPSI files are made by postprocessors.
#
# The server maintains a cache directory of dot files and layouts.
# The server always pulls the dot file, but doesn't bother with layout
# if its cache is valid.  This is checked using $SigCommand on the dot source
# (typically md5 or at least cksum).
#
# The cache should be cleaned externally, for example by a cron job.
# When testing, remember to clobber cache entries manually as needed.
#
# If we thought users were going to request many layouts of the same
# graph but in different layout formats, we might just cache the layout
# in canonical dot format, and run neato -nop for code generation.
#
# The first version of this script was written in tclsh by John Ellson
# and had some additional features for tclet integration, background
# images, and a "Graph by Webdot" logo in each image; they are not
# included here. 
#
# Thanks to John Linderman for perl hacking. --Stephen North
# 
#

use strict;
use FileHandle;
use Fcntl ':flock';
use File::Path qw( mkpath );
use LWP;

# bugs:
# need to test imap, ismap, svg
# vrml requires its own subdir?

# set $Tdir to the webdot cache directory.  note that this script must have
# write permission on the directory when it is run by your web server.
# for example apache's default httpd.conf specifies that CGI programs such
# as this one run as user 'nobody'.  in that case the cache directory must
# be writable by 'nobody' - either mode 0777 or chown to nobody.
my $Tdir = '/home/north/www/webdot/tmp';

# set $GraphvizBinDir to the dot/neato/twopi standalone command directory.
# DotFontPath shouldn't be necessary, but our graphviz installation is broken.
my $DotFontPath = '/home/north/lib/fonts/dos/windows/fonts';
my $GraphvizBinDir = '/home/north/arch/linux.i386/bin';

# set $EPSIfilter to the script that maps Postscript into epsi.
my $EPSIfilter = '/usr/bin/ps2epsi';

# set $GS to Ghostscript - must be compiled with -sDEVICE=pdfwrite enabled!
my $GS = '/usr/bin/gs';

# set $SigCommand to the path of your signature utility.  if you don't have md5,
# you could likely use GNU cksum or just /usr/bin/sum in a pinch.
# my $SigCommand = '/usr/local/SSLeay-0.9.0b/bin/md5';  for www.research.att.com
my $SigCommand = '/usr/bin/cksum';

# set 

my %KnownTypes = (
	dot =>    'application/x-dot',
	gif =>    'image/gif',
	png =>    'image/png',
	mif =>    'application/x-mif',
	hpgl =>   'application/x-hpgl',
	pcl =>    'application/x-pcl',
	vrml =>   'x-world/x-vrml',
	vtx =>    'application/x-vtx',
	ps =>     'application/postscript',
	epsi =>   'application/postscript',
	pdf =>    'application/pdf',
	map =>    'text/plain',
	txt =>    'text/plain',
	src =>    'text/plain',
	svg =>    'image/svg+xml',
);

my %KnownServers = ( 'dot' => 1, 'neato' => 1 , 'twopi' => 1 );

# What content type is returned.  Usually $KnownTypes{$tag},
# but not always.
my $ContentType = 'text/plain';

# What is returned.  In good times, the results of running dot,
# (and maybe a postprocessor), in bad times, an apologetic message.
my $TheGoods = 'Server Error, profound apologies';

# Arrange to return an error message
sub trouble {
    $TheGoods = shift;
    $ContentType = 'text/plain';
}


sub run_under_lock {
    my ($fh, $cmd) = @_;
    my $rc;

    flock($fh, LOCK_EX);	# Upgrade to exclusive lock
    truncate($fh, 0);		# Make sure file is empty
    $rc = system($cmd);		# Run command to load file
    unless ($rc == 0) {
	trouble("Server error: Non-zero exit $rc from $cmd\n");
	return;
    }
    flock($fh, LOCK_SH);	# Downgrade to shared lock
    return 1;
}

sub up_doc {
    my ($base, $url, $layouter, $tag) = @_;
    my $dotdir = "$Tdir/$layouter/$base";
    my $dotfile = "$dotdir/source";
    my $tagfile = "$dotdir/$tag";
    my $dotfh = new FileHandle;
    my $tagfh = new FileHandle;
    my $fh = new FileHandle;
    my ($size, $mtime, $cmd, $webdoc, $content);
    my ($ttime, $rc);
    my $now = time();
    my ($oldsig, $newsig);

    unless (-d $dotdir) {
	unless (mkpath( [ $dotdir ], 0, 02775)) {
	    trouble("Server error: Unable to make directory $dotdir: $!");
	    return;
	}
    }
    unless (open($dotfh, "+>> $dotfile")) {
	trouble("Server error: Open failed on $dotfile: $!");
	return;
    }
    flock($dotfh, LOCK_SH);
    ($size, $mtime) = (stat($dotfh))[7,9];
    # if($size > 0) { $oldsig = `$SigCommand $dotfile`; }
    $oldsig = ($size > 0? `$SigCommand $dotfile` : 0);

    my $browser = LWP::UserAgent->new();   ## Create a virtual browser
    $browser->agent("Kipper Browser");     ## Name it
    ## Do a GET request on the URL with the fake browser
    $webdoc = $browser->request(HTTP::Request->new(GET => $url));
    if($webdoc->is_success){ ## found it 
	$content = $webdoc->content();
	flock($dotfh, LOCK_EX);
	truncate($dotfh, 0);
	print $dotfh $content;
	$dotfh->autoflush();
	flock($dotfh, LOCK_SH);
	($size, $mtime) = (stat($dotfh))[7,9];
    } else {                 ## did not find it
	trouble("Server error: Could not find $url\n");
	return;
    }

    ($size, $mtime) = (stat($dotfh))[7,9];
    # if (($size == 0) || ((($now - $mtime)/(60*60)) > $SourceHours)) { }
    unless ($size) {
	trouble("Empty dot source\n");
	return;
    }
    unless (open($tagfh, "+>> $tagfile")) {
	trouble("Server error: Open failed on $tagfile: $!");
	return;
    }
    flock($tagfh, LOCK_SH);
    ($size, $ttime) = (stat($tagfh))[7,9];
    $newsig = `$SigCommand $dotfile`;
    if (($size == 0) || ($oldsig ne $newsig)) {
	my $dottag = $tag;
	my $tmpfile;
	my $tmpfh;
	if (($tag eq 'epsi') || ($tag eq 'pdf')) {
	    $dottag = 'ps';
	    $tmpfile = "$dotdir/ps";
	    $tmpfh = new FileHandle;
	    unless (open($tmpfh, "+>> $tmpfile")) {
		trouble("Server error: Open failed on $tmpfile: $!");
		return;
	    }
	} else {
	    $tmpfile = $tagfile;
	    $tmpfh = $tagfh;
	}
	$cmd = "DOTFONTPATH=\"$DotFontPath\" $GraphvizBinDir/$layouter -T$dottag < $dotfile > $tmpfile";
	return unless (run_under_lock($tmpfh, $cmd));
	## might have to postprocess ps into epsi or pdf
	if ($tag eq 'epsi') {
		$cmd = "$EPSIfilter < $tmpfile > $tagfile";
		return unless (run_under_lock($tagfh, $cmd));
	} elsif ($tag eq 'pdf') {
		$cmd = "$GS -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$tagfile $tmpfile";
		return unless (run_under_lock($tagfh, $cmd));
	}
    }
    seek($tagfh,0,0);
    {
	local($/);	# slurp mode
	$TheGoods = <$tagfh>;
    }
    1;
}


sub get_dot {
    my $urltag = shift;
    my ($url, $base, $layouter, $tag);

    # if ($urltag =~ /^(.+)[.]([^.]+)$/) {
    if ($urltag =~ /^(.+)[.]([^.]+)[.]([^.]+)$/) {
	($url, $layouter, $tag) = ($1, $2, $3);
	unless ($KnownServers{$layouter}) {
	    trouble("Unknown layout service $layouter from $url\n");
	    return;
	}
	unless ($ContentType = $KnownTypes{$tag}) {
	    trouble("Unknown tag type $tag from $url\n");
	    return;
	}
	($base = $url) =~ s%[/:]%-%g;	# remember to make safe for PC's
	# trouble("I see: '$base' '$url' '$layouter' '$tag' \n"); return;
	up_doc($base, $url, $layouter, $tag);
    } else {
	trouble("Unknown url format: $url\n");
    }
}


sub show_results {
    my $size = length($TheGoods);

    print <<EOF ;
Content-type: $ContentType;
Content-length: $size
Pragma: no-cache

EOF
    print($TheGoods);
}


sub main {
    my $arg;
    if ($arg = ($ENV{'PATH_INFO'})) {
	    $arg =~ s:/::;
	}
	else  {
		$arg = $ARGV[0];
	}
    get_dot($arg);
    show_results();
}
main();