1 | |
package ca.uhn.hl7v2.hoh.encoder; |
2 | |
|
3 | |
import java.io.ByteArrayOutputStream; |
4 | |
import java.io.IOException; |
5 | |
import java.io.InputStream; |
6 | |
import java.net.SocketException; |
7 | |
import java.net.SocketTimeoutException; |
8 | |
import java.nio.charset.Charset; |
9 | |
import java.nio.charset.UnsupportedCharsetException; |
10 | |
import java.util.ArrayList; |
11 | |
import java.util.LinkedHashMap; |
12 | |
import java.util.List; |
13 | |
import java.util.Map; |
14 | |
import java.util.regex.Pattern; |
15 | |
|
16 | |
import ca.uhn.hl7v2.hoh.api.DecodeException; |
17 | |
import ca.uhn.hl7v2.hoh.api.NonHl7ResponseException; |
18 | |
import ca.uhn.hl7v2.hoh.sign.SignatureFailureException; |
19 | |
import ca.uhn.hl7v2.hoh.sign.SignatureVerificationException; |
20 | |
import ca.uhn.hl7v2.hoh.util.ByteUtils; |
21 | |
import ca.uhn.hl7v2.hoh.util.GZipUtils; |
22 | |
import ca.uhn.hl7v2.hoh.util.IOUtils; |
23 | |
import ca.uhn.hl7v2.hoh.util.StringUtils; |
24 | |
import ca.uhn.hl7v2.hoh.util.repackage.Base64; |
25 | |
|
26 | 9500 | public abstract class AbstractHl7OverHttpDecoder extends AbstractHl7OverHttp { |
27 | |
|
28 | 5 | private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); |
29 | |
|
30 | |
|
31 | |
|
32 | |
|
33 | |
|
34 | |
|
35 | |
|
36 | |
public static final int DEFAULT_READ_TIMEOUT = 30 * 1000; |
37 | |
|
38 | 5 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractHl7OverHttpDecoder.class); |
39 | |
|
40 | |
private byte[] myBytes; |
41 | |
private List<String> myConformanceProblems; |
42 | 510 | private int myContentLength = -1; |
43 | |
private String myContentType; |
44 | |
private boolean myGzipCoding; |
45 | |
private long myLastStartedReading; |
46 | 510 | private long myReadTimeout = DEFAULT_READ_TIMEOUT; |
47 | |
private String myResponseName; |
48 | |
private Integer myResponseStatus; |
49 | |
private TransferEncoding myTransferEncoding; |
50 | |
private String mySignature; |
51 | |
private EncodingStyle myEncodingStyle; |
52 | |
|
53 | |
private boolean myConnectionCloseHeaderIsPresent; |
54 | |
|
55 | |
private void addConformanceProblem(String theString) { |
56 | 0 | ourLog.debug("Conformance problem detected: {}", theString); |
57 | 0 | if (myConformanceProblems == null) { |
58 | 0 | myConformanceProblems = new ArrayList<String>(); |
59 | |
} |
60 | 0 | myConformanceProblems.add(theString); |
61 | 0 | } |
62 | |
|
63 | |
protected abstract void authorize() throws AuthorizationFailureException; |
64 | |
|
65 | |
public void decode() throws DecodeException, SignatureVerificationException { |
66 | 0 | ourLog.trace("Entering decode()"); |
67 | |
|
68 | 0 | verifyNotUsed(); |
69 | |
|
70 | 0 | decodeHeaders(); |
71 | 0 | authorize(); |
72 | 0 | decodeBody(); |
73 | 0 | verifySignature(); |
74 | |
|
75 | 0 | ourLog.trace("Exiting decode()"); |
76 | 0 | } |
77 | |
|
78 | |
private void decodeBody() throws DecodeException { |
79 | 415 | byte[] bytes = myBytes; |
80 | |
|
81 | 415 | if (myGzipCoding) { |
82 | 10 | ourLog.debug("Decoding message contents using GZIP encoding style"); |
83 | |
try { |
84 | 10 | bytes = GZipUtils.uncompress(bytes); |
85 | 0 | } catch (IOException e) { |
86 | 0 | throw new DecodeException("Failed to uncompress GZip content", e); |
87 | 10 | } |
88 | |
} |
89 | |
|
90 | 415 | Charset charset = getCharset(); |
91 | |
|
92 | 415 | ourLog.debug("Message is {} bytes with charset {}", bytes.length, charset.name()); |
93 | 415 | if (ourLog.isTraceEnabled()) { |
94 | 20 | ourLog.trace("Raw message: {}", StringUtils.asciiEscape(bytes, charset)); |
95 | |
} |
96 | |
|
97 | 415 | String messageString = new String(bytes, charset); |
98 | 415 | setMessage(messageString); |
99 | 415 | } |
100 | |
|
101 | |
private void decodeHeaders() throws DecodeException { |
102 | |
|
103 | 415 | ourLog.trace("Header map contains: {}", getHeaders()); |
104 | |
|
105 | 415 | for (Map.Entry<String, String> nextEntry : getHeaders().entrySet()) { |
106 | 2285 | String nextHeader = nextEntry.getKey().toLowerCase(); |
107 | 2285 | String nextValue = nextEntry.getValue(); |
108 | |
|
109 | 2285 | ourLog.trace("Next header: {}={}", nextHeader, nextValue); |
110 | |
|
111 | 2285 | if ("transfer-encoding".equals(nextHeader)) { |
112 | 30 | if ("chunked".equalsIgnoreCase(nextValue)) { |
113 | 30 | myTransferEncoding = TransferEncoding.CHUNKED; |
114 | 30 | ourLog.trace("Found chunked transfer encoding"); |
115 | |
} else { |
116 | 0 | throw new DecodeException("Unknown transfer encoding: " + nextValue); |
117 | |
} |
118 | 2255 | } else if ("connection".equals(nextHeader)) { |
119 | 95 | if ("close".equals(nextValue)) { |
120 | 30 | myConnectionCloseHeaderIsPresent = true; |
121 | |
} |
122 | 2160 | } else if ("content-length".equals(nextHeader)) { |
123 | |
try { |
124 | 385 | myContentLength = Integer.parseInt(nextValue); |
125 | 385 | ourLog.trace("Found content length: {}", myContentLength); |
126 | 0 | } catch (NumberFormatException e) { |
127 | 0 | addConformanceProblem("Could not parse Content-Length header value: " + nextHeader); |
128 | 385 | } |
129 | 1775 | } else if ("content-type".equals(nextHeader)) { |
130 | 415 | int colonIndex = nextValue.indexOf(';'); |
131 | 415 | if (colonIndex == -1) { |
132 | 5 | myContentType = nextValue.trim(); |
133 | |
} else { |
134 | 410 | myContentType = nextValue.substring(0, colonIndex).trim(); |
135 | 410 | String charsetDef = nextValue.substring(colonIndex + 1).trim(); |
136 | 410 | if (charsetDef.startsWith("charset=")) { |
137 | 410 | String charsetName = charsetDef.substring(8); |
138 | |
Charset charset; |
139 | |
try { |
140 | 410 | charset = Charset.forName(charsetName); |
141 | 0 | } catch (UnsupportedCharsetException e) { |
142 | 0 | addConformanceProblem("Unsupported or invalid charset: " + charsetName); |
143 | 0 | continue; |
144 | 410 | } |
145 | 410 | setCharset(charset); |
146 | |
} |
147 | |
} |
148 | |
|
149 | 415 | myEncodingStyle = EncodingStyle.getEncodingStyleForContentType(myContentType); |
150 | 415 | ourLog.trace("Found content type {} with resolves to encoding style {}", myContentType, myEncodingStyle); |
151 | |
|
152 | 415 | } else if ("authorization".equals(nextHeader)) { |
153 | 250 | int spaceIndex = nextValue.indexOf(' '); |
154 | 250 | if (spaceIndex == -1) { |
155 | 0 | throw new DecodeException("Invalid authorization header. No authorization style detected"); |
156 | |
} |
157 | 250 | String type = nextValue.substring(0, spaceIndex); |
158 | 250 | if ("basic".equalsIgnoreCase(type)) { |
159 | 250 | String encodedCredentials = nextValue.substring(spaceIndex + 1); |
160 | 250 | byte[] decodedCredentials = Base64.decodeBase64(encodedCredentials); |
161 | 250 | String credentialsString = new String(decodedCredentials, getDefaultCharset()); |
162 | 250 | int colonIndex = credentialsString.indexOf(':'); |
163 | 250 | if (colonIndex == -1) { |
164 | 0 | setUsername(credentialsString); |
165 | |
} else { |
166 | 250 | setUsername(credentialsString.substring(0, colonIndex)); |
167 | 250 | setPassword(credentialsString.substring(colonIndex + 1)); |
168 | |
} |
169 | |
|
170 | 250 | ourLog.trace("Found authorization header with username: {}", getUsername()); |
171 | |
|
172 | 250 | } else { |
173 | 0 | addConformanceProblem("Invalid authorization type. Only basic authorization is supported."); |
174 | |
} |
175 | |
|
176 | 250 | } else if ("content-encoding".equals(nextHeader)) { |
177 | 10 | if (StringUtils.isNotBlank(nextValue)) { |
178 | 10 | if ("gzip".equals(nextValue)) { |
179 | 10 | myGzipCoding = true; |
180 | |
} else { |
181 | 0 | throw new DecodeException("Unknown Content-Encoding: " + nextValue); |
182 | |
} |
183 | |
} |
184 | 10 | ourLog.trace("Found content coding: {}", nextValue); |
185 | 1100 | } else if (HTTP_HEADER_HL7_SIGNATURE_LC.equals(nextHeader)) { |
186 | 5 | ourLog.trace("Found signature: {}", nextValue); |
187 | 5 | mySignature = nextValue; |
188 | |
} else { |
189 | 1095 | ourLog.trace("Ignoring header {}={}", nextHeader, nextValue); |
190 | |
} |
191 | |
|
192 | 2285 | } |
193 | |
|
194 | 415 | ourLog.trace("Done processing headers"); |
195 | |
|
196 | 415 | } |
197 | |
|
198 | |
|
199 | |
|
200 | |
|
201 | |
protected boolean isConnectionCloseHeaderPresent() { |
202 | 110 | return myConnectionCloseHeaderIsPresent; |
203 | |
} |
204 | |
|
205 | |
|
206 | |
|
207 | |
|
208 | |
|
209 | |
|
210 | |
|
211 | |
|
212 | |
|
213 | |
|
214 | |
public EncodingStyle getEncodingStyle() { |
215 | 540 | return myEncodingStyle; |
216 | |
} |
217 | |
|
218 | |
private void doReadContentsFromInputStreamAndDecode(InputStream theInputStream) throws DecodeException, AuthorizationFailureException, IOException, SignatureVerificationException { |
219 | 415 | decodeHeaders(); |
220 | 415 | authorize(); |
221 | 415 | if (myTransferEncoding == TransferEncoding.CHUNKED) { |
222 | 30 | myBytes = readBytesChunked(theInputStream); |
223 | |
} else { |
224 | 385 | myBytes = readBytesNonChunked(theInputStream); |
225 | |
} |
226 | |
|
227 | 415 | decodeBody(); |
228 | |
|
229 | 415 | if (getContentType() == null) { |
230 | 0 | throw new DecodeException("Content-Type not specified"); |
231 | |
} |
232 | 415 | if (getEncodingStyle() == null) { |
233 | 0 | throw new NonHl7ResponseException("Invalid Content-Type: " + getContentType(), getContentType(), getMessage()); |
234 | |
} |
235 | |
|
236 | 415 | verifySignature(); |
237 | 415 | } |
238 | |
|
239 | |
private byte[] readBytesChunked(InputStream theInputStream) throws DecodeException, IOException { |
240 | 30 | ourLog.debug("Decoding message bytes using CHUNKED encoding style"); |
241 | 30 | byte[] byteBuffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; |
242 | 30 | ByteArrayOutputStream bos = new ByteArrayOutputStream(IOUtils.DEFAULT_BUFFER_SIZE); |
243 | |
|
244 | |
while (true) { |
245 | |
String nextSize; |
246 | |
try { |
247 | 70 | nextSize = readLine(theInputStream); |
248 | 0 | } catch (IOException e) { |
249 | 0 | throw new DecodeException("Failed to decode CHUNKED encoding", e); |
250 | 70 | } |
251 | |
|
252 | 70 | ourLog.trace("Going to interpret CHUNKED size value: {}", nextSize); |
253 | |
|
254 | 70 | if (nextSize.length() == 0) { |
255 | 0 | break; |
256 | |
} |
257 | |
|
258 | |
int nextSizeInt; |
259 | |
try { |
260 | 70 | nextSizeInt = Integer.parseInt(nextSize, 16); |
261 | 0 | } catch (NumberFormatException e) { |
262 | 0 | throw new DecodeException("Failed to decode CHUNKED encoding", e); |
263 | 70 | } |
264 | |
|
265 | 70 | ourLog.debug("Next CHUNKED size: {}", nextSizeInt); |
266 | |
|
267 | 70 | if (nextSizeInt < 0) { |
268 | 0 | throw new DecodeException("Received invalid octet count in chunked transfer encoding: " + nextSize); |
269 | |
} |
270 | |
|
271 | 70 | boolean trailing = false; |
272 | 70 | if (nextSizeInt > 0) { |
273 | 40 | int totalRead = 0; |
274 | 40 | myLastStartedReading = System.currentTimeMillis(); |
275 | |
do { |
276 | 40 | int nextRead = Math.min(nextSizeInt, byteBuffer.length); |
277 | 40 | int bytesRead = theInputStream.read(byteBuffer, 0, nextRead); |
278 | 40 | if (bytesRead == -1) { |
279 | 0 | ourLog.debug("Exception in readBytesChunked(InputStream): Reached EOF. Buffer has {} bytes", bos.size()); |
280 | 0 | throw new DecodeException("Reached EOF while reading in message chunk"); |
281 | |
} |
282 | 40 | if (bytesRead == 0 && totalRead < nextSizeInt) { |
283 | 0 | pauseDuringTimedOutRead(); |
284 | |
} |
285 | 40 | totalRead += bytesRead; |
286 | |
|
287 | 40 | if (ourLog.isTraceEnabled()) { |
288 | 0 | ourLog.trace("Read {} byte chunk: {}", bytesRead, new String(byteBuffer, 0, bytesRead)); |
289 | |
}else { |
290 | 40 | ourLog.debug("Read {} byte chunk", bytesRead); |
291 | |
} |
292 | |
|
293 | 40 | bos.write(byteBuffer, 0, bytesRead); |
294 | |
|
295 | 40 | } while (totalRead < nextSizeInt); |
296 | 40 | } else { |
297 | 30 | trailing = true; |
298 | |
} |
299 | |
|
300 | |
|
301 | |
int nextChar; |
302 | 70 | boolean had13 = false; |
303 | 70 | boolean had10 = false; |
304 | |
while (true) { |
305 | |
try { |
306 | 140 | nextChar = theInputStream.read(); |
307 | 140 | if (ourLog.isTraceEnabled()) { |
308 | 0 | ourLog.trace("Read byte: " + (char)nextChar + " (" + nextChar + ")"); |
309 | |
} |
310 | 0 | } catch (SocketTimeoutException e) { |
311 | 0 | break; |
312 | 140 | } |
313 | |
|
314 | 140 | if (nextChar == -1) { |
315 | 0 | break; |
316 | 140 | } else if (nextChar == 13) { |
317 | 70 | if (had13) { |
318 | |
|
319 | |
|
320 | |
|
321 | |
|
322 | |
|
323 | 0 | trailing = true; |
324 | |
} |
325 | 70 | had13 = true; |
326 | 70 | continue; |
327 | 70 | } else if (nextChar == 10) { |
328 | 70 | if (had10) { |
329 | 0 | trailing = true; |
330 | |
} |
331 | |
break; |
332 | |
} else { |
333 | |
break; |
334 | |
} |
335 | |
} |
336 | |
|
337 | 70 | if (trailing) { |
338 | 30 | break; |
339 | |
} |
340 | |
|
341 | 40 | } |
342 | |
|
343 | 30 | return bos.toByteArray(); |
344 | |
} |
345 | |
|
346 | |
private void verifySignature() throws SignatureVerificationException, DecodeException { |
347 | 415 | if (getSigner() != null && StringUtils.isBlank(mySignature)) { |
348 | 0 | String mode = (this instanceof Hl7OverHttpRequestDecoder) ? "request" : "response"; |
349 | 0 | throw new SignatureVerificationException("No HL7 Signature found in " + mode); |
350 | |
} |
351 | 415 | if (getSigner() != null) { |
352 | |
try { |
353 | 5 | getSigner().verify(myBytes, mySignature); |
354 | 0 | } catch (SignatureFailureException e) { |
355 | 0 | throw new DecodeException("Failed to verify signature due to an error (signature may possibly be valid, but verification failed)", e); |
356 | 5 | } |
357 | |
} |
358 | 415 | } |
359 | |
|
360 | |
public List<String> getConformanceProblems() { |
361 | 120 | if (myConformanceProblems == null) { |
362 | 60 | myConformanceProblems = new ArrayList<String>(); |
363 | |
} |
364 | 120 | return myConformanceProblems; |
365 | |
} |
366 | |
|
367 | |
|
368 | |
|
369 | |
|
370 | |
public String getContentType() { |
371 | 600 | return myContentType; |
372 | |
} |
373 | |
|
374 | |
|
375 | |
|
376 | |
|
377 | |
public String getResponseName() { |
378 | 0 | return myResponseName; |
379 | |
} |
380 | |
|
381 | |
|
382 | |
|
383 | |
|
384 | |
public Integer getResponseStatus() { |
385 | 0 | return myResponseStatus; |
386 | |
} |
387 | |
|
388 | |
protected abstract String readActionLineAndDecode(InputStream theInputStream) throws IOException, NoMessageReceivedException, DecodeException; |
389 | |
|
390 | |
private byte[] readBytesNonChunked(InputStream theInputStream) throws IOException { |
391 | 385 | ourLog.debug("Decoding message bytes using non-chunked encoding style"); |
392 | |
|
393 | 385 | int length = myContentLength > 0 ? myContentLength : IOUtils.DEFAULT_BUFFER_SIZE; |
394 | 385 | ByteArrayOutputStream bos = new ByteArrayOutputStream(length); |
395 | |
|
396 | 385 | byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; |
397 | 385 | myLastStartedReading = System.currentTimeMillis(); |
398 | 900 | while ((myContentLength < 0 || bos.size() < myContentLength)) { |
399 | 515 | if (myContentLength < 0) { |
400 | |
try { |
401 | 0 | if (theInputStream.available() <= 0) { |
402 | 0 | ourLog.trace("No more bytes available"); |
403 | 0 | break; |
404 | |
} |
405 | 0 | } catch (IOException e) { |
406 | 0 | ourLog.debug("Received IOException while calling inputStream#available()", e); |
407 | 0 | throw e; |
408 | 0 | } |
409 | |
} |
410 | |
|
411 | |
int max; |
412 | 515 | if (myContentLength > 0) { |
413 | 515 | max = myContentLength - bos.size(); |
414 | 515 | max = Math.min(max, buffer.length); |
415 | |
} else { |
416 | 0 | max = buffer.length; |
417 | |
} |
418 | |
|
419 | |
try { |
420 | 515 | int bytesRead = theInputStream.read(buffer, 0, max); |
421 | 510 | myLastStartedReading = System.currentTimeMillis(); |
422 | 510 | if (bytesRead == -1) { |
423 | 0 | ourLog.trace("Read end of stream"); |
424 | 0 | break; |
425 | |
} else { |
426 | 510 | if (ourLog.isTraceEnabled()) { |
427 | 20 | ourLog.trace("Read {} bytes from stream:\n{}", bytesRead, ByteUtils.formatBytesForLogging(bytesRead, 0, buffer)); |
428 | |
} |
429 | |
} |
430 | 510 | bos.write(buffer, 0, bytesRead); |
431 | 5 | } catch (SocketTimeoutException e) { |
432 | 5 | long elapsed = System.currentTimeMillis() - myLastStartedReading; |
433 | 5 | if (elapsed > myReadTimeout) { |
434 | 0 | throw e; |
435 | |
} else { |
436 | 5 | ourLog.debug("Trying to read for {} / {}ms, going to keep trying", elapsed, myReadTimeout); |
437 | |
try { |
438 | 5 | Thread.sleep(100); |
439 | 0 | } catch (InterruptedException e1) { |
440 | |
|
441 | 5 | } |
442 | |
} |
443 | 0 | } catch (IOException e) { |
444 | 0 | ourLog.debug("Received IOException while calling inputStream#available()", e); |
445 | 0 | throw e; |
446 | 515 | } |
447 | 515 | } |
448 | |
|
449 | 385 | return bos.toByteArray(); |
450 | |
} |
451 | |
|
452 | |
|
453 | |
|
454 | |
|
455 | |
|
456 | |
|
457 | |
|
458 | |
|
459 | |
|
460 | |
|
461 | |
|
462 | |
|
463 | |
|
464 | |
|
465 | |
|
466 | |
|
467 | |
|
468 | |
|
469 | |
|
470 | |
|
471 | |
|
472 | |
|
473 | |
public void readContentsFromInputStreamAndDecode(InputStream theInputStream) throws AuthorizationFailureException, DecodeException, IOException, SignatureVerificationException { |
474 | 50 | verifyNotUsed(); |
475 | |
|
476 | 50 | doReadContentsFromInputStreamAndDecode(theInputStream); |
477 | 50 | } |
478 | |
|
479 | |
protected String readFirstLine(InputStream theInputStream) throws IOException, NoMessageReceivedException { |
480 | 460 | ourLog.trace("Entering readFirstLine(InputStream) with IS: {}", theInputStream); |
481 | 460 | String retVal = readLine(theInputStream, true); |
482 | 365 | ourLog.trace("Exiting readFirstLine(InputStream) with result: {}", retVal); |
483 | 365 | return retVal; |
484 | |
} |
485 | |
|
486 | |
|
487 | |
|
488 | |
|
489 | |
|
490 | |
|
491 | |
|
492 | |
|
493 | |
|
494 | |
|
495 | |
|
496 | |
|
497 | |
|
498 | |
|
499 | |
|
500 | |
|
501 | |
|
502 | |
|
503 | |
|
504 | |
|
505 | |
|
506 | |
|
507 | |
|
508 | |
|
509 | |
|
510 | |
|
511 | |
public void readHeadersAndContentsFromInputStreamAndDecode(InputStream theInputStream) throws IOException, DecodeException, NoMessageReceivedException, SignatureVerificationException { |
512 | 460 | verifyNotUsed(); |
513 | |
|
514 | 460 | String actionLine = readActionLineAndDecode(theInputStream); |
515 | |
|
516 | 365 | ourLog.debug("Read action line: {}", actionLine); |
517 | |
|
518 | 365 | if (getHeaders() == null) { |
519 | 365 | setHeaders(new LinkedHashMap<String, String>()); |
520 | |
|
521 | |
while (true) { |
522 | 2250 | String nextLine = readLine(theInputStream); |
523 | 2250 | if (nextLine.length() == 0) { |
524 | 365 | break; |
525 | |
} |
526 | |
|
527 | 1885 | int colonIndex = nextLine.indexOf(':'); |
528 | 1885 | if (colonIndex == -1) { |
529 | 0 | throw new DecodeException("Invalid HTTP header line detected. Value is: " + nextLine); |
530 | |
} |
531 | |
|
532 | 1885 | String key = nextLine.substring(0, colonIndex); |
533 | 1885 | String value = nextLine.substring(colonIndex + 1).trim(); |
534 | |
|
535 | 1885 | ourLog.debug("Read header {}={}", key,value); |
536 | |
|
537 | 1885 | getHeaders().put(key, value); |
538 | 1885 | } |
539 | |
} |
540 | |
|
541 | 365 | doReadContentsFromInputStreamAndDecode(theInputStream); |
542 | |
|
543 | 365 | } |
544 | |
|
545 | |
private String readLine(InputStream theInputStream) throws IOException { |
546 | |
try { |
547 | 2320 | return readLine(theInputStream, false); |
548 | 0 | } catch (NoMessageReceivedException e) { |
549 | 0 | throw new Error("Threw a NoMessageReceivedException. This should not happen.", e); |
550 | |
} |
551 | |
} |
552 | |
|
553 | |
private String readLine(InputStream theInputStream, boolean theFirstLine) throws IOException, NoMessageReceivedException { |
554 | |
|
555 | 2780 | myLastStartedReading = System.currentTimeMillis(); |
556 | |
|
557 | 2780 | StringBuilder retVal = new StringBuilder(); |
558 | |
while (true) { |
559 | |
|
560 | |
int b; |
561 | |
try { |
562 | 85300 | b = theInputStream.read(); |
563 | 85220 | if (ourLog.isTraceEnabled()) { |
564 | 4170 | ourLog.trace("Read byte: " + (char)b + " (" + b + ")"); |
565 | |
} |
566 | 50 | } catch (SocketTimeoutException e) { |
567 | 50 | if (retVal.length() == 0 && theFirstLine) { |
568 | 30 | ourLog.trace("No message received, aborting readLine(InputStream, boolean)"); |
569 | 30 | throw new NoMessageReceivedException(); |
570 | |
} |
571 | 20 | ourLog.trace("No message received in readLine(InputStream, boolean), going to wait and continue"); |
572 | 20 | pauseDuringTimedOutRead(); |
573 | 20 | continue; |
574 | 85220 | } |
575 | |
|
576 | 85220 | if (b == 13) { |
577 | 2635 | continue; |
578 | 82585 | } else if (b == 10) { |
579 | 2685 | break; |
580 | 79900 | } else if (b == -1) { |
581 | 35 | ourLog.debug("Current read line is: {}", retVal); |
582 | 35 | ourLog.info("Read -1 from input stream, closing it"); |
583 | 35 | theInputStream.close(); |
584 | 35 | if (retVal.length() == 0) { |
585 | 35 | throw new SocketException("Received EOF from input stream"); |
586 | |
} |
587 | |
break; |
588 | 79865 | } else if (b < ' ') { |
589 | 45 | continue; |
590 | |
} else { |
591 | 79820 | retVal.append((char) b); |
592 | |
} |
593 | 79820 | } |
594 | |
|
595 | 2685 | ourLog.debug("Current read line is: {}", retVal); |
596 | |
|
597 | 2685 | return WHITESPACE_PATTERN.matcher(retVal.toString()).replaceAll(" ").trim(); |
598 | |
} |
599 | |
|
600 | |
private void pauseDuringTimedOutRead() throws SocketTimeoutException { |
601 | 20 | long elapsed = System.currentTimeMillis() - myLastStartedReading; |
602 | 20 | if (elapsed > myReadTimeout) { |
603 | 0 | ourLog.trace("Elapsed time of {} exceeds max {}, throwing SocketTimeoutException", elapsed, myReadTimeout); |
604 | 0 | throw new SocketTimeoutException(); |
605 | |
} |
606 | |
try { |
607 | 20 | Thread.sleep(100); |
608 | 0 | } catch (InterruptedException e1) { |
609 | |
|
610 | 20 | } |
611 | 20 | } |
612 | |
|
613 | |
|
614 | |
|
615 | |
|
616 | |
|
617 | |
public void setReadTimeout(long theReadTimeout) { |
618 | 110 | myReadTimeout = theReadTimeout; |
619 | 110 | } |
620 | |
|
621 | |
|
622 | |
|
623 | |
|
624 | |
|
625 | |
public void setResponseName(String theResponseName) { |
626 | 145 | myResponseName = theResponseName; |
627 | 145 | } |
628 | |
|
629 | |
|
630 | |
|
631 | |
|
632 | |
|
633 | |
public void setResponseStatus(Integer theResponseStatus) { |
634 | 145 | myResponseStatus = theResponseStatus; |
635 | 145 | } |
636 | |
|
637 | |
} |