Re: [Caml-list] Re: Common CGI interface

Gerd Stolpmann <[email protected]> Thu, 16 Jun 2005 14:05:03 +0200
Newsgroups gmane.comp.lang.ocaml.lib.net.devel
Message-ID <[email protected]>
Am Dienstag, den 14.06.2005, 21:39 +0200 schrieb Christophe TROESTLER:
> On Tue, 14 Jun 2005, Gerd Stolpmann <[email protected]> wrote:
> > 
> > As it seems, I have some time this week. Will look at it tomorrow.
> 
> Thanks.  You can read a complete commented signature at
> http://ocaml-cgi.sourceforge.net/netcgi/Netcgi.html

Ok, thanks. I also read your old messages again.

When I had to summarize your proposal, I would do it the following way:

1) A refactorisation of the modules and classes, so that they are easier
to understand, especially how they are related to each other.

2) New design of the connector API, or better, the first trial to design
this API at all.

3) Some interesting details, such as the Random module.

First of all, I must say that your proposal looks quite convincing, and
much better than the current API. Nevertheless, I think we should
discuss the various aspects in depth to see whether it is really worth
changing the API. There is a very important downside of changes: they
often break existing code, and this may drive users away from using
Ocamlnet.

1) In your proposal, everything is contained in a top-level module
Netcgi. In the current API, we have (in this order):

Netcgi_env
Netcgi_types
Netcgi
Netcgi_<connector>

(a) When reading ocamldoc, the approach of having a single top-level
module gives a faster survey what exists, and how the various pieces are
connected. In the current API, this is more difficult. Just for naming
the approaches, I call your approach "monolithic" and the current API
"polylithic"

However, there are also other aspects: (b) engineering. In the
monolithic API, external people cannot add their own connectors to the
module namespace. It is also difficult to have connectors that are
optionally built. In the polylithic approach, this is no problem, just
name the module accordingly. We actually have this problem when the HTTP
connector will be added to Ocamlnet (it is already developed and checked
in as part of a Ocamlnet branch, but not yet released). It is much
larger than the other connectors and requires a number of additional
modules as prerequisites. Because of this, we are going to make this
connector optional. It also has a very different API, since it is
essentially a complete web server in itself. When we adopt the
monolithic API, at least this part cannot be incorporated.

(c) Code size. When the executable is linked, there is a step called
"garbage collection": unused code does not become part of the
executable. O'Caml does garbage collection only for top-level modules,
but not inside modules. This means that the monolithic approach will
typically produce large executables than the polylithic approach where
at least unused connectors do not go into the executable. This aspect
has some importance for real-world programs, and I already got a number
of complaints why programs are so large.

As a compromise of the three mentioned aspects I can imagine the
following:

- Only combine Netcgi_env, Netcgi_types, and the non-CGI-specific part
  of Netcgi into a new top-level module Netcgi_api.

  The old modules would remain intact, so existing programs are not
  broken. Netcgi_api's task would be to establish the user's view on
  the core typing, whereas the old modules would focus on the 
  implementor's view.

- For every connector there is a new module Netcgi_<connector>_api
  in addition to the existing modules. Same split of views.

2) Now to the connectors. First of all, I like the direction of
thinking. The connectors should be more uniform, and it should be easier
to exchange one connector by a different one. 

(a) One of your ideas is to have a "run" interface for sequential
connectors. At the first glance, this looks very convincing. However, I
am not sure whether the users are put onto the right track. Both CGI and
FCGI allow implicit parallelism driven by the web server. This isn't
properly reflected by "run". The various "run" entries actually have
very different semantics: CGI runs only once, but this may happen in
parallel. FCGI runs several times, and in parallel. AJP runs several
times, but there is only one instance. The common idea of "run" is that
it is process-bound, and this is a quite low-level idea. I hope we can
find something better (see below).

(b) Regarding the socket interfaces: I don't understand how they work.
Can you sketch some examples?

(c) Unfortunately, the URL-binding stuff of AJP has been removed. The
point is that AJP differs from CGI and FCGI here. In CGI, the running
program is part of the file system backing the URL space: foo.cgi is
found because there is a file with the same name. For AJP this is not
true any more! In AJP, one binds a whole URL subtree to an AJP daemon
(e.g. the /servlet subtree such that the daemon is invoked
when /servlet/foo is requested as URL). It is now the task of the daemon
to dispatch the services that can be selected by the URL part following
the common prefix (e.g. invoke the "foo" handler for /servlet/foo).

This style is generally also applicable to CGI if you configure a CGI as
handler rather than the usual way. For the coming HTTP daemon, one needs
URL dispatching, too (there is already an implementation - look at
http://cvs.sourceforge.net/viewcvs.py/ocamlnet/ocamlnet/src/nethttpd/Attic/nethttpd_services.mli?rev=1.1.2.2&only_with_tag=ocamlnet-1-0-nethttpd&view=auto and http://cvs.sourceforge.net/viewcvs.py/ocamlnet/ocamlnet/examples/nethttpd/Attic/file_mt_reactor.ml?rev=1.1.2.2&only_with_tag=ocamlnet-1-0-nethttpd&view=auto)

In general, I would more like a unification of the connectors based on
their semantics (or better, semantic implications). In this approach one
would set up a connector in two steps:

First describe the aspects of the connector (run-time model, URL
binding, actions), possibly several connector styles the program can
support.

Second call the connector entry function with the description. The
function selects the right (or "best") connector style automatically.

I think this can be made very smooth by choosing the right defaults
(e.g. "any" as default run-time model; all input URLs are bound by
default). For example:

type runtime_model =
  [ `Any
  | `Sequential of ...
       (* would be interpreted strictly, e.g. in the CGI case the
        * activations would be serialized using mutexes
        *)
  | `Concurrent_processes of ...
       (* arguments describe that in detail: number of processes,
        * limit of the process lifetime etc. One can leave things
        * unspecified, especially when the web server controls them
        *)
  | `Concurrent_threads of ...
       (* Similar idea *)
  ]
  (* The arguments are objects, to allow subtyping *)

type binding_list =
  ( url_filter * action ) list

type binding =
  [ `All of action
        (* All URLs are handled by action *)
  | `Select of binding_list
        (* Select the first matching action *)
  ... (* maybe more styles *)
  ]

val cgi_entry : ?runtime_models:runtime_model list -> binding -> unit
val fcgi_entry : ?runtime_models:runtime_model list -> binding -> unit
val ajp_entry : ?runtime_models:runtime_model list -> binding -> unit

(* runtime_models defaults to [`Any] *)

(* test_entry will be special because input does not come from
 * environment
 *)

3) Concerning the Random module, I think this is much better solved in
Cryptokit. So better remove this module at all.

There is a simplification for `Transactional. Well, transactions with
file buffers are not very common, but sometimes one needs them.

Cookie module: This must be improved. Other languages have much better
cookie support. Especially one needs a cookie storage that is
independent from the representation in the header.

Shorter names for argument methods: Well, this is hard to change now. It
would break all programs at a lot of locations. 

What is the idea of arg_true? This sounds very Perlish, and imports bad
habits ("0" is false and e.g. "0E0" is true).

Addition of "log": Good idea, and it already happened in CVS, but in a
slightly different way. "log" is primarily part of the environment. It
is not a channel, because we cannot guarantee this. It is simply a
function like

val log : string -> unit

and means that a single message is logged (more following the syslog
interface), together with an automatically generated timestamp.

Maybe I overlooked other ideas in your proposal.

Gerd
-- 
------------------------------------------------------------
Gerd Stolpmann * Viktoriastr. 45 * 64293 Darmstadt * Germany 
[email protected]          http://www.gerd-stolpmann.de
------------------------------------------------------------




-------------------------------------------------------
SF.Net email is sponsored by: Discover Easy Linux Migration Strategies
from IBM. Find simple to follow Roadmaps, straightforward articles,
informative Webcasts and more! Get everything you need to get up to
speed, fast. http://ads.osdn.com/?ad_id=7477&alloc_id=16492&op=click