Eliom客户端到客户端消息传递 - 外部参照范围问题

时间:2015-08-23 22:03:30

标签: ocaml communication reactive-programming instant-messaging ocsigen

我最近一直试图更好地了解Eliom的通信功能,为此我尝试构建一个简单的网页,允许用户互相发送消息。

如果我通过Firefox以及第二个用户通过Chrome登录,则网页可以正常工作。但是当我在同一个浏览器中登录两个不同的用户并尝试将消息从一个用户发送到另一个用户(即从一个选项卡到另一个选项卡)时,发送的任何消息都显示在所有选项卡上,而不是预期的收件人'仅限标签。

我认为我可能在选择erefs范围或我设置/获取范围和erefs(顶层与服务定义)方面存在一些问题。

我正在尝试纠正我的错误,以便两个用户可以登录到同一浏览器的两个不同选项卡并相互发送消息,并且消息仅显示在正确的用户选项卡上。

注意:此代码中的一部分来自Eliom网站教程: http://ocsigen.org/tuto/4.2/manual/how-to-implement-a-notification-system

我的.eliom文件:

(* Example website login: localhost:8080/?user_num=1 *)

{shared{
  open Eliom_lib
  open Eliom_content
  open Html5
  open Html5.F
  open Eliom_registration
  open Eliom_parameter
}}

module Channel_example_app =
  Eliom_registration.App (
    struct
      let application_name = "channel_example"
    end)

let main_service =
  Eliom_service.App.service ~path:[] ~get_params:(string "user_num") ()

let new_message_action =
  Eliom_service.Http.post_coservice'
    ~post_params:(string "from_user_id" ** string "to_user_id" ** string "msg") ()

(* Set the scope used by all erefs *)
let eref_scope = Eliom_common.default_process_scope

(* Create a channel eref *)
let channel_ref =
  Eliom_reference.Volatile.eref_from_fun
    ~scope:eref_scope
    (fun () ->
      let (s, notify) = Lwt_stream.create () in
      let c = Eliom_comet.Channel.create s in
      (c, notify)
    )

(* Reactive string eref *)
let react_string_ref =
  Eliom_reference.Volatile.eref_from_fun
    ~scope:eref_scope
    (fun () ->
      let (client_string, send_client_string) :
          (string React.E.t * (?step:React.step -> string -> unit) ) =
        React.E.create ()
      in
      (client_string, send_client_string)
    )

(* Reactive string to display the users session group *)
let react_session_group_ref =
  Eliom_reference.Volatile.eref_from_fun
    ~scope:eref_scope
    (fun () ->
      let (session_group_string, send_session_group_string) :
          (string React.E.t * (?step:React.step -> string -> unit) ) =
        React.E.create ()
      in
      (session_group_string, send_session_group_string)
    )

(* Reactive string to display the users session group size *)
let react_session_group_size_ref =
  Eliom_reference.Volatile.eref_from_fun
    ~scope:eref_scope
    (fun () ->
      let (session_group_size_string, send_session_group_size_string) :
          (string React.E.t * (?step:React.step -> string -> unit) ) =
        React.E.create ()
      in
      (session_group_size_string, send_session_group_size_string)
    )

(* Send a message from one client to another *)
let notify from_user_id to_user_id s =
  (* Get the session group state for the user *)
  let state =
    Eliom_state.Ext.volatile_data_group_state     ~scope:Eliom_common.default_group_scope to_user_id in
    (* Iterate on all sessions from the group *)
    Eliom_state.Ext.iter_volatile_sub_states ~state
    (fun state ->
      (* Iterate on all client process states in the session *)
      Eliom_state.Ext.iter_volatile_sub_states ~state
      (fun state ->
        let (_, notify) = Eliom_reference.Volatile.Ext.get state channel_ref in
        notify (Some ("Hello from " ^ from_user_id ^ "! You are user " ^ to_user_id ^ "\n\n" ^ s))
      )
    )

(* Action for a client to send a message *)
let () =
  Eliom_registration.Action.register
    ~options:`NoReload
    ~service:new_message_action
    (fun () (from_user_id, (to_user_id, msg)) ->
      Lwt.return @@ notify from_user_id to_user_id msg
    )

(* Post form for one user to send a message to another user *)
let client_message_form =
  Eliom_content.Html5.F.post_form ~service:new_message_action ~port:8080
  (
    fun (from_user_id, (to_user_id, msg)) ->
      [p [pcdata "To:"];
       string_input ~input_type:`Text ~name:to_user_id ();
       p [pcdata "From:"];
       string_input ~input_type:`Text ~name:from_user_id ();
       p [pcdata "Send a message here:"];
       string_input ~input_type:`Text ~name:msg ();
       button ~button_type:`Submit [pcdata "Send Message"]
      ]
  )

let () =
  Channel_example_app.register
    ~service:main_service
    (fun user_num () ->
      (* Set the session group to which the erefs belong *)
      Eliom_state.set_volatile_data_session_group
        ~set_max:1
        ~scope:Eliom_common.default_session_scope
        ~secure:true
        user_num;
      let (channel, _) = Eliom_reference.Volatile.get channel_ref in
      let my_client_string, my_send_client_string = Eliom_reference.Volatile.get react_string_ref in
      let my_send_client_string' =
        server_function Json.t<string> (fun s -> Lwt.return @@ my_send_client_string s)
      in
      let c_down = Eliom_react.Down.of_react my_client_string in
      (* When a message is received on the channel, push it as a reactive event *)
      let _ =
        {unit{
          Lwt.async
            (fun () ->
              Lwt_stream.iter (fun (s : string) -> ignore @@ %my_send_client_string' s) %channel
            )
        }}
      in
      let my_session_group =
        match
          Eliom_state.get_volatile_data_session_group
            ~scope:Eliom_common.default_session_scope
            ~secure:true ()
        with
        | None -> "No session group"
        | Some sg -> sg
      in
      let my_session_group_size =
        match
          Eliom_state.get_volatile_data_session_group_size
            ~scope:Eliom_common.default_session_scope
            ~secure:true ()
        with
        | None -> "0"
        | Some gs -> string_of_int gs
      in
      Lwt.return
        (Eliom_tools.F.html
           ~title:"channel_example"
           ~css:[["css";"channel_example.css"]]
           Eliom_content.Html5.F.(body [
             h2 [pcdata ("Your are logged in as user " ^ user_num)];
             client_message_form ();
             p [pcdata "Your message is:"];
             C.node {{R.pcdata (React.S.hold "No message yet" %c_down)}};
             p [pcdata ("I am a part of the session group named " ^ my_session_group)];
             p [pcdata ("My session group size is " ^ my_session_group_size)]
           ])))

1 个答案:

答案 0 :(得分:1)

问题来自于使用循环遍历所有选项卡的notify函数。我使用了Eliom Base App的Hashtable / Weak Hashtable结构,它纠正了所有的通信问题。关键是改变通知功能如下:

let notify ?(notforme = false) ~id ~to_user ~msg =
  Lwt.async (fun () ->
    I.fold
      (fun (userid_o, ((_, _, send_e) as nn)) (beg : unit Lwt.t) ->
        if notforme && nn == Eliom_reference.Volatile.get notif_e
        then Lwt.return ()
        else
          lwt () = beg in
          let content = if Some to_user = userid_o then Some msg else None in
          match content with
          | Some content -> send_e (id, content); Lwt.return ()
          | None -> Lwt.return ()
      )
      id (Lwt.return ()))