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
|
External plugins are standalone, executable programs, that can be written
in any language. When ikiwiki starts up, it runs the program, and
communicates with it using [XML RPC][xmlrpc]. If you want to [[write]] an external
plugin, read on..
[xmlrpc]: http://www.xmlrpc.com/
ikiwiki contains one sample external plugin, named `externaldemo`. This is
written in perl, but is intended to be an example of how to write an
external plugin in your favorite programming language. Wow us at how much
easier you can do the same thing in your favorite language. ;-)
There's now a second external plugin, the [[rst]] plugin, written in
python. It uses a `proxy.py`, a helper library for ikiwiki python plugins.
[[!toc ]]
## How external plugins use XML RPC
While XML RPC is typically used over http, ikiwiki doesn't do that.
Instead, the external plugin reads XML RPC data from stdin, and writes it
to stdout. To ease parsing, each separate XML RPC request or response must
start at the beginning of a line, and end with a newline. When outputting
XML RPC to stdout, be _sure_ to flush stdout. Failure to do so will result
in deadlock!
An external plugin should operate in a loop. First, read a command from
stdin, using XML RPC. Dispatch the command, and return its result to
stdout, also using XML RPC. After reading a command, and before returning
the result, the plugin can output XML RPC requests of its own, calling
functions in ikiwiki. Note: *Never* make an XML RPC request at any other
time. IkiWiki won't be listening for it, and you will deadlock.
When ikiwiki starts up an external plugin, the first RPC it will make
is to call the plugin's `import()` function. That function typically makes
an RPC to ikiwiki's `hook()` function, registering a callback.
An external plugin can use XML RPC to call any of the exported functions
documented in the [[plugin_interface_documentation|write]]. It can also
actually call any non-exported IkiWiki function, but doing so is a good way
to break your plugin when ikiwiki changes. There is currently no versioned
interface like there is for perl plugins, but external plugins were first
supported in ikiwiki version 2.6.
## Accessing data structures
IkiWiki has a few global data structures such as `%config`, which holds
its configuration. External plugins can use the `getvar` and `setvar` RPCs
to access any such global hash. To get the "url" configuration value,
call `getvar("config", "url")`. To set it, call
`setvar("config", "url", "http://example.com/)`.
The `%pagestate` is a special hash with a more complex format. To access
it, external plugins can use the `getstate` and `setstate` RPCs. To access
stored state, call `getstate("page", "id", "key")`, and to store state,
call `setstate("page", "id", "key", "value")`.
To access ikiwiki's ARGV array, call `getargv()`. To change its ARGV, call
`setargv(array)`.
## Notes on function parameters
The [[plugin_interface_documentation|write]] talks about functions that take
"named parameters". When such a function is called over XML RPC, such named
parameters look like a list of keys and values:
page, foo, destpage, bar, magnify, 1
If a name is repeated in the list, the later value overrides the earlier
one:
name, Bob, age, 20, name, Sally, gender, female
In perl, boiling this down to an associative array of named parameters is
very easy:
sub foo {
my %params=@list;
Other languages might not find it so easy. If not, it might be a good idea
to convert these named parameters into something more natural for the
language as part of their XML RPC interface.
## undef
XML RPC has a limitation that it does not have a way to pass
undef/NULL/None. There is an extension to the protocol that supports this,
but it is not yet available in the [[!cpan XML::RPC]] library used by
ikiwiki.
Until the extension is available, ikiwiki allows undef to be communicated
over XML RPC by passing a sentinal value, a hash with a single key "null"
with a value of an empty string. External plugins that need to communicate
null values to or from ikiwiki will have to translate between undef and
the sentinal.
## Function injection
Some parts of ikiwiki are extensible by adding or overriding functions.
It's actually possible to do this from an external plugin too.
To make your external plugin override the `IkiWiki::formattime` function, for
example, make an RPC call to `inject`. Pass it named parameters "name" and
"call", where "name" is the name of the function to inject into perl (here
"Ikiwiki::formattime" and "call" is the RPC call ikiwiki will make whenever
that function is run.
If the RPC call is memoizable, you can also pass a "memoize" parameter, set
to 1.
## Limitations of XML RPC
Since XML RPC can't pass around references to objects, it can't be used
with functions that take or return such references. That means you can't
100% use XML RPC for `cgi` or `formbuilder` hooks (which are passed CGI and
FormBuilder perl objects), or use it to call `template()` (which returns a
perl HTML::Template object).
## Performance issues
Since each external plugin is a separate process, when ikiwiki is
configured to use lots of external plugins, it will start up slower, and
use more resources. One or two should not be a problem though.
There is some overhead in using XML RPC for function calls. Most plugins
should find it to be pretty minimal though. In one benchmark, ikiwiki was
able to perform 10000 simple XML RPC calls in 11 seconds -- 900 per second.
Using external plugins for hooks such as `sanitize` and `format`, which
pass around entire pages, and are run for each page rendered, will cause
more XML RPC overhead than usual, due to the larger number of calls, and the
large quantity of data conversion going on. In contrast, `preprocess` hooks
are called generally rarely, and pass around minimal data.
External plugins should avoid making RPC calls unnecessarily (ie, in a loop).
Memoizing the results of appropriate RPC calls is one good way to minimize the
number of calls.
Injecting a replacement for a commonly called ikiwiki function
could result in a lot more RPC calls than expected and slow
everything down. `pagetitle`, for instance, is called about 100 times
per page build. Whenever possible, you should tell ikiwiki to memoize
injected functions.
In general, use common sense, and your external plugin will probably
perform ok.
|