\label{Developer_Tutorial}
\section{Adding a Handler to LON-CAPA}
In this section, we will add a brand new {}``handler'' to LON-CAPA.
A \textbf{handler\index{handler}} is code that gets loaded by the
Apache web server that tells the web server how to handle a certain
kind of request. For a complete discussion of this process, you will
need to read the Apache documentation; we especially recommend the
O'Reilly book {}``Writing Apache Modules with Perl and C''. In this
chapter we will add a {}``Response'' handler; this is the most important
handler in the system because it is directly responsible for what
the user sees on the screen when they end up using the handler.
As a side-effect of installing a new handler from scratch, we will
also show how to modify the files LON-CAPA uses and run LON-CAPA with
the modified files.
After completing this tutorial you should be able to more easily understand
the structure of the vast majority of LON-CAPA code.
If you are brave and are not using Red Hat to run LON-CAPA, the \texttt{/etc/httpd}
and \texttt{/var/log/httpd} directories may not exist, as your distribution
may prefer to refer to them as \texttt{/etc/apache} and \texttt{/etc/log/apache},
such as Gentoo does. In that case, I recommend symlinking \texttt{/etc/httpd}
to \texttt{/etc/apache} (LON-CAPA depends on \texttt{/etc/httpd} existing),
and either make the appropriate substitutions in the following directions
for the log files, or make a symlink there, too. (\texttt{ln -s /etc/apache
/etc/httpd})
\subsection{Pre-requisites}
\begin{itemize}
\item LON-CAPA must be installed and running.
\item \textbf{Perl}: You're going to need to know some or you will not understand
the code. You don't need to be intimately familiar with the many hundreds
of libraries Perl has, though, which is much harder than learning
the language.
\item Knowledge of the Apache server is not required for this tutorial,
but {}``real work'' may require more information then is given here.
\end{itemize}
\subsection{Adding a New Handler}
You need to create a \emph{new Perl module} to serve as the handler.
A {}``Perl module''\index{Perl module} is a file that has a name
ending in {}``.pm''. For the sake of concreteness, I will refer
to this file as \texttt{lontutorial.pm}%
\footnote{Currently, almost all LON-CAPA Perl modules are prefixed with {}``lon''.
This is a weak convention used inconsistently, and theoretically it
will be dropped from the project someday. %
}.
Create this new file where ever you'd like for now.
\subsubsection{Apache Handler Shell}
Put the following code into your Perl module:
\begin{lyxcode}
package~Apache::lontutorial;
~
use~strict;
use~Apache::Constants~qw(:common);
use~Apache::loncommon;
~
=pod
~
=head1~NAME
~
lontutorial~-~tutorial~code
~
=head1~SYNOPSIS
~
lontutorial~contains~the~code~from~the~tutorial,~and
does~tutorial~stuff.
~
=cut
~
sub~handler~\{
~~~~my~\$r~=~shift;
~
~~~~\$r->print('Hello~world!');
~~~~return~OK;
\}
~
1;
\end{lyxcode}
A line-by-line breakdown:
\begin{itemize}
\item \texttt{\textbf{package Apache::lontutorial;}}: This puts everything
in the file in the \texttt{Apache::lontutorial} namespace. Note that
the \texttt{lontutorial} part should match your filename before the
\texttt{.pm}. (The file may contain other packages; see \texttt{lonnavmaps.pm}
or \texttt{lonhelper.pm} for an example. But this package should show
up in the file or Apache may have trouble finding your file.)
\item \texttt{\textbf{use strict;}}: Turns on strict variable checking,
which is a Very Good Idea. If you intend to submit the code back to
the main LON-CAPA project, please make sure to use this.
\item \texttt{\textbf{use Apache::Constants qw(:common);}}: This loads certain
constants used to communicate with Apache into the current namespace.
\item \texttt{\textbf{use Apache::loncommon;}}: This loads some commonly-used
functions we will use later. See \texttt{man Apache::loncommon} for
more information.
\item \texttt{\textbf{=pod}} through \texttt{\textbf{=cut}}: This contains
inline documentation in the POD format. Please see \texttt{man perlpod}
for more information. I encourage you to document your code. Note
many current LON-CAPA modules do not contain good examples. Please
consult \texttt{perldoc lonhelp.pm} in the \texttt{loncom/interface}
directory of the CVS repository, or see the \texttt{lonhelp.pm} information
later in the developer manual, for style considerations. The documentation
so produced can be easily added to this Developer's Manual if it would
be useful to others, so you're accomplishing the dual purpose of documenting
in the file and the manual at the same time.
\item \texttt{\textbf{sub handler \{}}: Apache will call the \texttt{handler}
function in the namespace it is given for the handler in the \texttt{loncapa\_apache.conf}
file; see below. Thus, we need to supply it, or Apache will fail and
complain about a missing subroutine.
\item \texttt{\textbf{my \$r = shift;}}: The first and only parameter to
the handler function is the Apache Response object. We will partially
cover this object in this tutorial, for more information consult outside
documentation.
\item \texttt{\textbf{\$r->print('Hello world!');}}: \texttt{\$r->print}
is how we sent text to the user. This is the \emph{only} method to
send text to the user's web browser, and in the end, all output ends
up going through this function.
\item \texttt{\textbf{return OK;}}: {}``OK'' is a constant we imported
in our namespace in the use \texttt{Apache::Constants} line. It tells
the server the page was successfully displayed, as opposed to a 404
or other error.
\item \texttt{\textbf{1;}}: This tells Perl the module was successfully
parsed, and is a required part of Perl modules.
\end{itemize}
\subsubsection{Telling Apache About Your Handler}
In order for Apache to use your file, you need to tell Apache where
to find it, and what URL(s) to use for it. Add the following to your
\texttt{/etc/httpd/conf/loncapa\_apache.conf}, after the \texttt{PerlTransHandler}
declaration:
\begin{lyxcode}
PerlAccessHandler~~~~~~~Apache::lonacc
SetHandler~~~~~~~~~~~~~~perl-script
PerlHandler~~~~~~~~~~~~~Apache::lontutorial
ErrorDocument~~~~~~500~~/adm/errorhandler
\end{lyxcode}
This will tell Apache to call Apache::lontutorial::handler when someone
goes to /adm/tutorial to see what to do.
Note: The \texttt{Apache::} is not technically necessary in mod\_perl,
it's just that LON-CAPA uses it. We may remove it from the system
someday. In the meantime, it would be consistent with the rest of
the system to continue use it. (If you don't understand what this
is saying, don't worry about it.)
\subsubsection{Putting The File Where Apache Can Find It}
Now you need to put that file where Apache can find it.
Apache will look for it in \texttt{/home/httpd/lib/perl/Apache}/,
so copy it there.
\begin{lyxcode}
cp~lontutorial~/home/httpd/lib/perl/Apache/
\end{lyxcode}
\subsubsection{Running Apache}
You can now (re-)start the Apache server.
\begin{lyxcode}
/etc/init.d/httpd~restart
\end{lyxcode}
Also, if you are logged into LON-CAPA, log out (exit) before continuing.
(Note re-starting the server does \emph{not} automatically log you
out, which is really helpful while debugging so you don't have to
continuously log back in.)
If everything went well, you can now go to your server and go to \texttt{http://your.server/adm/tutorial},
with \texttt{your.server} replaced by the domain name of your LON-CAPA
server, of course. (I will use {}``your.server'' that way for the
rest of this tutorial.)
And you should see\ldots{} {}``Bad Request''? What happened?
You don't know it yet, but we're making a handler for functionality
that will require the user to be logged in for it to work, because
it is going to depend on information about the user. Right now, nobody
is logged in. The \texttt{PerlAccessHandler\index{PerlAccessHandler}}
line in what we put in \texttt{loncapa\_apache.conf} tells Apache
to use {}``Apache::lonacc::handler'' as an \textbf{access handler}\index{access handler}.
An \textbf{access handler} determines whether the user has permission
to perform the current action. lonacc is a handler written for LON-CAPA
that among other things, ensures the user is logged in. This handler
told Apache that this is a {}``Bad Request'', which is why you got
the {}``Bad Request'' screen. Your handler code was actually \textbf{never
even executed}; Apache aborted the request before getting to your
code.
Log into LON-CAPA, and choose a role with a course. (You may need
to create a course; please see the Course Management manual for more
information.) Any role with a course, Student, Instructor, Course
Coordinator or otherwise is fine. We won't be making any changes to
the course data so it doesn't matter if it's a {}``real'' course.
Once you've gotten to the first page of the course (normal entry),
change your URL to read \texttt{http://your.server/adm/tutorial}.
You should now see {}``Hello world!'' on the screen.
\paragraph{Apache can't find \texttt{/adm/tutorial}:}
If Apache is claiming it can't find the file named {}``/adm/tutorial'',
something is wrong with your \texttt{loncapa\_apache.conf} file. Please
double check the entry to make sure it is in the right place, that
it is typed correctly, and that all the lines are present. If it is
still incorrect, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
list, including a copy of your \texttt{loncapa\_apache.conf} file
in the email; either something is wrong that is beyond the scope of
this document(other system configuration issues) or you really can't
find the error.
\paragraph{{}``Something went wrong, please help us find out what''}
If you get an icon of the LON-CAPA logo with a cracked screen and
the preceding message, something is wrong with your Perl code. This
is the standard screen that asks the user to send us more information
about what happened, so we can try to diagnose the problem. Since
you're a developer, you can ignore that screen and actually collect
the information and act on it directly.
The Perl error landed in \texttt{/var/log/httpd/error\_log}, in the
last few lines. Take a look at what was in there, and try to fix it.
\begin{lyxcode}
tail~/var/log/httpd/error\_log
\end{lyxcode}
Since I can't guess what error you encountered, I'm afraid I can't
help you out here. But I do know the code I wrote above worked, so
as a last resort try to figure out what is different. If you are absolutely
stumped, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
list, including a copy of your \texttt{lontutorial.pm} file in the
email; either something is wrong that is beyond the scope of this
document (other system configuration issues) or you really can't find
the error.
Actually, if you have the time, I recommend adding a syntax error
to your \texttt{lontutorial.pm}, even if it worked the first time,
so you can see what happens. This will not be the last time there's
an error in your code\ldots{}
\subsubsection{Modifying The Module}
When Apache is started, it reads all the Perl modules in LON-CAPA
into memory, and after that, does not use the \texttt{.pm} files in
\texttt{/home/httpd/lib/perl/Apache}%
\footnote{Slight exaggeration here: Actually it only loads the ones mentioned
in startup.pl, and since your module isn't in that file, it won't
be loaded at startup. But this really doesn't matter much.%
}. To get Apache to use your newly-edited files, you need to copy them
to \texttt{/home/httpd/lib/perl/Apache} and restart the Apache server.
It is generally not a good idea to directly modify the files in \texttt{/home/httpd/lib/perl/Apache}
while developing, because of the difficulty you will have later propagating
changes made there back to more useful places, such as in the LON-CAPA
CVS repository. To alleviate the annoyance of constantly copying the
file to that directory and restarting the server, compose a single
command line that does all of it at once. I often use something like
the following:
\begin{lyxcode}
pushd~.;~cd~YOUR\_DIRECTORY;~cp~lontutorial.pm
~/home/httpd/lib/perl/Apache;~/etc/init.d/httpd~restart;
~popd
\end{lyxcode}
where YOUR\_DIRECTORY is replaced by the directory containing the
\texttt{lontutorial.pm} file, and that is all one line.
The {}``pushd'' and {}``popd'' commands are built-in to the Bash
shell; other shells may or may not contain such commands.
Once you've created this command line, you can just use the Up arrow
in your shell to get it back. You can add whatever you want to it
as needed; that's why I wrap it in the {}``pushd'' and {}``popd''
commands, so you can {}``cd'' without affecting your current directory.
If you had a syntax error in your Perl module, fix it now. If you
didn't, I'd recommend make a small change to the printed string or
something and making sure you have figured out how to change the code
in the Apache server correctly before continuing.
Once again, notice that even after re-starting the server, you are
still logged in with the same settings you were using before the server
re-start.
\subsection{Making The Handler Do Something}
\subsubsection{A Bit More Boilerplate}
At this point, your handler isn't doing much. If you look carefully,
you'll notice that the {}``Hello World'' on the screen is in a mono-spaced
font, not the proportional font used by default in HTML. That's because
\emph{all} you sent was {}``Hello World!''; the browser looks at
the header of the HTTP response to see what kind of response it is
getting back, but you didn't even \emph{send} a header. Technically,
that's not a legal HTTP response, but the browser was accommodating
and displayed what you got back anyhow as text. (Consult documentation
on the HTTP protocol for more information about this; generally you
will not need to worry about this in LON-CAPA beyond the boilerplate
presented here.)
To send back a proper HTTP response, add this code after the \texttt{my
\$r = shift;}:
\begin{lyxcode}
~~~~\#~Handle~header-only~request
~~~~if~(\$r->header\_only)~\{
~~~~~~~~if~(\$env\{'browser.mathml'\})~\{
~~~~~~~~~~~~\$r->content\_type('text/xml');
~~~~~~~~\}~else~\{
~~~~~~~~~~~~\$r->content\_type('text/html');
~~~~~~~~\}
~~~~~~~~\$r->send\_http\_header;
~~~~~~~~return~OK;
~~~~\}
~~~~\#~Send~header,~don't~cache~this~page
~~~~if~(\$env\{'browser.mathml'\})~\{
~~~~~~~~\$r->content\_type('text/xml');
~~~~\}~else~\{
~~~~~~~~\$r->content\_type('text/html');
~~~~\}
~~~~\&Apache::loncommon::no\_cache(\$r);
~~~~\$r->send\_http\_header;
\end{lyxcode}
This is boilerplate that appears at the top of many LON-CAPA handlers.
It handles the case where the user is requesting that only headers
be sent; unusual, but worth handling. It sets the content type appropriately,
and tells the client not to cache this page, which is appropriate
since it will be dynamic.
For efficiency, when you use \texttt{\$r->print()} to send something
to the user, Apache assumes you know what you are doing and will immediately%
\footnote{Actually, if you send a lot of little things, Apache is smart enough
to bundle them together into one big network packet for efficiency.
If you absolutely want Apache to send everything it has to the user
\emph{right now}, use \texttt{\$r->rflush()}\ldots{} but sparingly,
as repeated use can cause a \emph{huge} slowdown.%
} send what you printed across the network. Since the HTTP header is
supposed to precede all content, it is important that you send the
header before you send any other content across the network, so we
ask the Apache server to go ahead and send the header with \texttt{\$r->send\_http\_header()}.
Since the \texttt{Apache::loncommon::no\_cache()} call affects the
header, it must be called before we send it out, because once the
header is sent there's no getting it back.
All code that follows this code will send the output to the user directly.
Finally, let's add two more lines to make it fit into the system better.
Wrap the following three lines around the {}``Hello World'' line,
as shown:
\begin{lyxcode}
\$r->print('
Tutorial~Page');
\$r->print(\&Apache::loncommon::bodytag('Tutorial~Handler','',''));
\$r->print('Hello~world!');
\$r->print('