具有OCaml编组数据的通信客户端 - 服务器

时间:2014-07-31 09:41:02

标签: ocaml marshalling js-of-ocaml

我想在OCaml中使用服务器进行客户端js_of_ocaml应用程序,下面描述了约束,我想知道下面的方法是否正确,或者是否有更高效的方法。服务器有时可以发送大量数据(> 30MB)。

为了使客户端和服务器之间的通信更安全,更高效,我在.mli文件中共享类型t,如下所示:

type client_to_server =
| Say_Hello
| Do_something_with of int

type server_to_client =
| Ack
| Print of string * int

然后,这种类型被编组成一个字符串并在网络上发送。我知道在客户端,缺少某些类型(Int64.t)。

此外,在客户端发送的XMLHTTPRequest中,我们希望从服务器接收多个编组对象,有时以流模式接收(即:在{{1}期间处理收到的编组对象(如果可能)请求的状态,而不仅仅是loading状态)。

这些约束迫使我们使用XMLHTTPRequest的字段done和内容类型responseText

此外,当我们从application/octet-stream返回响应时,会进行编码转换,因为JavaScript的字符串是UTF-16。但是编组的对象是二进制数据,我们执行必要的操作以便检索我们的二进制数据(通过使用responseText覆盖charset并在x-user-defined字符串的每个字符上应用掩码。)< / p>

服务器(OCaml中的HTTP服务器)正在做这样简单的事情:

responseText

但是,在客户端,let process_request req = let res = process_response req in let s = Marshal.to_string res [] in send s 的js_of_ocaml的实际JavaScript原语需要一个MlString。但是在流媒体模式下,我们不想在MlString中转换javascript的字符串(可以在完整的字符串上转换),我们更喜欢进行大小验证和解组(以及掩码的应用)对于编码问题)仅对读取的字节数。因此,我在javascript中编写了自己的marshal原语。

处理请求和响应的客户端代码是:

caml_marshal_data_size

基元是:

external marshal_total_size : Js.js_string Js.t -> int -> int = "my_marshal_total_size"
external marshal_from_string : Js.js_string Js.t -> int -> 'a = "my_marshal_from_string"

let apply (f:server_to_client -> unit) (str:Js.js_string Js.t) (ofs:int) : int =
  let len = str##length in
  let rec aux pos =
    let tsize = 
      try Some (pos + My_primitives.marshal_total_size str pos)
      with Failure _ -> None
    in
    match tsize with
    | Some tsize when tsize <= len ->
      let data = My_primitives.marshal_from_string str pos in
      f data;
      aux tsize
    | _ -> pos
  in
  aux ofs

let reqcallback f req ofs =
  match req##readyState, req##status with
  | XmlHttpRequest.DONE, 200 ->
      ofs := apply f req##responseText !ofs

  | XmlHttpRequest.LOADING, 200 ->
      ignore (apply f req##responseText !ofs)

  | _, 200 -> ()

  | _, i -> process_error i

let send (f:server_to_client -> unit) (order:client_to_server) =
  let order = Marshal.to_string order [] in
  let msg = Js.string (my_encode order) in (* Do some stuff *)
  let req = XmlHttpRequest.create () in
  req##_open(Js.string "POST", Js.string "/kernel", Js._true);
  req##setRequestHeader(Js.string "Content-Type",
            Js.string "application/octet-stream");
  req##onreadystatechange <- Js.wrap_callback (reqcallback f req (ref 0));
  req##overrideMimeType(Js.string "application/octet-stream; charset=x-user-defined");
  req##send(Js.some msg)

这是将大型OCaml值从服务器传输到客户端的最有效方式,还是节省时间和空间的替代方案?

1 个答案:

答案 0 :(得分:3)

您是否尝试使用EventSource https://developer.mozilla.org/en-US/docs/Web/API/EventSource

您可以流式传输json数据而不是封送数据。 Json.unsafe_input应该比unmarshal更快。

class type eventSource =
 object
  method onmessage :
    (eventSource Js.t, event Js.t -> unit) Js.meth_callback
    Js.writeonly_prop
 end
and event =
 object
  method data : Js.js_string Js.t Js.readonly_prop
  method event : Js.js_string Js.t Js.readonly_prop
 end

let eventSource : (Js.js_string Js.t -> eventSource Js.t) Js.constr = 
   Js.Unsafe.global##_EventSource

let send (f:server_to_client -> unit) (order:client_to_server) url_of_order =
 let url = url_of_order order in
 let es = jsnew eventSource(Js.string url) in
 es##onmessage <- Js.wrap_callback (fun e ->
  let d = Json.unsafe_input (e##data) in
  f d);
 ()

在服务器端,您需要依赖deriving_json http://ocsigen.org/js_of_ocaml/2.3/api/Deriving_Json来序列化您的数据

type server_to_client =
 | Ack
 | Print of string * int 
deriving (Json)

let process_request req =
  let res = process_response req in
  let data = Json_server_to_client.to_string res in
  send data

note1:Deriving_json使用js_of_ocaml中值的内部表示将ocaml值序列化为json。 Json.unsafe_inputDeriving_json的快速反序列化程序,它依赖于浏览器本机JSON支持。

note2:Deriving_jsonJson.unsafe_input负责ocaml字符串编码