Skip to content

Commit 1616253

Browse files
committed
move ConnectionPool to be shared among all instances; add undocumented logging system props com.marklogic.client.okhttp.httplogginginterceptor.level (BASIC,BODY,HEADERS,NONE) and com.marklogic.client.okhttp.httplogginginterceptor.output (LOGGER,STDOUT,STDERR)
1 parent 35b6df1 commit 1616253

File tree

1 file changed

+83
-184
lines changed

1 file changed

+83
-184
lines changed

src/main/java/com/marklogic/client/impl/OkHttpServices.java

Lines changed: 83 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@
8585
import com.fasterxml.jackson.databind.node.ArrayNode;
8686
import com.fasterxml.jackson.databind.node.ObjectNode;
8787

88+
import okhttp3.ConnectionPool;
8889
import okhttp3.Cookie;
90+
import okhttp3.CookieJar;
8991
import okhttp3.Headers;
9092
import okhttp3.HttpUrl;
9193
import okhttp3.MediaType;
@@ -145,6 +147,7 @@
145147
import java.util.List;
146148
import java.util.Map;
147149
import java.util.NoSuchElementException;
150+
import java.util.Properties;
148151
import java.util.Random;
149152
import java.util.Set;
150153
import java.util.UUID;
@@ -156,6 +159,9 @@
156159
public class OkHttpServices implements RESTServices {
157160
static final private Logger logger = LoggerFactory.getLogger(OkHttpServices.class);
158161

162+
static final public String OKHTTP_LOGGINGINTERCEPTOR_LEVEL = "com.marklogic.client.okhttp.httplogginginterceptor.level";
163+
static final public String OKHTTP_LOGGINGINTERCEPTOR_OUTPUT = "com.marklogic.client.okhttp.httplogginginterceptor.output";
164+
159165
static final private String DOCUMENT_URI_PREFIX = "/documents?uri=";
160166

161167
static final private int DELAY_FLOOR = 125;
@@ -188,6 +194,8 @@ public void verify(String hostname, X509Certificate cert) throws SSLException {
188194
}
189195
}
190196

197+
static final private ConnectionPool connectionPool = new ConnectionPool();
198+
191199
private DatabaseClient databaseClient;
192200
private String database = null;
193201
private HttpUrl baseUri;
@@ -208,7 +216,6 @@ static protected class ThreadState {
208216
}
209217
}
210218

211-
// workaround: Jersey keeps the DIGEST nonce in a thread-local variable
212219
private final ThreadLocal<ThreadState> threadState = new ThreadLocal<ThreadState>() {
213220
@Override
214221
protected ThreadState initialValue() {
@@ -244,14 +251,10 @@ private FailedRequest extractErrorFields(Response response) {
244251

245252
@Override
246253
public void connect(String host, int port, String database, String user, String password,
247-
Authentication authenType, SSLContext context,
254+
Authentication authenType, SSLContext sslContext,
248255
SSLHostnameVerifier verifier) {
249256
HostnameVerifier hostnameVerifier = null;
250-
if (verifier == null) {
251-
if (context != null) {
252-
hostnameVerifier = null;
253-
}
254-
} else if (verifier == SSLHostnameVerifier.ANY) {
257+
if (verifier == SSLHostnameVerifier.ANY) {
255258
hostnameVerifier = new HostnameVerifier() {
256259
@Override
257260
public boolean verify(String hostname, SSLSession session) {
@@ -261,42 +264,27 @@ public boolean verify(String hostname, SSLSession session) {
261264
} else if (verifier == SSLHostnameVerifier.COMMON) {
262265
hostnameVerifier = null;
263266
} else if (verifier == SSLHostnameVerifier.STRICT) {
264-
// TODO: implement SSLHostnameVerifier.STRICT for OkHttp
265-
throw new IllegalStateException("Not yet implemented");
266-
} else if (context != null) {
267+
hostnameVerifier = null;
268+
} else if (verifier != null) {
267269
hostnameVerifier = new HostnameVerifierAdapter(verifier);
268-
} else {
269-
throw new IllegalArgumentException(
270-
"Null SSLContext but non-null SSLHostnameVerifier for client");
271-
}
272-
connect(host, port, database, user, password, authenType, context, hostnameVerifier);
270+
}// else {
271+
// throw new IllegalArgumentException(
272+
// "Null SSLContext but non-null SSLHostnameVerifier for client");
273+
//}
274+
connect(host, port, database, user, password, authenType, sslContext, hostnameVerifier);
273275
}
274276

275277
private void connect(String host, int port, String database, String user, String password,
276-
Authentication authenType, SSLContext context,
278+
Authentication authenType, SSLContext sslContext,
277279
HostnameVerifier verifier) {
278280
logger.debug("Connecting to {} at {} as {}", new Object[]{host, port, user});
279281

280282
if (host == null) throw new IllegalArgumentException("No host provided");
281283

282-
if (authenType == null) {
283-
if (context != null) {
284-
authenType = Authentication.BASIC;
285-
}
286-
}
287-
288-
if (authenType != null) {
289-
if (user == null) throw new IllegalArgumentException("No user provided");
290-
if (password == null) throw new IllegalArgumentException("No password provided");
291-
if (authenType == Authentication.BASIC) {
292-
checkFirstRequest = false;
293-
}
294-
}
295-
296284
this.database = database;
297285

298286
this.baseUri = new HttpUrl.Builder()
299-
.scheme(context == null ? "http" : "https")
287+
.scheme(sslContext == null ? "http" : "https")
300288
.host(host)
301289
.port(port)
302290
.encodedPath("/v1/ping")
@@ -307,46 +295,72 @@ private void connect(String host, int port, String database, String user, String
307295
final BasicAuthenticator basicAuthenticator = new BasicAuthenticator(credentials);
308296
final DigestAuthenticator digestAuthenticator = new DigestAuthenticator(credentials);
309297

298+
if ( authenType == null && sslContext != null ) {
299+
authenType = Authentication.BASIC;
300+
}
301+
310302
DispatchingAuthenticator.Builder authenticator = new DispatchingAuthenticator.Builder();
311-
if (authenType == Authentication.BASIC) {
312-
authenticator = authenticator.with("basic", basicAuthenticator);
313-
} else if (authenType == Authentication.DIGEST) {
314-
authenticator = authenticator.with("digest", digestAuthenticator);
303+
if (authenType == null) {
304+
checkFirstRequest = false;
305+
} else {
306+
if (user == null) throw new IllegalArgumentException("No user provided");
307+
if (password == null) throw new IllegalArgumentException("No password provided");
308+
if (authenType == Authentication.BASIC) {
309+
authenticator = authenticator.with("basic", basicAuthenticator);
310+
checkFirstRequest = false;
311+
} else if (authenType == Authentication.DIGEST) {
312+
authenticator = authenticator.with("digest", digestAuthenticator);
313+
checkFirstRequest = true;
314+
} else {
315+
throw new MarkLogicInternalException(
316+
"Internal error - unknown authentication type: " + authenType.name());
317+
}
315318
}
316319

317-
this.client = new OkHttpClient.Builder()
320+
OkHttpClient.Builder clientBldr = new OkHttpClient.Builder()
318321
.authenticator(new CachingAuthenticatorDecorator(authenticator.build(), authCache))
319322
.addInterceptor(new AuthenticationCacheInterceptor(authCache))
320-
/*
321-
.addNetworkInterceptor(
322-
new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
323-
public void log(String message) {
324-
logger.debug(message);
325-
}
326-
})
327-
.setLevel(HttpLoggingInterceptor.Level.HEADERS)
328-
)
329-
*/
330323
.followRedirects(false)
331324
.followSslRedirects(false)
332-
.build();
325+
// all clients share a single connection pool
326+
.connectionPool(connectionPool)
327+
// cookies are ignored (except when a Transaction is being used)
328+
.cookieJar(CookieJar.NO_COOKIES);
333329

334-
/*
335-
if (connection != null) connection = null;
336-
if (connMgr != null) {
337-
connMgr.shutdown();
338-
connMgr = null;
339-
}
340-
if (client != null) {
341-
client.destroy();
342-
client = null;
330+
if ( verifier != null ) {
331+
clientBldr = clientBldr.hostnameVerifier(verifier);
343332
}
344333

345-
346-
String baseUri = ((context == null) ? "http" : "https") + "://" + host + ":" + port + "/v1/";
347-
348334
Properties props = System.getProperties();
349335

336+
if (props.containsKey(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) {
337+
final boolean useLogger = "LOGGER".equalsIgnoreCase(props.getProperty(OKHTTP_LOGGINGINTERCEPTOR_OUTPUT));
338+
final boolean useStdErr = "STDERR".equalsIgnoreCase(props.getProperty(OKHTTP_LOGGINGINTERCEPTOR_OUTPUT));
339+
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(
340+
new HttpLoggingInterceptor.Logger() {
341+
public void log(String message) {
342+
if ( useLogger == true ) {
343+
logger.debug(message);
344+
} else if ( useStdErr == true ) {
345+
System.err.println(message);
346+
} else {
347+
System.out.println(message);
348+
}
349+
}
350+
}
351+
);
352+
if ( "BASIC".equalsIgnoreCase(props.getProperty(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) ) {
353+
interceptor = interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
354+
} else if ( "BODY".equalsIgnoreCase(props.getProperty(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) ) {
355+
interceptor = interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
356+
} else if ( "HEADERS".equalsIgnoreCase(props.getProperty(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) ) {
357+
interceptor = interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
358+
} else if ( "NONE".equalsIgnoreCase(props.getProperty(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) ) {
359+
interceptor = interceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
360+
}
361+
clientBldr = clientBldr.addNetworkInterceptor(interceptor);
362+
}
363+
350364
if (props.containsKey(MAX_DELAY_PROP)) {
351365
String maxDelayStr = props.getProperty(MAX_DELAY_PROP);
352366
if (maxDelayStr != null && maxDelayStr.length() > 0) {
@@ -366,123 +380,12 @@ public void log(String message) {
366380
}
367381
}
368382

369-
// TODO: integrated control of HTTP Client and Jersey Client logging
370-
if (!props.containsKey("org.apache.commons.logging.Log")) {
371-
System.setProperty("org.apache.commons.logging.Log",
372-
"org.apache.commons.logging.impl.SimpleLog");
373-
}
374-
if (!props.containsKey("org.apache.commons.logging.simplelog.log.org.apache.http")) {
375-
System.setProperty(
376-
"org.apache.commons.logging.simplelog.log.org.apache.http",
377-
"warn");
378-
}
379-
if (!props.containsKey("org.apache.commons.logging.simplelog.log.org.apache.http.wire")) {
380-
System.setProperty(
381-
"org.apache.commons.logging.simplelog.log.org.apache.http.wire",
382-
"warn");
383-
}
384-
385-
Scheme scheme = null;
386-
if (context == null) {
387-
SchemeSocketFactory socketFactory = PlainSocketFactory
388-
.getSocketFactory();
389-
scheme = new Scheme("http", port, socketFactory);
390-
} else {
391-
SSLSocketFactory socketFactory = new SSLSocketFactory(context,
392-
verifier);
393-
scheme = new Scheme("https", port, socketFactory);
394-
}
395-
SchemeRegistry schemeRegistry = new SchemeRegistry();
396-
schemeRegistry.register(scheme);
397-
398-
int maxRouteConnections = 100;
399-
int maxTotalConnections = 2 * maxRouteConnections;
400-
401-
// start 4.1
402-
connMgr = new ThreadSafeClientConnManager(
403-
schemeRegistry);
404-
connMgr.setMaxTotal(maxTotalConnections);
405-
connMgr.setDefaultMaxPerRoute(maxRouteConnections);
406-
connMgr.setMaxForRoute(new HttpRoute(new HttpHost(baseUri)),
407-
maxRouteConnections);
408-
// end 4.1
409-
410-
// CredentialsProvider credentialsProvider = new
411-
// BasicCredentialsProvider();
412-
// credentialsProvider.setCredentials(new AuthScope(host, port),
413-
// new UsernamePasswordCredentials(user, password));
414-
415-
HttpParams httpParams = new BasicHttpParams();
416-
417-
if (authenType != null) {
418-
List<String> authpref = new ArrayList<String>();
419-
if (authenType == Authentication.BASIC)
420-
authpref.add(AuthPolicy.BASIC);
421-
else if (authenType == Authentication.DIGEST)
422-
authpref.add(AuthPolicy.DIGEST);
423-
else
424-
throw new MarkLogicInternalException(
425-
"Internal error - unknown authentication type: "
426-
+ authenType.name());
427-
httpParams.setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
428-
}
429-
430-
httpParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
431-
432-
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
433-
434-
// HttpConnectionParams.setStaleCheckingEnabled(httpParams, false);
435-
383+
this.client = clientBldr.build();
384+
// System.setProperty("javax.net.debug", "all"); // all or ssl
385+
/*
436386
// long-term alternative to isFirstRequest alive
437387
// HttpProtocolParams.setUseExpectContinue(httpParams, false);
438388
// httpParams.setIntParameter(CoreProtocolPNames.WAIT_FOR_CONTINUE, 1000);
439-
440-
DefaultApacheHttpClient4Config config = new DefaultApacheHttpClient4Config();
441-
Map<String, Object> configProps = config.getProperties();
442-
configProps
443-
.put(ApacheHttpClient4Config.PROPERTY_PREEMPTIVE_BASIC_AUTHENTICATION,
444-
false);
445-
configProps.put(ApacheHttpClient4Config.PROPERTY_DISABLE_COOKIES, true);
446-
configProps.put(ApacheHttpClient4Config.PROPERTY_CONNECTION_MANAGER,
447-
connMgr);
448-
// ignored?
449-
configProps.put(ApacheHttpClient4Config.PROPERTY_FOLLOW_REDIRECTS,
450-
false);
451-
// configProps.put(ApacheHttpClient4Config.PROPERTY_CREDENTIALS_PROVIDER,
452-
// credentialsProvider);
453-
configProps.put(ApacheHttpClient4Config.PROPERTY_HTTP_PARAMS,
454-
httpParams);
455-
// switches from buffered to streamed in Jersey Client
456-
configProps.put(ApacheHttpClient4Config.PROPERTY_CHUNKED_ENCODING_SIZE,
457-
32 * 1024);
458-
459-
client = ApacheHttpClient4.create(config);
460-
461-
// System.setProperty("javax.net.debug", "all"); // all or ssl
462-
463-
if (authenType == null) {
464-
checkFirstRequest = false;
465-
} else if (authenType == Authentication.BASIC) {
466-
checkFirstRequest = false;
467-
468-
client.addFilter(new HTTPBasicAuthFilter(user, password));
469-
} else if (authenType == Authentication.DIGEST) {
470-
checkFirstRequest = true;
471-
472-
// workaround for JerseyClient bug 1445
473-
client.addFilter(new DigestChallengeFilter());
474-
475-
client.addFilter(new HTTPDigestAuthFilter(user, password));
476-
}
477-
else {
478-
throw new MarkLogicInternalException(
479-
"Internal error - unknown authentication type: "
480-
+ authenType.name());
481-
}
482-
483-
// client.addFilter(new LoggingFilter(System.err));
484-
485-
connection = client.resource(baseUri);
486389
*/
487390
}
488391

@@ -500,7 +403,7 @@ private OkHttpClient getConnection() {
500403
return client;
501404
} else if ( released ) {
502405
throw new IllegalStateException(
503-
"You cannot use this connected object anymore--connection has already been released");
406+
"You cannot use this connected object anymore--connection has already been released");
504407
} else {
505408
throw new MarkLogicInternalException("Cannot proceed--connection is null for unknown reason");
506409
}
@@ -510,19 +413,15 @@ private OkHttpClient getConnection() {
510413
public void release() {
511414
try {
512415
released = true;
513-
client.dispatcher().executorService().shutdown();
416+
client.dispatcher().executorService().shutdownNow();
514417
} finally {
515418
try {
516-
client.connectionPool().evictAll();
419+
if ( client.cache() != null ) client.cache().close();
420+
} catch (IOException e) {
421+
throw new MarkLogicIOException(e);
517422
} finally {
518-
try {
519-
if ( client.cache() != null ) client.cache().close();
520-
} catch (IOException e) {
521-
throw new MarkLogicIOException(e);
522-
} finally {
523-
client = null;
524-
logger.debug("Releasing connection");
525-
}
423+
client = null;
424+
logger.debug("Releasing connection");
526425
}
527426
}
528427
}

0 commit comments

Comments
 (0)