1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.common.http.service;
17
18 import static com.google.common.base.Optional.absent;
19 import static com.google.common.base.Optional.fromNullable;
20 import static com.google.common.base.Preconditions.checkState;
21 import static com.google.common.collect.Lists.newArrayList;
22 import static java.lang.System.currentTimeMillis;
23 import static org.kuali.common.http.model.HttpStatus.SUCCESS;
24 import static org.kuali.common.util.base.Exceptions.illegalState;
25 import static org.kuali.common.util.base.Threads.sleep;
26 import static org.kuali.common.util.log.Loggers.newLogger;
27
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.List;
31
32 import org.apache.commons.io.IOUtils;
33 import org.apache.http.HttpEntity;
34 import org.apache.http.client.HttpRequestRetryHandler;
35 import org.apache.http.client.config.RequestConfig;
36 import org.apache.http.client.methods.CloseableHttpResponse;
37 import org.apache.http.client.methods.HttpGet;
38 import org.apache.http.config.SocketConfig;
39 import org.apache.http.impl.client.CloseableHttpClient;
40 import org.apache.http.impl.client.HttpClients;
41 import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
42 import org.kuali.common.http.model.HttpContext;
43 import org.kuali.common.http.model.HttpRequestResult;
44 import org.kuali.common.http.model.HttpStatus;
45 import org.kuali.common.http.model.HttpWaitResult;
46 import org.kuali.common.util.FormatUtils;
47 import org.slf4j.Logger;
48
49 import com.google.common.base.Optional;
50
51 public class DefaultHttpService implements HttpService {
52
53 private static final Logger logger = newLogger();
54
55 @Override
56 public HttpWaitResult wait(String url) {
57 return wait(HttpContext.create(url));
58 }
59
60 @Override
61 public HttpWaitResult wait(HttpContext context) {
62 HttpWaitResult result = getWaitResult(context);
63 HttpStatus actual = result.getStatus();
64 if (!context.isQuiet()) {
65 checkState(SUCCESS.equals(result.getStatus()), "[%s] returned [%s]", context.getUrl(), actual);
66 }
67 return result;
68 }
69
70 protected HttpWaitResult getWaitResult(HttpContext context) {
71 logger.debug(context.getUrl());
72 CloseableHttpClient client = getHttpClient(context);
73 long start = currentTimeMillis();
74 long end = start + context.getOverallTimeoutMillis();
75 List<HttpRequestResult> requestResults = newArrayList();
76 Object[] args = { context.getLogMsgPrefix(), context.getUrl(), FormatUtils.getTime(context.getOverallTimeoutMillis()) };
77 if (!context.isQuiet()) {
78 logger.info("{} - [{}] - [Timeout in {}]", args);
79 }
80 int retryAttempts = 0;
81 for (;;) {
82 HttpRequestResult rr = doRequest(client, context);
83 requestResults.add(rr);
84 if (!isFinishState(context, rr, end, retryAttempts)) {
85 logHttpRequestResult(context.getLogMsgPrefix(), rr, context.getUrl(), end, context.isQuiet());
86 sleep(context.getSleepIntervalMillis());
87 } else {
88 HttpStatus status = getResultStatus(context, retryAttempts, rr, end);
89 HttpWaitResult waitResult = HttpWaitResult.builder(status, rr, start).requestResults(requestResults).build();
90 logWaitResult(waitResult, context.getUrl(), context.getLogMsgPrefix(), context.isQuiet());
91 return waitResult;
92 }
93 retryAttempts++;
94 }
95 }
96
97 protected void logHttpRequestResult(String logMsgPrefix, HttpRequestResult result, String url, long end, boolean quiet) {
98 String statusText = getStatusText(result);
99 String timeout = FormatUtils.getTime(end - currentTimeMillis());
100 Object[] args = { logMsgPrefix, url, statusText, timeout };
101 if (!quiet) {
102 logger.info("{} - [{}] - [{}] - [Timeout in {}]", args);
103 }
104 }
105
106 protected void logWaitResult(HttpWaitResult result, String url, String logMsgPrefix, boolean quiet) {
107 String status = result.getStatus().toString();
108 String elapsed = FormatUtils.getTime(result.getStop() - result.getStart());
109 String statusText = getStatusText(result.getFinalRequestResult());
110 Object[] args = { logMsgPrefix, url, status, statusText, elapsed };
111 if (!quiet) {
112 logger.info("{} - [{}] - [{} - {}] Total time: {}", args);
113 }
114 }
115
116 protected String getStatusText(HttpRequestResult result) {
117 if (result.getException().isPresent()) {
118 return result.getStatusText();
119 } else {
120 int code = result.getStatusCode().get();
121 return code + " - " + result.getStatusText();
122 }
123 }
124
125 protected HttpStatus getResultStatus(HttpContext context, int retryAttempts, HttpRequestResult rr, long end) {
126
127 if (maxRetriesExceeded(context, retryAttempts)) {
128 return HttpStatus.MAX_RETRIES_EXCEEDED;
129 }
130
131
132 if (rr.getStop() > end) {
133 return HttpStatus.TIMEOUT;
134 }
135
136
137 if (rr.getException().isPresent()) {
138 return HttpStatus.IO_EXCEPTION;
139 }
140
141
142 checkState(rr.getStatusCode().isPresent(), "statusCode should never be null here");
143
144
145 if (isSuccess(context.getSuccessCodes(), rr.getStatusCode().get())) {
146 return HttpStatus.SUCCESS;
147 } else {
148 return HttpStatus.INVALID_HTTP_STATUS_CODE;
149 }
150 }
151
152 protected boolean maxRetriesExceeded(HttpContext context, int retryAttempts) {
153 if (context.getMaxRetries().isPresent()) {
154 return retryAttempts > context.getMaxRetries().get();
155 } else {
156 return false;
157 }
158 }
159
160 protected boolean quitTrying(HttpContext context, int retryAttempts) {
161 if (context.getMaxRetries().isPresent()) {
162 return retryAttempts >= context.getMaxRetries().get();
163 } else {
164 return false;
165 }
166 }
167
168 protected boolean isFinishState(HttpContext context, HttpRequestResult rr, long end, int retryAttempts) {
169
170 if (quitTrying(context, retryAttempts)) {
171 return true;
172 }
173
174
175 if (rr.getStop() > end) {
176 return true;
177 }
178
179
180 if (!rr.getStatusCode().isPresent()) {
181 return false;
182 }
183
184
185 if (isSuccess(context.getSuccessCodes(), rr.getStatusCode().get())) {
186 return true;
187 }
188
189
190 if (isContinueWaiting(context.getContinueWaitingCodes(), rr.getStatusCode().get())) {
191 return false;
192 } else {
193
194 return true;
195 }
196 }
197
198 protected HttpRequestResult doRequest(CloseableHttpClient client, HttpContext context) {
199 long start = currentTimeMillis();
200 try {
201 HttpGet httpGet = new HttpGet(context.getUrl());
202 CloseableHttpResponse response = client.execute(httpGet);
203 Optional<String> responseBody = getResponseBodyAsString(response, context);
204 int statusCode = response.getStatusLine().getStatusCode();
205 String statusText = response.getStatusLine().getReasonPhrase();
206 return HttpRequestResult.builder(statusText, statusCode, responseBody, start).build();
207 } catch (IOException e) {
208 return HttpRequestResult.builder(e, start).build();
209 }
210 }
211
212 protected Optional<String> getResponseBodyAsString(CloseableHttpResponse response, HttpContext context) {
213 InputStream in = null;
214 try {
215 Optional<HttpEntity> entity = fromNullable(response.getEntity());
216 if (!entity.isPresent()) {
217 return absent();
218 }
219 in = entity.get().getContent();
220 byte[] buffer = new byte[4096];
221 int length = in.read(buffer);
222 long bytesRead = 0;
223 StringBuilder sb = new StringBuilder();
224 while (length != -1) {
225 String content = new String(buffer, 0, length, context.getEncoding());
226 sb.append(content);
227 bytesRead += length;
228 if (isMaxBytes(bytesRead, context)) {
229 break;
230 }
231 length = in.read(buffer);
232 }
233 return Optional.of(sb.toString());
234 } catch (IOException e) {
235 throw illegalState("unexpected io error", e);
236 } finally {
237 IOUtils.closeQuietly(response);
238 IOUtils.closeQuietly(in);
239 }
240 }
241
242 protected boolean isMaxBytes(long bytesRead, HttpContext context) {
243 if (context.getMaxBytes().isPresent()) {
244 long max = context.getMaxBytes().get();
245 return bytesRead >= max;
246 } else {
247 return false;
248 }
249 }
250
251 protected boolean isSuccess(List<Integer> successCodes, int resultCode) {
252 return isMatch(resultCode, successCodes);
253 }
254
255 protected boolean isContinueWaiting(List<Integer> continueWaitingCodes, int resultCode) {
256 return isMatch(resultCode, continueWaitingCodes);
257 }
258
259 protected boolean isMatch(int i, List<Integer> integers) {
260 for (int integer : integers) {
261 if (i == integer) {
262 return true;
263 }
264 }
265 return false;
266 }
267
268 protected CloseableHttpClient getHttpClient(HttpContext context) {
269 int timeout = context.getRequestTimeoutMillis();
270 SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(timeout).build();
271 HttpRequestRetryHandler retryHandler = new StandardHttpRequestRetryHandler(0, false);
272 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).setConnectionRequestTimeout(timeout)
273 .setStaleConnectionCheckEnabled(true).build();
274 return HttpClients.custom().setRetryHandler(retryHandler).setDefaultSocketConfig(socketConfig).setDefaultRequestConfig(requestConfig).build();
275 }
276
277 }