(* * <COPYRIGHT> * Copyright 2002 Joachim Schrod Network and Publication Consultance GmbH, Gerd Stolpmann * * <GPL> * This file is part of WDialog. * * WDialog is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * WDialog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WDialog; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * </> *) (* $Id: wd_cycle.ml,v 3.15 2005-06-11 14:24:14 stolpmann Exp $ * ---------------------------------------------------------------------- * *) open Wd_types open Printf module S = Netstring_str let debug = false;; (* Note debug messages: * These are written to stderr. The Apache server adds such messages to * the error log. Other servers (e.g. Netscape) do not do this; in this * case it is recommended to start the CGI program with a shell script * that redirect stderr to a log file. *) class empty_dialog universe name env = object (* An empty dialog implementation *) inherit Wd_dialog.dialog universe name env method prepare_page() = () method handle() = () end ;; (* TODO: document the 'force_handle_invocation' hack. * - Some notes: * Normally, when the application has just been started, there is no need * to invoke the 'handle' callback; there has been no previous user interaction * and so the application need not to react on it. When the form is * submitted by a Javascript handler, a similar situation happens; no * user interaction is recognized. However, there are situations * requiring a 'handle' invocation even in these cases; to enforce that * the 'handle' callback is always performed, one can set the CGI variable * 'force_handle_invocation' to a value other than "". *) let extended_arguments (cgi:Netcgi.cgi_activation) = (* Return the CGI arguments plus the decoded contents of * 'uiobject_extra_args'. * - Some notes: * Normally, the web browser simply posts the form containing the * CGI parameters. This technique is simple, but it is limited because * it must be known which parameters exist in the form at the moment * when the form is created (by HTML elements). However, some interactions * require that parameters must be dynamically added just before the * form is submitted. The special CGI parameter 'uiobject_extra_args' * can be used for this purpose: It is interpreted as url-encoded set * of extra parameters that are added to the ordinary set of * CGI parameters. * From Javascript, it is a simple task to set 'uiobject_extra_args' * because there is a function (escape) actually url-encoding its * input. *) lazy begin let cgi_args = List.map (fun arg -> arg#name, arg) cgi # arguments in let extra = cgi # argument_value ~default:"" "uiobject_extra_args" in let extra' = Pcre.qreplace ~pat:"\\+" ~templ:"%2b" extra in (* Unfortunately, the Javascript function 'escape' does not protect * the plus character. So we do it here; otherwise '+' would be * converted to space. *) let extra_decoded = Netencoding.Url.dest_url_encoded_parameters extra' in (* TODO *) let extra_args = List.map (fun (n,v) -> n, (Netcgi.Argument.simple n v)) extra_decoded in extra_args @ cgi_args (* Note: extra_args are returned first. Usually, this means that CGI * parameters occuring in both lists are preferred from extra_args, * such that the dynamically added parameters can even override the * statical ones. *) end ;; let debug_print_args cgi = (* Dumps the real set of CGI parameters to stderr *) prerr_endline "*** Beginning dump of CGI parameters:"; List.iter (fun arg -> prerr_endline (arg#name ^ "=" ^ arg#value); ) (cgi # arguments); prerr_endline "--- End of dump"; ;; let arg cgi n = let args = Lazy.force (extended_arguments cgi) in List.assoc n args ;; let argval cgi n = (arg cgi n) # value ;; (* let ( !% ) n = (* !% "name": returns the CGI parameter called "name" as abstract value, * or raises Not_found if it does not exist. *) List.assoc n (arguments()) ;; let ( !$ ) n = Cgi.arg_value (!% n) (* !$ "name": returns the CGI parameter called "name" as string value, * or raises Not_found if it does not exist. *) ;; *) let update_variables_from_cgi cgi dlg= (* Examines the CGI parameters for changed variable values. For example, * if the previous page contained a text box, the variable bound to the * box must now be updated to the value of the corresponding CGI * parameter. *) let ( !$ ) = argval cgi in let var_re = S.regexp "^var_\\(.*\\)$" in let digits_re = S.regexp "^[0-9_]+$" in let ia = dlg # interactors in let dlg_name = dlg # name in let dlg_decl = dlg # declaration in (* Iterate over all CGI parameters and recognize the parameters whose * name match 'var_re'. These parameters contain the new variable * values. * Furthermore, we must find out which variable is meant by a CGI * parameter. If the suffix (the string following 'var_') of the * CGI name is a number, we can look up the name of the variable in * the interactor structure. * If the suffix is not a number, we assume that the suffix already * denotes the name of the variable (and that the variable is not * associative). Note that you can force this type of CGI parameter * by using the cgi="keep" attribute (in the XML element). * The name_index_list is a list of (vname, (vindex, vvalue)), * where vname is the variable name, vindex is the variable index * (or "" if not applicable), and vvalue is the new value of the * variable. * * Note: protected variables are simply ignored. *) let name_index_list = List.flatten (List.map (fun (cgiparam,arg) -> match S.string_match var_re cgiparam 0 with Some r -> let v = arg#value in let n = S.matched_group r 1 cgiparam in if S.string_match digits_re n 0 <> None then begin (* The variable is passed by numerical ID *) let (name,index,_) = Wd_interactor.lookup ia.ui_vars n in [ name, (index,v) ] end else begin (* The variable is passed by name *) [ n, ("",v) ] end | None -> [] ) (Lazy.force(extended_arguments cgi)) ) in (* visble_pages contains the names of the pages that were * visible at the moment the form was submitted (i.e. the * name of the main page and the names of the popup pages). * When a popup window is submitted, this means that the * fields of the form are copied to the form of the main * window, and that the form in the main window is actually * submitted. Consequently, not only the interactors of the * popup window exist, but also the interactors of the main * window. * The variable 'uiobject_visible_pages' contains a space-separated * list of the pages that were submitted in the last action; * i.e. either the page name of the only visible window, or the * page names of the main window and the popup window. * In the following code, an interactor is only accepted * if it occurs on a page listed in visible_pages. *) let visible_pages = let v = " " ^ try !$ "uiobject_visible_pages" with Not_found -> "" in S.split (S.regexp "[ \t]+") v in (* findall n0 name_index_list: * Returns all (vindex, vvalue) pairs in name_index_list for the * variable n0. *) let findall n0 l = List.map snd (List.filter (fun (n,v) -> n=n0) l) in (* findenums n0 name_index_page_list: * Returns all (vindex, vvalue, page) pairs in name_index_page_list for the * variable n0. *) let findenums n0 l = List.map (fun (a,b,c) -> b) (List.filter (fun (n,v,pg) -> n=n0 && List.mem pg visible_pages) l) in let definedenum n0 l = List.exists (fun (n,v,pg) -> n=n0 && List.mem pg visible_pages) l in let rec unprotected_variables dlg = (* Returns the unprotected variables of [dlg] and of subdialogs; * names of dialog variables are not returned *) let dlg_decl = dlg # declaration in let unprotected = List.filter (fun vname -> not (dlg_decl # variable vname).var_protected) dlg_decl#variable_names in (* Expand dialogs: *) List.flatten (List.map (fun vname -> if (dlg_decl # variable vname).var_type = Dialog_type then begin match dlg # dialog_variable vname with Some dlg' -> let unprotected' = unprotected_variables dlg' in List.map (fun vname' -> vname ^ "." ^ vname') unprotected' | None -> [] (* drop this variable *) end else [vname] ) unprotected ) in (* Iterate over all declared variables of the dialog, look up whether * there are values for the variables in name_index_list, and update * the values stored in the dialog. *) List.iter (fun vname -> let var = dlg # variable_decl vname in (* Contains the declaration of the variable *) let index_value_pairs = findall vname name_index_list in (* All (vindex,vvalue) pairs for the variable 'vname' *) let update_alist_value new_associations = (* Update the associations of the alist variable 'vname': The * associations in 'new_associations' are changed; the other * associations are kept. * The order of the old associations is retained. Totally new * entries are appended. *) let old_associations = dlg # alist_variable vname in let new_dict = Wd_dictionary.of_alist new_associations in let new_dict' = ref Wd_dictionary.empty in let al = List.map (fun (n,v) -> try let v' = Wd_dictionary.find n new_dict in (* or Not_found *) new_dict' := Wd_dictionary.add n () !new_dict'; (n,v') with Not_found -> (n,v) ) old_associations in let al' = ref [] in Wd_dictionary.iter (fun n v -> if not(Wd_dictionary.mem n !new_dict') then al' := (n,v) :: !al' ) new_dict; dlg # set_variable vname (Alist_value (al @ !al')); in (* How the values from the CGI parameters are to be interpreted mainly * depends on the type of the variable. So here is a case for every * variable type. *) match var.var_type with String_type -> if var.var_associative then begin (* Associative strings: The new (vindex,vvalue) pairs replace * the existing associations from indexes to values. * Associations for which no index has been passed by CGI * parameter are kept as they are. *) let new_strings = List.map (fun (x,s) -> x,String_value s) index_value_pairs in update_alist_value new_strings end else begin (* Non-associative strings: Set the value to one of the * values found in name_index_list (should be * [ name, ("", value) ] ). *) try let _, v = List.assoc vname name_index_list (* or Not_found *) in dlg # set_variable vname (String_value v) with Not_found -> () end | Enum_type _ -> (* Enumerators: We must only set the enumerator variables for * which there was an interactor on the last page (otherwise * the values of enumerators would be reset without reason). * The list of enumerators of the last page is stored in * ia.ui_enumvars. *) if var.var_associative then begin (* Associative enumerators: Set only the associations * occuring in ia.ui_enumvars. *) let relevant_index_values = findenums vname ia.ui_enumvars in (* Only these index values must me set! *) let new_enums = List.map (fun some_indexvalue -> match some_indexvalue with None -> assert false | Some indexvalue -> let values = findall indexvalue index_value_pairs in indexvalue, Enum_value values ) relevant_index_values in update_alist_value new_enums end else begin (* Non-associative enumerators: Set the enumerator value * only if the name occurs in ia.ui_enumvars. *) if definedenum vname (ia.ui_enumvars) then begin let values = List.map snd index_value_pairs in dlg # set_variable vname (Enum_value values) end end | Dyn_enum_type -> (* Dynamic enumerators: Same problem as with simple enumerators; * we must only set those enumerators occuring in the last * page. *) if var.var_associative then begin let relevant_index_values = findenums vname ia.ui_enumvars in let new_enums = List.map (fun some_indexvalue -> match some_indexvalue with None -> assert false | Some indexvalue -> let values = findall indexvalue index_value_pairs in indexvalue, Dyn_enum_value (List.map (fun v -> v, "") values) ) relevant_index_values in update_alist_value new_enums end else begin if definedenum vname (ia.ui_enumvars) then begin let values = List.map snd index_value_pairs in dlg # set_variable vname (Dyn_enum_value (List.map (fun v -> v, "") values)) end end | Dialog_type -> (* It is not possible to set dialog variables by CGI parameters *) () ) (unprotected_variables dlg) ;; exception Result of (event * string option);; (* Used locally in get_event_from_cgi *) let get_event_from_cgi cgi dlg = (* Examine the CGI parameters, and search the parameter expressing the * last event ("click"). * Return a pair (event, default_next_page) denoting the found event * and the page that should be displayed next (unless overridden by * the 'handle' method of the dialog). * The 'default_next_page' may be set by the "goto" attribute of the * interactor that caused the event; because of this, the * default_next_page is determined in this function. *) let ( !$ ) = argval cgi in let ia = dlg # interactors in let button_re = S.regexp "^button_\\(.*\\)$" in let xbutton_re = S.regexp "^xbutton_\\(.*\\)$" in let anchor_re = S.regexp "^anchor_\\(.*\\)$" in let xanchor_re = S.regexp "^xanchor_\\(.*\\)$" in let ibuttonx_re = S.regexp "^imagebutton_\\(.*\\)\\.x$" in let ibuttony_re = S.regexp "^imagebutton_\\(.*\\)\\.y$" in let xibuttonx_re = S.regexp "^ximagebutton_\\(.*\\)\\.x$" in let xibuttony_re = S.regexp "^ximagebutton_\\(.*\\)\\.y$" in (* 'matchers': This is a list of functions which will be tried in turn * in order to find the CGI parameter expressing the event. The functions * have signature * (cginame:string) * (cgivalue:string) -> event * (nextpage:string), * i.e. they get the name of the CGI parameter and its value as input, * and the function returns the event and the name of the next page * if it recognizes the CGI parameter. The function may also raise Not_found * to indicate that it does not know the kind of parameter. * Later, every CGI parameter will be passed to every function, and the first * function that does not raise Not_found recognizes the event, and the * result of the function is taken as event and default next page. * * There is a bug in some browsers causing the following strange * behaviour: * When the user clicks on a ui:a anchor, the form is submitted; however * the first submit button is also selected. * To work around this bug, the first matchers are the anchor matchers * and the loop searching the right CGI parameter first tries to match * all parameters with the anchor pattern. So the anchor will be found before * the submit button. *) let matchers = [ (fun (n,arg) -> (* Test on (non-indexed) anchor events *) match S.string_match anchor_re n 0 with Some r when arg#value <> "0" -> let id_str = S.matched_group r 1 n in let (name, _, goto) = Wd_interactor.lookup ia.ui_anchors id_str in (* Note: Anchor events are represented as Button, too *) Button name, goto | _ -> raise Not_found ); (fun (n,arg) -> (* Test on indexed anchor events *) match S.string_match xanchor_re n 0 with Some r when arg#value <> "0" -> let id_str = S.matched_group r 1 n in let (name, index, goto) = Wd_interactor.lookup ia.ui_indexed_anchors id_str in (* Note: Anchor events are represented as Button, too *) Indexed_button (name,index), goto | _ -> raise Not_found ); (fun (n,arg) -> (* Test on (non-indexed) button events. *) match S.string_match button_re n 0 with Some r -> let id_str = S.matched_group r 1 n in let (name, _, goto) = Wd_interactor.lookup ia.ui_buttons id_str in Button name, goto | _ -> raise Not_found ); (fun (n,arg) -> (* Test on indexed button events *) match S.string_match xbutton_re n 0 with Some r -> let id_str = S.matched_group r 1 n in let (name, index, goto) = Wd_interactor.lookup ia.ui_indexed_buttons id_str in Indexed_button(name,index), goto | _ -> raise Not_found ); (fun (n,argx) -> (* Test on (non-indexed) image buttons *) match S.string_match ibuttonx_re n 0 with Some r -> let vx = argx#value in let id_str = S.matched_group r 1 n in let (name, _, goto) = Wd_interactor.lookup ia.ui_imagebuttons id_str in let ny = "imagebutton_" ^ id_str ^ ".y" in let vy = try !$ ny with Not_found -> failwith ("Cannot find CGI param " ^ ny) in Image_button(name, int_of_string vx, int_of_string vy), goto | _ -> raise Not_found ); (fun (n,argx) -> (* Test on indexed image buttons *) match S.string_match xibuttonx_re n 0 with Some r -> let vx = argx#value in let id_str = S.matched_group r 1 n in let (name, index, goto) = Wd_interactor.lookup ia.ui_indexed_imagebuttons id_str in let ny = "ximagebutton_" ^ id_str ^ ".y" in let vy = try !$ ny with Not_found -> failwith ("Cannot find CGI param " ^ ny) in Indexed_image_button(name, index, int_of_string vx, int_of_string vy), goto | _ -> raise Not_found ); ] in let event, default_next_page = (* Iterate over every combination of CGI parameter and matcher function, * and try the matcher function. * On success, the found event is passed back using the 'Result' * exception (to jump out of the two iterators). * * Note: Because of the mentioned browser bug, this loop iterates first * over all matchers, and then over the CGI parameters; see the explanations * above. *) begin try List.iter (fun matcher -> List.iter (fun (n,arg) -> try raise (Result (matcher (n,arg))) (* will be caught right below *) with Not_found -> () ) (Lazy.force (extended_arguments cgi)) ) matchers; (* If no matcher function recognizes a CGI parameter: *) No_event, None with (* Catch the exception used to exit from the search loop: *) Result (e,g) -> e,g end in (event, default_next_page) ;; (* let printf (cgi:Netcgi_types.cgi_activation) (arg : ('a, Buffer.t, unit) format) : 'a = let b = Buffer.create 16 in Printf.bprintf b arg; cgi # output # output_buffer b ;; *) let default_response_header = { rh_status = `Ok; rh_content_type = "text/html"; rh_cache = `Unspecified; rh_filename = None; rh_language = None; rh_script_type = None; rh_style_type = None; rh_set_cookie = []; rh_fields = []; } ;; let make_environment cgi = { debug_mode = false; debug_mode_style = `Partially_encoded; prototype_mode = false; server_popup_mode = false; self_url = ""; response_header = default_response_header; cgi = cgi; } ;; let in_session_scope session f = let dlg = session # dialog in dlg # enter_session_scope session; try f dlg; dlg # leave_session_scope() with any -> dlg # leave_session_scope(); raise any ;; let process_request ?(session_manager = new Wd_dialog.instant_session_manager()) ?self_url ?(response_header = default_response_header) (universe:universe_type) (cgi:Netcgi.cgi_activation) = (* Main processing function: Interprets CGI parameters, and outputs * the next page according to CGI. *) (* General note about CGI parameters: * We must avoid to get the value of arbitrary CGI parameters, because this * would mean that also uploaded files are loaded into memory. *) let ( !$ ) = argval cgi in if debug then debug_print_args cgi; let t0 = Unix.gettimeofday() in let ui_pipe_ch = (* If present, open pipe to uidebugger right at the beginning. * Otherwise it may happen that the pipe is never closed, and the * uidebugger hangs. *) try let ui_pipe = Sys.getenv "UI_PIPE" in Some(open_out ui_pipe) with Not_found -> None in let prototype_mode = universe # application # prototype_mode in (* Prototype mode: If there is no registered dialog for a declared * dialog, an empty implementation of the dialog will be assumed. *) let force_handle_invocation = universe # application # onstartup_call_handle in (* force_handle_invocation: Forces that the 'handle' method of the * dialog is called even if there was no previous event (e.g. * when the application is started). * (There is also a comment above about force_handle_invocation.) *) let server_popup_page = try !$ "uiobject_server_popup" with Not_found -> "" in let is_server_popup_request = server_popup_page <> "" in (* server_popup_page: Is set if a popup window has been opened whose * contents are dynamically generated (so-called "server popup windows"). * server_popup_page contains the name of the page that will be displayed * in the popup window. *) let env = { debug_mode = universe # application # debug_mode; debug_mode_style = universe # application # debug_mode_style; prototype_mode = prototype_mode; server_popup_mode = is_server_popup_request; self_url = (match self_url with Some url -> url | None -> cgi#url()); response_header = { response_header with rh_status = response_header.rh_status }; cgi = cgi; } in if prototype_mode then begin (* Add empty dialogs as needed: *) List.iter (fun name -> (* Does the dialog with 'name' exist? *) try ignore(universe # create env name) with Not_found -> (* No: Register an empty implementation with 'name' *) universe # register name (new empty_dialog)) (universe # application # dialog_names) end; (****************************** STAGE 1 *****************************) (* First, we get the current dialog, and modify it. The current event * must be set in this dialog; and the variables of the dialog must * updated (from the values passed in CGI parameters). *) (* Decode the current session *) let session = try let s = !$ "uiobject_session" in (* or raise Not_found *) let s_obj = session_manager # unserialize universe env s in (* Server popup request: Create a copy of the session! *) if is_server_popup_request then session_manager # create (s_obj # dialog) else s_obj with Not_found -> if debug then prerr_endline "Starting with brand-new dialog!"; let n = universe # application # start_dialog_name in let start_dlg = try universe # create env n with Not_found -> raise (Runtime_error ("The start dialog has claimed to be `" ^ n ^ "'; however such a dialog has not been found in the registered universe of dialogs")) in session_manager # create start_dlg in let t1 = ref 0.0 in in_session_scope session (fun dlg -> (* If this is a server popup request, store this piece of information * in dlg such that dlg knows that the current request comes from a new * popup window. *) if is_server_popup_request then dlg # set_server_popup_request(); if debug then begin prerr_endline "*** Dump of initial dialog:"; dlg # dump (Format.err_formatter); prerr_endline "\n--- End of dump"; end; (* TODO: check here all pages, and check whether the "goto" attributes of * all buttons are valid page names. *) if is_server_popup_request then begin try if not(dlg # declaration # page_is_declared_as_popup server_popup_page) then raise(Runtime_error("The popup page `" ^ server_popup_page ^ "' has been requested, but this page is not declared as popup page")); with Not_found -> raise(Runtime_error("The popup page `" ^ server_popup_page ^ "' has been requested, but this page does not exist")) end; (* Examine the CGI parameters for the current event: * Normally, the CGI parameter describing the last "click" (anchor or * button) is searched, and the page associated with this click is * returned (this does get_event_from_cgi). * However, if this request results from opening a server popup, * the event is Popup_request. *) let (event, default_next_page) = if not is_server_popup_request then get_event_from_cgi cgi dlg (* normal case *) else begin let index = try !$ "uiobject_popup_index" with Not_found -> "" in Popup_request index, Some server_popup_page (* server popup request *) end in (* Set the event in 'dlg', and set the next page in 'dlg': *) dlg # set_event event; begin match default_next_page with None -> (* The default default next page is the current page: *) dlg # set_next_page (dlg # page_name); | Some n -> dlg # set_next_page n end; (* Update variables in 'dlg' from CGI parameters: * (Skip if this is a server popup request; such requests do not set any * variables.) *) if not is_server_popup_request then update_variables_from_cgi cgi dlg; (****************************** STAGE 2 *****************************) (* The 'dlg' is now ready for further processing. The 'handle' method * of the dialog is invoked. * The result may be * - (a) we continue with this dialog * - (b) we continue, but on a different page * - (c) we change to a new dialog *) t1 := Unix.gettimeofday(); if event <> No_event || force_handle_invocation then begin try dlg # handle(); (* Case (a) *) with Change_dialog dlg' -> (* Case (c) *) (* This is illegal in a popup request, because the page displayed * in the popup window must belong to the same dialog as the * page in the main window. *) if is_server_popup_request then raise(Runtime_error("The 'handle' method tried to change the current dialog although this is a request for a popup window")); session # change_dialog dlg'; | Change_page pg -> (* Case (b) *) dlg # set_next_page pg; end; ); (* Terminate old session scope... *) in_session_scope session (fun dlg -> (* ... because a different dialog might be in scope now! *) let dlg_name = dlg # name in let dlg_decl = dlg # declaration in (****************************** STAGE 3 *****************************) (* Now, 'dlg' denotes the dialog that displays the next page. * The 'prepare_page' method is invoked such that the dialog * has the chance to set further variables. *) let t2 = Unix.gettimeofday() in (* Tell the dialog which page comes next: *) dlg # init (dlg # next_page); let t3 = Unix.gettimeofday() in dlg # prepare_page(); (* Maybe we have to init again, because set_next_page has been called * from prepare_page: *) if dlg # page_name <> dlg # next_page then ( dlg # init (dlg # next_page); ); (****************************** STAGE 4 *****************************) (* The state of 'dlg' is now ready such that the HTML page can be * generated. *) let t4 = Unix.gettimeofday() in (* Get the next page (as syntax_tree): *) let page = try (dlg # declaration) # page (dlg # page_name) with Not_found -> raise (Runtime_error ("The next page `" ^ (dlg # page_name) ^ "' is not declared")) in let vars = { within_popup = is_server_popup_request; current_page = "<outside>"; popup_env_initialized = false; condition_code = false; serialize_session = (fun () -> session # commit_changes(); session # serialize; ) } in let rh = env.response_header in cgi # set_header ~status:rh.rh_status ~content_type:rh.rh_content_type ~cache:rh.rh_cache ?filename:rh.rh_filename ?language:rh.rh_language ?script_type:rh.rh_script_type ?style_type:rh.rh_style_type ~set_cookie:rh.rh_set_cookie ~fields:rh.rh_fields (); (* Generate HTML: *) Wd_transform.to_html ~vars dlg (cgi # output :> Netchannels.out_obj_channel) page; let want_stats = Netstring_pcre.string_match (Netstring_pcre.regexp "^text/html") env.response_header.rh_content_type 0 <> None in (* Finally, some statistics is appended: *) if want_stats then begin let b = Buffer.create 5000 in let t5 = Unix.gettimeofday() in bprintf b "\n<!-- Real time for process_request until start of output: -->\n"; bprintf b "<!-- Total time: %5.1f seconds -->\n" (t5 -. t0); bprintf b "<!-- Initialization: %5.1f seconds -->\n" (!t1 -. t0); bprintf b "<!-- 'handle': %5.1f seconds -->\n" (t2 -. !t1); bprintf b "<!-- Setting up next page: %5.1f seconds -->\n" (t3 -. t2); bprintf b "<!-- 'prepare_page': %5.1f seconds -->\n" (t4 -. t3); bprintf b "<!-- Generation: %5.1f seconds -->\n" (t5 -. t4); let p = Unix.times() in bprintf b "\n<!-- Process time until start of output: -->\n"; bprintf b "<!-- User time: %5.1f seconds -->\n" (p.Unix.tms_utime +. p.Unix.tms_cutime); bprintf b "<!-- System time: %5.1f seconds -->\n" (p.Unix.tms_stime +. p.Unix.tms_cstime); let gc = Gc.stat() in bprintf b "\n<!-- Memory consumption: -->\n"; bprintf b "<!-- Number of minor collections: %d -->\n" (gc.Gc.minor_collections); bprintf b "<!-- Number of major collections: %d -->\n" (gc.Gc.major_collections); cgi # output # output_buffer b; end; if env.debug_mode && want_stats then begin (* Add 'ps' output: (Assumes Unix98 ps) *) let ps_out = Unix.open_process_in ("ps -l -y -p " ^ (string_of_int (Unix.getpid())) ^ " 2>/dev/null") in try while true do let line = input_line ps_out in printf "<!-- ps: %s -->\n" line done with End_of_file -> ignore(Unix.close_process_in ps_out) end; (* If working with uidebuggger, output the new dialog state now to the * communication pipe. *) (* begin match ui_pipe_ch with Some ch -> output_string ch dlg_name; output_string ch "\n"; output_string ch (Netencoding.Base64.encode (dlg # serialize)); output_string ch "\n"; close_out ch; | None -> () end; *) ); () ;; (* ====================================================================== * History: * * $Log: wd_cycle.ml,v $ * Revision 3.15 2005-06-11 14:24:14 stolpmann * Extension of bracket expressions: many new functions. * Functions in bracket expressions may now lazily evaluate their arguments. * ui:if and ui:ifvar may refer to any functions defined for bracket * expressions. * New: ui:ifexpr * Parsing of bracket expressions is now done with ulex. Private interfaces * of PXP are no longer used for this purpose. * Serialization has been rewritten, and it is now safe. * Fix: In ui:context there may now be macro references at any location. * Also documented all these changes. * * Revision 3.14 2004/12/12 17:57:32 stolpmann * Added <q:wd-link> and <q:wd-xlink> to generate links for * applications that cannot use Javascript. Limited functionality, however. * See stdlib.xml for details. * * Revision 3.13 2004/08/01 18:30:33 stolpmann * Removed dependency on Cgi. * * Revision 3.12 2003/03/09 17:09:12 stolpmann * Fix: The current session is duplicated when a server popup * request arrives * * Revision 3.11 2003/02/16 23:48:15 stolpmann * Improved wd-debug-mode: there are now two styles * * Revision 3.10 2003/01/26 17:59:46 stolpmann * new: Wd_cycle.make_environment * * Revision 3.9 2003/01/26 16:03:46 stolpmann * It is now allowed to call [set_next_page] from [prepare_page]. * * Revision 3.8 2003/01/15 23:04:28 stolpmann * Statistics are only appended if output type is text/html * * Revision 3.7 2003/01/04 21:55:25 stolpmann * new record response_header * * Revision 3.6 2002/11/09 12:19:12 stolpmann * Fix: ui:select accepts dot notation * * Revision 3.5 2002/11/06 01:43:51 stolpmann * Fix: digit_re * * Revision 3.4 2002/06/06 12:03:59 stolpmann * Fix: when a popup request is received, the variable * [within_popup] is set to [true]. * Workaround: In current versions of ocamlnet there is * a bug in [split]. By prepending a space character to * [visible_pages], the problematic case can be avoided. * * Revision 3.3 2002/02/26 16:00:42 stolpmann * Fix: The order of associative variables is preserved if * possible. * * Revision 3.2 2002/02/14 16:15:21 stolpmann * Added copyright notice. * * Revision 3.1 2002/02/12 20:28:59 stolpmann * Initial release at sourceforge. * * Revision 1.20 2002/02/11 11:14:21 gerd * Fix: It is now possible to change the dialog of sessions (required * for the Change_dialog exception) * * Revision 1.19 2002/02/05 18:51:22 gerd * Support for session managers. * Protected variables can no longer be set by CGI parameters. * Popup pages must be declared with popup="yes". * * Revision 1.18 2002/01/31 23:06:39 gerd * Revised conditional expansion (ui:if, ui:ifvar, ui:iflang, * ui:cond). * Added some support for internationalization (xml:lang). * * Revision 1.17 2002/01/14 15:03:23 gerd * Major change: Typing has been completely revised, and almost * every tiny thing has now a new type. Also renamed a lot. * * Revision 1.16 2001/05/21 12:54:24 gerd * Removed debugging output: "OCaml memory in words". The * type changed from int to float in O'Caml 3.01, and this information * is not very useful at all. * * Revision 1.15 2001/01/15 12:28:52 gerd * Fix: Added close_process_in for ps_out * * Revision 1.14 2000/12/21 17:08:57 gerd * Fixed the workaround. * * Revision 1.13 2000/12/21 17:05:22 gerd * Workaround for the strange behaviour of the Javascript * 'escape' function which does not protect '+'. * * Revision 1.12 2000/12/21 15:00:37 gerd * Server popups: The generated "open" function has now a second * parameter which is passed back to the application. The Popup_request * event gets this parameter. The parameter can be used freely by the * application. * * Revision 1.11 2000/12/06 17:52:45 gerd * CGI arguments can be reset. * * Revision 1.10 2000/12/06 15:25:59 gerd * Support for the ~self_url parameter. * * Revision 1.9 2000/11/30 18:39:28 gerd * Implementation of ui:server-popup. * * Revision 1.8 2000/09/25 13:22:13 gerd * New ui:popup element * * Revision 1.7 2000/05/15 11:46:33 gerd * Necessary changes for uidebugger. * * Revision 1.6 2000/05/09 14:27:15 gerd * Added many comments; changed the structure to make the * concepts clearer. * Furthermore, support for the "cgi" attribute. * * Revision 1.5 2000/05/08 17:56:22 gerd * Changed such that arbitrary strings can be used as interactor * IDs as well as automatically generated numbers. * * Revision 1.4 2000/05/08 15:30:08 gerd * The default context is passed to the 'to_html' invocation. * * Revision 1.3 2000/05/08 10:34:32 gerd * Dynamic parameters: An empty initial context is used. This * is questionable, but currently the only possible way. * * Revision 1.2 2000/04/17 10:10:52 gerd * New Cgi module. * File upload strongly improved. * * Revision 1.1 2000/04/13 17:42:58 gerd * Initial revision. * * *)