MLLP.Client (mllp v0.9.4)

MLLP.Client provides a simple tcp client for sending and receiving data via MLLP over TCP.

While MLLP is primarily used to send HL7 messages, MLLP.Client can be used to send non-hl7 messages, such as XML.

Connection Behaviour

Upon successful start up via start_link/4, the client will attempt to establish a connection to the given address on the provided port. If a connection can not be immediately established, the client will keep trying to establish a connection per the value of :auto_reconnect_interval which defaults to 1 second. Therefore it is possible that before a connection is fully established, the caller may attempt to send a message which will result in MLLP.Client.Error.t() being returned containing the last error encountered in trying to establish a connection. Additionally, said behavour could be encountered at any point during life span of an MLLP.Client process if the connection becomes severed on either side.

All connections, send, and receive failures will be logged as errors.

Examples

Sending messages as strings

iex> MLLP.Receiver.start(dispatcher: MLLP.EchoDispatcher, port: 4090)
{:ok,
%{
  pid: #PID<0.2167.0>,
  port: 4090,
  receiver_id: #Reference<0.3312799297.2467299337.218126>
}}
iex> {:ok, client} = MLLP.Client.start_link("127.0.0.1", 4090)
{:ok, #PID<0.369.0>}
iex> msg = "MSH|^~\&|MegaReg|XYZHospC|SuperOE|XYZImgCtr|20060529090131-0500|..."
"MSH|^~\&|MegaReg|XYZHospC|SuperOE|XYZImgCtr|20060529090131-0500|..."
iex> MLLP.Client.send(client, msg)
{:ok, "MSH|^~\&|SuperOE|XYZImgCtr|MegaReg|XYZHospC|20060529090131-0500||ACK^A01^ACK|..."}
iex>

Sending messages with HL7.Message.t()

iex> MLLP.Receiver.start(dispatcher: MLLP.EchoDispatcher, port: 4090)
{:ok,
%{
  pid: #PID<0.2167.0>,
  port: 4090,
  receiver_id: #Reference<0.3312799297.2467299337.218126>
}}
iex> {:ok, client} = MLLP.Client.start_link("127.0.0.1", 4090)
{:ok, #PID<0.369.0>}
iex> msg = HL7.Message.new(HL7.Examples.wikipedia_sample_hl7())
iex> MLLP.Client.send(client, msg)
{:ok, :application_accept,
    %MLLP.Ack{
    acknowledgement_code: "AA",
    hl7_ack_message: nil,
    text_message: "A real MLLP message dispatcher was not provided"
}}

Using TLS

iex> tls_opts = [
  cacertfile: "/path/to/ca_certificate.pem",
  verify: :verify_peer,
  certfile: "/path/to/server_certificate.pem",
  keyfile: "/path/to/private_key.pem"
]
iex> MLLP.Receiver.start(dispatcher: MLLP.EchoDispatcher, port: 4090, tls: tls_opts)
iex> {:ok, client} = MLLP.Client.start_link("localhost", 8154, tls: [verify: :verify_peer, cacertfile: "path/to/ca_certfile.pem"])
iex> msg = HL7.Message.new(HL7.Examples.wikipedia_sample_hl7())
iex> MLLP.Client.send(client, msg)
{:ok, :application_accept,
    %MLLP.Ack{
    acknowledgement_code: "AA",
    hl7_ack_message: nil,
    text_message: "A real MLLP message dispatcher was not provided"
}}

Link to this section Summary

Functions

Returns true if the connection is open and established, otherwise false.

Instructs the client to disconnect (if connected) and attempt a reconnect.

Sends a message and receives a response.

Sends a message without awaiting a response.

Starts a new MLLP.Client.

Stops an MLLP.Client given a MLLP.Client pid.

Link to this section Types

Link to this type

ip_address()

Specs

ip_address() :: :inet.socket_address() | String.t()

Specs

pid_ref() :: atom() | pid() | {atom(), any()} | {:via, atom(), any()}

Specs

t() :: %MLLP.Client{
  address: ip_address(),
  auto_reconnect_interval: non_neg_integer(),
  backoff: any(),
  caller: pid() | nil,
  close_on_recv_error: boolean(),
  context: atom(),
  host_string: term(),
  last_byte_received: byte(),
  pid: pid() | nil,
  port: char(),
  receive_buffer: iolist(),
  send_opts: term(),
  socket: any(),
  socket_address: String.t(),
  socket_opts: Keyword.t(),
  tcp: module() | nil,
  tcp_error: term(),
  telemetry_module: module() | nil,
  tls_opts: Keyword.t()
}

Link to this section Functions

Link to this function

connected(event, msg, data)

Link to this function

default_socket_opts()

Link to this function

disconnected(event, current_state, data)

Link to this function

fixed_socket_opts()

Link to this function

is_connected?(pid)

Specs

is_connected?(pid :: pid()) :: boolean()

Returns true if the connection is open and established, otherwise false.

Link to this function

receive_impl(reply, data)

Link to this function

receiving(event_kind, request, data)

Specs

reconnect(pid :: pid()) :: :ok

Instructs the client to disconnect (if connected) and attempt a reconnect.

Link to this function

send(pid, payload, options \\ %{}, timeout \\ :infinity)

Specs

send(
  pid :: pid(),
  payload :: HL7.Message.t() | String.t() | binary(),
  options :: MLLP.ClientContract.send_options(),
  timeout :: non_neg_integer() | :infinity
) ::
  {:ok, String.t()}
  | MLLP.Ack.ack_verification_result()
  | {:error, MLLP.ClientContract.client_error()}

Sends a message and receives a response.

send/4 supports both HL7.Message and String.t().

All messages and responses will be wrapped and unwrapped via MLLP.Envelope.wrap_message/1 and MLLP.Envelope.unwrap_message/1 respectively

In case the payload provided is an HL7.Message.t() the acknowledgment returned from the server will always be verified via MLLP.Ack.verify_ack_against_message/2. This is the only case where an MLLP.Ack.ack_verification_result() will be returned.

Options

  • :reply_timeout - Optionally specify a timeout value for receiving a response. Must be a positive integer or :infinity. Defaults to 60 seconds.
Link to this function

send_async(pid, payload, timeout \\ :infinity)

Sends a message without awaiting a response.

Given the synchronous nature of MLLP/HL7 this function is mainly useful for testing purposes.

Link to this function

start_link(address, port, options \\ [])

Specs

start_link(
  address :: ip_address(),
  port :: :inet.port_number(),
  options :: MLLP.ClientContract.options()
) :: {:ok, pid()}

Starts a new MLLP.Client.

MLLP.Client.start_link/4 will start a new MLLP.Client process.

This function will raise a ArgumentError if an invalid ip_address() is provided.

Options

  • :use_backoff - Specify if an exponential backoff should be used for connection. When an attempt to establish a connection fails, either post-init or at some point during the life span of the client, the backoff value will determine how often to retry a reconnection. Starts at 1 second and increases exponentially until reaching backoff_max_seconds seconds. Defaults to true.

  • :backoff_max_seconds - Specify the max limit of seconds the backoff reconection attempt should take, defauls to 180 (3 mins).

  • :auto_reconnect_interval - Specify the interval between connection attempts. Specifically, if an attempt to establish a connection fails, either post-init or at some point during the life span of the client, the value of this option shall determine how often to retry a reconnection. Defaults to 1000 milliseconds. This option will only be used if use_backoff is set to false.

  • :reply_timeout - Optionally specify a timeout value for receiving a response. Must be a positive integer or :infinity. Defaults to 60 seconds.

  • :socket_opts - A list of socket options as supported by :gen_tcp. Note that :binary, :packet, and :active can not be overridden. Default options are enumerated below.

    • send_timeout: Defaults to 60 seconds
    • send_timeout_close: Defaults to true
  • :close_on_recv_error - A boolean value which dictates whether the client socket will be closed when an error in receiving a reply is encountered, this includes timeouts. Setting this to true is usually the safest behaviour to avoid a "dead lock" situation between a client and a server. This functions similarly to the :send_timeout option provided by :gen_tcp. Defaults to true.

  • :tls - A list of tls options as supported by :ssl. When using TLS it is highly recommended you set :verify to :verify_peer, select a CA trust store using the :cacertfile or :cacerts options. Additionally, further hardening can be achieved through other ssl options such as enabling certificate revocation via the :crl_check and :crl_cache options and customization of enabled protocols and cipher suites for your specific use-case. See :ssl for details.

Specs

stop(pid :: pid()) :: :ok

Stops an MLLP.Client given a MLLP.Client pid.

This function will always return :ok per :gen_statem.stop/1, thus you may give it a pid that references a client which is already stopped.