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

Christophe TROESTLER <[email protected]> Tue, 21 Jun 2005 21:32:19 +0200 (CEST)
Newsgroups gmane.comp.lang.ocaml.lib.net.devel
Organization Universite de Mons-Hainaut (http://math.umh.ac.be/an/)
Message-ID <[email protected]>
On Thu, 16 Jun 2005, Gerd Stolpmann <[email protected]> wrote:
> 
> we should discuss the various aspects in depth to see whether it is
> really worth changing the API. There is a very important downside
> [...] drive users away from using Ocamlnet.

That's why I proposed to develop the new modules in the sub-directory
netcgi/ at the same level as cgi/.  That way, nothing changes for
current users and one can develop the new API without hurry.  One
should also take this opportunity to remove the remaining limitations
in the library (e.g. FCGI string limit and develop AJP 1.3) and, IMHO,
to get rid of regexes (they are really simple so a convenience rather
than a real need; benefits are increased speed and, more importantly,
no dependency on a C lib which means easier porting -- I am thinking
especially about native win32 here).

> (a) [...] I call your approach "monolithic" and the current API "polylithic"

The fact that the interface is a single file is only a convenience to
present it.  I basically agree with your design (see below).

> (b) engineering. In the monolithic API, external people cannot add
> their own connectors to the module namespace. [...] difficult to
> have connectors optionally built.

I agree with that.  I am also thinking to add a mod_caml connector.

> (c) Code size. When the executable is linked, there is a step called
> "garbage collection" [...] only for top-level modules, [...]
> complaints why programs are so large.

Maybe it is a good time to reevaluate whether the current web of
dependencies is what we want.  See the attached Postscript files (in
the first one, the transitive dependencies are not show in the
interest of readability.

> As a compromise of the three mentioned aspects I can imagine the
> following: [...]

I agree with your division.  However I would not just add these
modules to the others (would probably just be more confusing) but to
develop them into a separate directory netcgi/, thus name them

Netcgi    (* base module, not connector specific; general aux fun if any *)
Netcgi_cgi         (* connectors sharing a common philosophy + specific *)
Netcgi_fcgi        (* functions per connector if needed *)
Netcgi_ajp
Netcgi_mod_caml
Netcgi_...


> 2) Now to the connectors.

The tough part. :)

> (a) One of your ideas is to have a "run" interface for sequential connectors.

The idea of [run] was more: no hassle, I'll use whatever default
concurrency is provided by the connector; I just want my script to
run.  I thought (maybe naively) that, in many cases, it is just what
one wants.

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

  open Netcgi_ajp

You create a socket with [socket()].  Then, several processes or
threads can accept connections on it.  Then you have to decide whether
you want to deal with connections in separate threads or processes
(e.g. with a pool of threads).  If one does not want these, one can
use [accept_threads] with [~thread:(fun f -> f())] (the default).  If
one wants a new thread or to access a pool of threads, define
[~thread] accordingly.  For [accept_fork], on passes a [fork]ing
function which must return the process id and a file descriptor to
communicate with the script (e.g. say "shutdown script").  A simple
example of fork is

  let fork f =
    let (infd, outfd) = Unix.pipe () in
    match Unix.fork() with
    | 0 -> f outfd; exit 0  (* or the double fork trick *)
    | n -> n, infd

but of course it is also possible to send the job to a pool of
processes.

After accept, one can run a ['a connection_handler].  It can be
created with [handle_connection ~cookie:StringCookie.marshal f]
running [f] on each connection and reacting appropriately if [f]
raises an exception -- i.e. log it and exit gracefully (do not let the
thread or process die abruptly).

There are a few details to get right in the signatures but you get the
idea.

> (c) Unfortunately, the URL-binding stuff of AJP has been removed.

I'll read more about AJP.  Do you have a good references besides the
spec?  How is this different from PATH_INFO ?

> 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 *)
>   ]

It is fine with me as long as one can discriminate the "end of URL"
with e.g. [#cgi_path_info] -- sometimes dynamic URLs are indeed
needed.  Now, if one cannot imagine an additional style, maybe

  type binding = (url_filter * action) list
  and url_filter = url -> bool

is enough -- (fun _ -> true) is easy enough to write -- or maybe a
[any_url] can be provided which also allows to have a "default" (catch
all) final case:

  [(fun s -> start_with s "admin/",  admin_script);
   (any_url, default_script)
  ]

If an URL that is not matched is requested, a default error message is
sent.

> For the coming HTTP daemon, one needs URL dispatching, too

I will have a look at it -- no time yet.

> In general, I would more like a unification of the connectors based
> on their semantics (or better, semantic implications).  [...]

This is a good idea.  My only worry is flexibility (see below).

> 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 *)
> 
> val cgi_entry : ?runtime_models:runtime_model list -> binding -> unit

(Here I do not understand why the [runtime_models] is a list.)  Does
[`Any] means "the default model of the connector"? -- if so, maybe
[`Default] or [`Dont_care] is better?

What is not clear to me is how to make this general enough not to need
the low level stuff anymore (at least 99.9% of the time).  For
example, how do you access an existing pool of threads?

I'll have to think over it more but in the interest of having your
point of view on other issues, I send the mail now... :)

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

Yes, a convenient test connector has yet to be designed.  The problem
is that there are two kind of testing that are interesting: offline
testing (run in a shell) and "online" testing (run through the web
server).  In the later case, one may want to "frame" the output to add
debugging controls or information in other frames... or at least offer
the primitives that make that possible by a "template system".

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

Ok.  A reference to it in the doc would be nice though.
(Done in http://ocaml-cgi.sourceforge.net/netcgi/Netcgi.html).

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

Not sure what you mean here.

> 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.

You mean for setting and retrieving data?    There is unfortunately no
convenient way of storing any value in a cookie since we do not have
dynamic typing.  So we have to precise beforehand what types we want
to encode/decode (and how).  That implies that [environment] and
[cgi] will carry a parameter, the type of cookies.  The problem is how
to instantiate this type.

One possibility is to functorize all connectors.  This seems too heavy
to me.

So I adopted the idea of requiring a ~cookie argument to connectors to
handle encoding and decoding of these.  For a given connector, only 1
cookie type is possible (as a consequence of static typing) but of
course it may be a variant.  A default StringCookie is available.  A
functor creates Cookie modules with specialized types and encoding /
decoding functions.  See
http://ocaml-cgi.sourceforge.net/netcgi/Netcgi.html#2_Cookies for
further details (some names may need to be changed for better ones).

One may also imagine adding an [output_js : t cookie -> string] that
returns some javascript code for setting the cookie.

Is that the kind of thing you have in mind?  Otherwise, could you
detail and/or give pointers?

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

Since the old module will still exists, the new ones will only be used
for new programs.  [arg*] are likely to be used very often so short
meaningful names are an asset I believe.  (Now, one may provide a
script for all s/argument_value/arg_val/ ... if wanted.)

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

Well, yes.  Maybe a #arg_exists is better (true only if the arg is
present, whatever its value).  It is just a convenience to toggle
things.

> "log" is primarily part of the environment.  [...] not a channel,
> because we cannot guarantee this. It is simply a function like val
> log : string -> unit

This is fine (and adopted in the current version).

> Maybe I overlooked other ideas in your proposal.

Well, I removed the possibility to mutate arguments as I argued in a
previous mail (it is better for libraries build on top not to
interfere).  Since you said nothing about that, I assume you agree. :)

ChriS

---
P.S.  I have brought up to date http://ocaml-cgi.sourceforge.net/netcgi/
with the above discussion.
ocamlnet-trans-reduc.ps (application/postscript, 26.9 KB) - not displayed
ocamlnet.ps (application/postscript, 42.8 KB) - not displayed