/
Connection.php
190 lines (164 loc) · 6.08 KB
/
Connection.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<?php
declare(strict_types=1);
namespace Aranyasen\HL7;
use Aranyasen\Exceptions\HL7ConnectionException;
use Aranyasen\Exceptions\HL7Exception;
use Exception;
use Socket;
/**
* Usage:
* ```php
* $connection = new Connection('127.0.0.1', 5002);
* $req = new Message();
* // ... set some request attributes
* $response = $connection->send($req);
* $response->toString(); // Read ACK message from remote
* ```
*
* The Connection object represents the tcp connection to the HL7 message broker. The Connection has only one public
* method (apart from the constructor), send(). The 'send' method takes a Message object as argument, and also
* returns a Message object. The send method can be used more than once, before the connection is closed.
* Connection is closed automatically when the connection object is destroyed.
*
* The Connection object holds the following fields:
*
* MESSAGE_PREFIX
*
* The prefix to be sent to the HL7 server to initiate the
* message. Defaults to \013.
*
* MESSAGE_SUFFIX
* End of message signal for HL7 server. Defaults to \034\015.
*
*/
class Connection
{
protected Socket $socket;
protected int $timeout;
/** # Octal 13 (Hex: 0B): Vertical Tab */
protected string $MESSAGE_PREFIX = "\013";
/** # 34 (Hex: 1C): file separator character, 15 (Hex: 0D): Carriage return */
protected string $MESSAGE_SUFFIX = "\034\015";
/**
* Creates a connection to a HL7 server, or throws exception when a connection could not be established.
*
* @param string $host Host to connect to
* @param int $port Port to connect to
* @param int $timeout Connection timeout
* @throws HL7ConnectionException
*/
public function __construct(string $host, int $port, int $timeout = 10)
{
if (!extension_loaded('sockets')) {
throw new HL7ConnectionException('Please install ext-sockets to run Connection');
}
$this->setSocket($host, $port, $timeout);
$this->timeout = $timeout;
}
/**
* Create a client-side TCP socket
*
* @param int $timeout Connection timeout
* @throws HL7ConnectionException
*/
protected function setSocket(string $host, int $port, int $timeout = 10): void
{
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
$this->throwSocketError('Failed to create socket');
}
if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $timeout, 'usec' => 0])) {
$this->throwSocketError('Unable to set timeout on socket');
}
if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $timeout, 'usec' => 0])) {
$this->throwSocketError('Unable to set timeout on socket');
}
if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
$this->throwSocketError('Unable to set reuse-address on socket');
}
// Uncomment this if server requires a certain client-side port to be used
// if (!socket_bind($socket, "0.0.0.0", $localPort)) {
// $this->throwSocketError('Unable to bind socket');
// }
$result = null;
try {
$result = socket_connect($socket, $host, $port);
} catch (Exception) {
$this->throwSocketError("Failed to connect to server ($host:$port)");
}
if (!$result) {
$this->throwSocketError("Failed to connect to server ($host:$port)");
}
$this->socket = $socket;
}
/**
* @throws HL7ConnectionException
*/
protected function throwSocketError(string $message): void
{
throw new HL7ConnectionException($message . ': ' . socket_strerror(socket_last_error()));
}
/**
* Sends a Message object over this connection.
*
* @param string $responseCharEncoding The expected character encoding of the response.
* @param bool $noWait Do no wait for ACK. Helpful for building load testing tools...
* @throws HL7ConnectionException
* @throws HL7Exception
*/
public function send(Message $msg, string $responseCharEncoding = 'UTF-8', bool $noWait = false): ?Message
{
$message = $this->MESSAGE_PREFIX . $msg->toString(true) . $this->MESSAGE_SUFFIX; // As per MLLP protocol
if (!socket_write($this->socket, $message, strlen($message))) {
throw new HL7Exception("Could not send data to server: " . socket_strerror(socket_last_error()));
}
if ($noWait) {
return null;
}
$data = null;
$startTime = time();
while (($buf = socket_read($this->socket, 1024)) !== false) { // Read ACK / NACK from server
$data .= $buf;
if (preg_match('/' . $this->MESSAGE_SUFFIX . '$/', $data)) {
break;
}
if ((time() - $startTime) > $this->timeout) {
throw new HL7ConnectionException(
"Response partially received. Timed out listening for end-of-message from server"
);
}
}
if (empty($data)) {
throw new HL7ConnectionException("No response received within {$this->timeout} seconds");
}
// Remove message prefix and suffix added by the MLLP server
$data = preg_replace('/^' . $this->MESSAGE_PREFIX . '/', '', $data);
$data = preg_replace('/' . $this->MESSAGE_SUFFIX . '$/', '', $data);
// set character encoding
$data = mb_convert_encoding($data, $responseCharEncoding);
return new Message($data, null, true, true);
}
/*
* Return the raw socket opened/used by this class
*/
public function getSocket(): Socket
{
return $this->socket;
}
/**
* Close the socket
* TODO: Close only when the socket is open
*/
private function close(): void
{
try {
socket_close($this->socket);
} catch (Exception $e) {
echo 'Failed to close socket: ' . socket_strerror(socket_last_error()) . PHP_EOL;
}
}
public function __destruct()
{
$this->close();
}
}