PageRenderTime 64ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/http-svr/xmlrpc_client.ml

http://github.com/xen-org/xen-api-libs
OCaml | 319 lines | 255 code | 31 blank | 33 comment | 11 complexity | 6b91af23adf1a22ee73dac3bc1d466c3 MD5 | raw file
Possible License(s): 0BSD, LGPL-2.1
  1. (*
  2. * Copyright (C) Citrix Systems Inc.
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU Lesser General Public License as published
  6. * by the Free Software Foundation; version 2.1 only. with the special
  7. * exception on linking described in file LICENSE.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Lesser General Public License for more details.
  13. *)
  14. open Stringext
  15. open Pervasiveext
  16. open Threadext
  17. module D = Debug.Debugger(struct let name = "xmlrpc_client" end)
  18. open D
  19. module E = Debug.Debugger(struct let name = "mscgen" end)
  20. module Internal = struct
  21. let set_stunnelpid_callback : (string option -> int -> unit) option ref = ref None
  22. let unset_stunnelpid_callback : (string option -> int -> unit) option ref = ref None
  23. end
  24. let user_agent = "xen-api-libs/1.0"
  25. let connect ?session_id ?task_id ?subtask_of path =
  26. let arg str x = Opt.default [] (Opt.map (fun x -> [ str, x ]) x) in
  27. let cookie = arg "session_id" session_id @ (arg "task_id" task_id) @ (arg "subtask_of" subtask_of) in
  28. Http.Request.make ~user_agent ~version:"1.0" ~keep_alive:true ~cookie ?subtask_of
  29. Http.Connect path
  30. let xmlrpc ?frame ?version ?keep_alive ?task_id ?cookie ?length ?auth ?subtask_of ?query ?body path =
  31. let headers = Opt.map (fun x -> [ Http.Hdr.task_id, x ]) task_id in
  32. Http.Request.make ~user_agent ?frame ?version ?keep_alive ?cookie ?headers ?length ?auth ?subtask_of ?query ?body
  33. Http.Post path
  34. (** Thrown when ECONNRESET is caught which suggests the remote crashed or restarted *)
  35. exception Connection_reset
  36. module StunnelDebug=Debug.Debugger(struct let name="stunnel" end)
  37. let write_to_log x = StunnelDebug.debug "%s" (String.strip String.isspace x)
  38. (** Return true if this fd is connected to an HTTP server by sending an XMLRPC request
  39. for an unknown method and checking we get a matching MESSAGE_METHOD_UNKNOWN.
  40. This is used to prevent us accidentally trying to reuse a connection which has been
  41. closed or left in some other inconsistent state. *)
  42. let check_reusable (x: Unix.file_descr) =
  43. let msg_name = "system.isAlive" in
  44. let msg_uuid = Uuid.string_of_uuid (Uuid.make_uuid ()) in
  45. (* This is for backward compatability *)
  46. let msg_func = Printf.sprintf "%s:%s" msg_name msg_uuid in
  47. let msg_param = [ XMLRPC.To.string msg_uuid ] in
  48. let xml = Xml.to_string (XMLRPC.To.methodCall msg_func msg_param) in
  49. let http = xmlrpc ~version:"1.1" ~keep_alive:true ~body:xml "/" in
  50. try
  51. Http_client.rpc x http
  52. (fun response _ ->
  53. match response.Http.Response.content_length with
  54. | Some len ->
  55. let len = Int64.to_int len in
  56. let tmp = String.make len 'X' in
  57. let buf = Buf_io.of_fd x in
  58. Buf_io.really_input buf tmp 0 len;
  59. begin match XMLRPC.From.methodResponse (Xml.parse_string tmp) with
  60. | XMLRPC.Failure("MESSAGE_METHOD_UNKNOWN", [ param ])
  61. when param = msg_func ->
  62. (* This must be the server pre-dates system.isAlive *)
  63. true
  64. | XMLRPC.Success param when param = msg_param ->
  65. (* This must be the new server withs system.isAlive *)
  66. true
  67. | _ ->
  68. StunnelDebug.debug "check_reusable: unexpected response: connection not reusable: %s" tmp;
  69. false
  70. end
  71. | None ->
  72. StunnelDebug.debug "check_reusable: no content-length from known-invalid URI: connection not reusable";
  73. false
  74. )
  75. with exn ->
  76. StunnelDebug.debug "check_reusable: caught exception %s; assuming not reusable" (Printexc.to_string exn);
  77. false
  78. (** Thrown when repeated attempts to connect an stunnel to a remote host and check
  79. the connection works fail. *)
  80. exception Stunnel_connection_failed
  81. let get_new_stunnel_id =
  82. let counter = ref 0 in
  83. let m = Mutex.create () in
  84. fun () -> Mutex.execute m (fun () -> incr counter; !counter)
  85. (** Returns an stunnel, either from the persistent cache or a fresh one which
  86. has been checked out and guaranteed to work. *)
  87. let get_reusable_stunnel ?use_fork_exec_helper ?write_to_log host port ?verify_cert =
  88. let found = ref None in
  89. (* 1. First check if there is a suitable stunnel in the cache. *)
  90. let verify_cert = Stunnel.must_verify_cert verify_cert in
  91. begin
  92. try
  93. while !found = None do
  94. let (x: Stunnel.t) = Stunnel_cache.remove host port verify_cert in
  95. if check_reusable x.Stunnel.fd
  96. then found := Some x
  97. else begin
  98. StunnelDebug.debug "get_reusable_stunnel: Found non-reusable stunnel in the cache. disconnecting from %s:%d" host port;
  99. Stunnel.disconnect x
  100. end
  101. done
  102. with Not_found -> ()
  103. end;
  104. match !found with
  105. | Some x ->
  106. x
  107. | None ->
  108. StunnelDebug.debug "get_reusable_stunnel: stunnel cache is empty; creating a fresh connection to %s:%d" host port;
  109. (* 2. Create a fresh connection and make sure it works *)
  110. begin
  111. let max_attempts = 10 in
  112. let attempt_number = ref 0 in
  113. let delay = 10. in (* seconds *)
  114. while !found = None && (!attempt_number < max_attempts) do
  115. incr attempt_number;
  116. try
  117. let unique_id = get_new_stunnel_id () in
  118. let (x: Stunnel.t) = Stunnel.connect ~unique_id ?use_fork_exec_helper ?write_to_log ~verify_cert host port in
  119. if check_reusable x.Stunnel.fd
  120. then found := Some x
  121. else begin
  122. StunnelDebug.error "get_reusable_stunnel: fresh stunnel failed reusable check; delaying %.2f seconds before reconnecting to %s:%d (attempt %d / %d)" delay host port !attempt_number max_attempts;
  123. Thread.delay delay;
  124. Stunnel.disconnect x
  125. end
  126. with e ->
  127. StunnelDebug.error "get_reusable_stunnel: fresh stunnel connection failed with exception: %s: delaying %.2f seconds before reconnecting to %s:%d (attempt %d / %d)" (Printexc.to_string e) delay host port !attempt_number max_attempts;
  128. Thread.delay delay;
  129. done
  130. end;
  131. begin match !found with
  132. | Some x ->
  133. x
  134. | None ->
  135. StunnelDebug.error "get_reusable_stunnel: failed to acquire a working stunnel to connect to %s:%d" host port;
  136. raise Stunnel_connection_failed
  137. end
  138. module SSL = struct
  139. type t = {
  140. use_fork_exec_helper: bool;
  141. use_stunnel_cache: bool;
  142. verify_cert: bool option;
  143. task_id: string option
  144. }
  145. let make ?(use_fork_exec_helper=true) ?(use_stunnel_cache=false) ?(verify_cert) ?task_id () = {
  146. use_fork_exec_helper = use_fork_exec_helper;
  147. use_stunnel_cache = use_stunnel_cache;
  148. verify_cert = verify_cert;
  149. task_id = task_id
  150. }
  151. let to_string (x: t) =
  152. Printf.sprintf "{ use_fork_exec_helper = %b; use_stunnel_cache = %b; verify_cert = %s; task_id = %s }"
  153. x.use_fork_exec_helper x.use_stunnel_cache (Opt.default "None" (Opt.map (fun x -> string_of_bool x) x.verify_cert))
  154. (Opt.default "None" (Opt.map (fun x -> "Some " ^ x) x.task_id))
  155. end
  156. type transport =
  157. | Unix of string
  158. | TCP of string * int
  159. | SSL of SSL.t * string * int
  160. let string_of_transport = function
  161. | Unix x -> Printf.sprintf "Unix %s" x
  162. | TCP (host, port) -> Printf.sprintf "TCP %s:%d" host port
  163. | SSL (ssl, host, port) -> Printf.sprintf "SSL %s:%d %s" host port (SSL.to_string ssl)
  164. let transport_of_url (scheme, _) =
  165. let open Http.Url in
  166. match scheme with
  167. | File { path = path } -> Unix path
  168. | Http ({ ssl = false } as h) ->
  169. let port = Opt.default 80 h.port in
  170. TCP(h.host, port)
  171. | Http ({ ssl = true } as h) ->
  172. let port = Opt.default 443 h.port in
  173. SSL(SSL.make (), h.host, port)
  174. let with_transport transport f = match transport with
  175. | Unix path ->
  176. let fd = Unixext.open_connection_unix_fd path in
  177. finally
  178. (fun () -> f fd)
  179. (fun () -> Unix.close fd)
  180. | TCP (host, port) ->
  181. let fd = Unixext.open_connection_fd host port in
  182. finally
  183. (fun () ->
  184. Unixext.set_tcp_nodelay fd true;
  185. f fd)
  186. (fun () -> Unix.close fd)
  187. | SSL ({
  188. SSL.use_fork_exec_helper = use_fork_exec_helper;
  189. use_stunnel_cache = use_stunnel_cache;
  190. verify_cert = verify_cert;
  191. task_id = task_id}, host, port) ->
  192. let st_proc =
  193. if use_stunnel_cache
  194. then get_reusable_stunnel ~use_fork_exec_helper ~write_to_log host port ?verify_cert
  195. else
  196. let unique_id = get_new_stunnel_id () in
  197. Stunnel.connect ~use_fork_exec_helper ~write_to_log ~unique_id ?verify_cert ~extended_diagnosis:true host port in
  198. let s = st_proc.Stunnel.fd in
  199. let s_pid = Stunnel.getpid st_proc.Stunnel.pid in
  200. debug "stunnel pid: %d (cached = %b) connected to %s:%d" s_pid use_stunnel_cache host port;
  201. (* Call the {,un}set_stunnelpid_callback hooks around the remote call *)
  202. let with_recorded_stunnelpid task_opt s_pid f =
  203. debug "with_recorded_stunnelpid task_opt=%s s_pid=%d" (Opt.default "None" task_opt) s_pid;
  204. begin
  205. match !Internal.set_stunnelpid_callback with
  206. | Some f -> f task_id s_pid
  207. | _ -> ()
  208. end;
  209. finally f
  210. (fun () ->
  211. match !Internal.unset_stunnelpid_callback with
  212. | Some f -> f task_id s_pid
  213. | _ -> ()
  214. ) in
  215. with_recorded_stunnelpid task_id s_pid
  216. (fun () ->
  217. finally
  218. (fun () ->
  219. try
  220. f s
  221. with e ->
  222. warn "stunnel pid: %d caught %s" s_pid (Printexc.to_string e);
  223. if e = Connection_reset && not use_stunnel_cache
  224. then Stunnel.diagnose_failure st_proc;
  225. raise e)
  226. (fun () ->
  227. if use_stunnel_cache
  228. then begin
  229. Stunnel_cache.add st_proc;
  230. debug "stunnel pid: %d (cached = %b) returned stunnel to cache" s_pid use_stunnel_cache;
  231. end else
  232. begin
  233. Unix.unlink st_proc.Stunnel.logfile;
  234. Stunnel.disconnect st_proc
  235. end
  236. )
  237. )
  238. let with_http request f s =
  239. try
  240. Http_client.rpc s request (fun response s -> f (response, s))
  241. with Unix.Unix_error(Unix.ECONNRESET, _, _) -> raise Connection_reset
  242. let curry2 f (a, b) = f a b
  243. module type FORMAT = sig
  244. type response
  245. val response_of_string: string -> response
  246. val response_of_file_descr: Unix.file_descr -> response
  247. type request
  248. val request_to_string: request -> string
  249. val request_to_short_string: request -> string
  250. end
  251. module XML = struct
  252. type response = Xml.xml
  253. let response_of_string = Xml.parse_string
  254. let response_of_file_descr fd = Xml.parse_in (Unix.in_channel_of_descr fd)
  255. type request = Xml.xml
  256. let request_to_string = Xml.to_string
  257. let request_to_short_string = fun _ -> "(XML)"
  258. end
  259. module XMLRPC = struct
  260. type response = Rpc.response
  261. let response_of_string x = Xmlrpc.response_of_string x
  262. let response_of_file_descr fd = Xmlrpc.response_of_in_channel (Unix.in_channel_of_descr fd)
  263. type request = Rpc.call
  264. let request_to_string x = Xmlrpc.string_of_call x
  265. let request_to_short_string x = x.Rpc.name
  266. end
  267. module Protocol = functor(F: FORMAT) -> struct
  268. (** Take an optional content_length and task_id together with a socket
  269. and return the XMLRPC response as an XML document *)
  270. let read_response r s =
  271. try
  272. match r.Http.Response.content_length with
  273. | Some l when (Int64.to_int l) <= Sys.max_string_length ->
  274. F.response_of_string (Unixext.really_read_string s (Int64.to_int l))
  275. | Some _ | None -> F.response_of_file_descr s
  276. with
  277. | Unix.Unix_error(Unix.ECONNRESET, _, _) -> raise Connection_reset
  278. let rpc ?(srcstr="unset") ?(dststr="unset") ~transport ~http req =
  279. (* Caution: req can contain sensitive information such as passwords in its parameters,
  280. * so we should not log the parameters or a string representation of the whole thing.
  281. * The name should be safe though, e.g. req.Rpc.name when F is XMLRPC. *)
  282. E.debug "%s=>%s [label=\"%s\"];" srcstr dststr (F.request_to_short_string req) ;
  283. let body = F.request_to_string req in
  284. let http = { http with Http.Request.body = Some body } in
  285. with_transport transport (with_http http (curry2 read_response))
  286. end
  287. module XML_protocol = Protocol(XML)
  288. module XMLRPC_protocol = Protocol(XMLRPC)