Java tutorial
/** * Copyright 2005-2015 titilink * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL * 1.0 (the "Licenses"). You can select the license that you prefer but you may * not use this file except in compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the LGPL 3.0 license at * http://www.opensource.org/licenses/lgpl-3.0 * * You can obtain a copy of the LGPL 2.1 license at * http://www.opensource.org/licenses/lgpl-2.1 * * You can obtain a copy of the CDDL 1.0 license at * http://www.opensource.org/licenses/cddl1 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * https://github.com/titilink/titilink-framework * * titilink is a registered trademark of titilink.inc */ package com.titilink.camel.rest.client; import com.titilink.camel.rest.common.RestResponse; import com.titilink.common.app.RestletClientProperties; import com.titilink.common.log.AppLogger; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.*; import org.restlet.data.ChallengeResponse; import org.restlet.engine.header.Header; import org.restlet.engine.header.HeaderConstants; import org.restlet.util.Series; import javax.net.ssl.SSLException; import java.net.URI; import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * ? * <p> * @author by kam * @date 2015/05/01 * @since v1.0.0 */ public final class CamelClient { private static final String REST_CLIENT_CONN_COUNT = "client.rest.connCount"; private static final String KEY_SPLIT_MARK = "_"; private static final int DEFAULT_AT_LEAST_CONNECTION_NUM = 5; private static int minConnectionNum = DEFAULT_AT_LEAST_CONNECTION_NUM; private static final int HTTP_READ_TIME = 300000; /** * client * keyhost + port + isSSL * valueclient */ private static final Map<String, CamelClient> CLIENTS = new HashMap<String, CamelClient>(); /** * ??? */ private static final List<String> HEADER_NEED_SKIP = new ArrayList<String>(); static { HEADER_NEED_SKIP.add("connection"); HEADER_NEED_SKIP.add("content-length"); HEADER_NEED_SKIP.add("host"); HEADER_NEED_SKIP.add("user-agent"); HEADER_NEED_SKIP.add("transfer-encoding"); HEADER_NEED_SKIP.add("x-real-ip"); minConnectionNum = RestletClientProperties.getIntProperty("camelClientMinConnectionNum", DEFAULT_AT_LEAST_CONNECTION_NUM); } /** * ? */ private static final AppLogger LOGGER = AppLogger.getInstance(CamelClient.class); private EventLoopGroup workerGroup; /** * host??ip */ private final String host; /** * port??port */ private final int port; /** * */ private final int connAmout; private final Bootstrap bootstrap; /** * ????? */ private final BlockingQueue<Channel> channelQueue; /** * ?? */ private AtomicBoolean initFlag = new AtomicBoolean(false); /** * ?client */ private static final int DEFAULT_CONN_AMOUNTS = 30; /** * <> * ?? */ private CamelClient(String host, int port, int connAmout, boolean ssl) { this.host = host; this.port = port; //Configure the client. workerGroup = new NioEventLoopGroup(); bootstrap = new Bootstrap(); bootstrap.group(workerGroup).channel(NioSocketChannel.class).handler(new HttpClientInitializer(ssl)); bootstrap.option(ChannelOption.SO_KEEPALIVE, true); bootstrap.remoteAddress(this.host, this.port); //at least one connection this.connAmout = (connAmout > 0) ? connAmout : minConnectionNum; channelQueue = new LinkedBlockingQueue<Channel>(); } private Channel connect() { return bootstrap.connect().syncUninterruptibly().channel(); } private static String genClientKey(String host, int port, boolean ssl) { StringBuilder sbuilder = new StringBuilder(); sbuilder.append(host); sbuilder.append(KEY_SPLIT_MARK); sbuilder.append(port); sbuilder.append(KEY_SPLIT_MARK); sbuilder.append(ssl); return sbuilder.toString(); } private void initConns() { if (!initFlag.compareAndSet(false, true)) { return; } LOGGER.info("initConns connAmout is {}", minConnectionNum); for (int i = 0; i < minConnectionNum; i++) { if (!channelQueue.offer(connect())) { LOGGER.error("initConns. channel dose not be added to the queue"); } } } private static boolean isSSl(String scheme) { return (null != scheme && "https".equalsIgnoreCase(scheme)) ? true : false; } private static CamelClient getInstance(URI uri) { boolean ssl = isSSl(uri.getScheme()); String clientKey = genClientKey(uri.getHost(), uri.getPort(), ssl); CamelClient client = CLIENTS.get(clientKey); if (null != client) { return client; } else { synchronized (CamelClient.class) { client = CLIENTS.get(clientKey); if (null == client) { client = new CamelClient(uri.getHost(), uri.getPort(), getClientConnCount(), ssl); CLIENTS.put(clientKey, client); } } return client; } } private static int getClientConnCount() { return RestletClientProperties.getIntProperty(REST_CLIENT_CONN_COUNT, DEFAULT_CONN_AMOUNTS); } private BlockingQueue<Channel> getChannelQueue() { return channelQueue; } /** * ??http get * * @param uri * @param additionalHeaders header????token * @param challengeResponse * @return */ public static RestResponse handleGet(URI uri, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse) { return handleGet(uri, additionalHeaders, challengeResponse, HTTP_READ_TIME); } /** * ??Http post * * @param uri * @param reqBuffer ? * @param additionalHeaders header????token * @param challengeResponse * @return */ public static RestResponse handlePost(URI uri, byte[] reqBuffer, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse) { return handlePost(uri, reqBuffer, additionalHeaders, challengeResponse, HTTP_READ_TIME); } /** * put * * @param uri * @param reqBuffer ? * @param additionalHeaders header????token * @return json */ public static RestResponse handlePut(URI uri, byte[] reqBuffer, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse) { return handlePut(uri, reqBuffer, additionalHeaders, challengeResponse, HTTP_READ_TIME); } /** * delete * * @param uri * @param additionalHeaders header????token * @param challengeResponse * @return */ public static RestResponse handleDelete(URI uri, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse) { return handleDelete(uri, additionalHeaders, challengeResponse, HTTP_READ_TIME); } /** * ??http get * * @param uri * @param additionalHeaders header????token * @param challengeResponse * @param msTimeout ?0?10s * @return */ public static RestResponse handleGet(URI uri, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse, long msTimeout) { return handle(HttpMethod.GET, uri, null, additionalHeaders, challengeResponse, true, msTimeout); } /** * ??Http post * * @param uri * @param reqBuffer ? * @param additionalHeaders header????token * @param challengeResponse * @param msTimeout ?0?10s * @return */ public static RestResponse handlePost(URI uri, byte[] reqBuffer, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse, long msTimeout) { return handle(HttpMethod.POST, uri, reqBuffer, additionalHeaders, challengeResponse, true, msTimeout); } /** * put * * @param uri * @param reqBuffer ? * @param additionalHeaders header????token * @param msTimeout ?0?10s * @return json */ public static RestResponse handlePut(URI uri, byte[] reqBuffer, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse, long msTimeout) { return handle(HttpMethod.PUT, uri, reqBuffer, additionalHeaders, challengeResponse, true, msTimeout); } /** * delete * * @param uri * @param additionalHeaders header????token * @param challengeResponse * @param msTimeout ?0?10s * @return */ public static RestResponse handleDelete(URI uri, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse, long msTimeout) { return handle(HttpMethod.DELETE, uri, null, additionalHeaders, challengeResponse, true, msTimeout); } /** * ??GET POST PUT DELETE * * @param uri * @param reqBuffer ? * @param additionalHeaders header????token * @param challengeResponse challengeResponse * @param msTimeout * @return RestResponse * @since v1.0.3 */ public static RestResponse handleDefault(String method, URI uri, byte[] reqBuffer, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse, long msTimeout) { return handle(HttpMethod.valueOf(method.toUpperCase()), uri, reqBuffer, additionalHeaders, challengeResponse, true, msTimeout); } private static RestResponse handle(HttpMethod httpMethod, URI uri, byte[] reqBuffer, Map<String, String> additionalHeaders, ChallengeResponse challengeResponse, boolean longConn, long msTimeout) { LOGGER.debug("handleRequest begin. uri={}, method={}, longConn={}, msTimeout={}", uri, httpMethod, longConn, msTimeout); if (null == uri) { LOGGER.error("uri is null."); return null; } CamelClient client = getInstance(uri); // Create a simple GET request with just headers. FullHttpRequest request = null; String requestUri = uri.getRawQuery() == null ? uri.getRawPath() : uri.getRawPath() + "?" + uri.getRawQuery(); if (null == reqBuffer) { request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, httpMethod, requestUri); } else { ByteBuf byteBuf = Unpooled.wrappedBuffer(reqBuffer); request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, httpMethod, requestUri, byteBuf); request.headers().add(HttpHeaders.Names.CONTENT_LENGTH, request.content().readableBytes()); } request.headers().add(HttpHeaders.Names.HOST, uri.getHost() + ':' + uri.getPort()); request.headers().add(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); request.headers().add("user-agent", "HttpClient"); if (null != additionalHeaders) { Iterator<Map.Entry<String, String>> iter = additionalHeaders.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, String> entry = (Map.Entry<String, String>) iter.next(); String key = entry.getKey(); String headerValue = entry.getValue(); if (null == key || HEADER_NEED_SKIP.contains(key.toLowerCase())) { continue; } if (null != headerValue) { request.headers().add(key, headerValue); } } } // duel with challengeResponse if (null != challengeResponse) { Series<Header> headers = new Series<Header>(Header.class); for (Map.Entry<String, String> entry : request.headers().entries()) { if (null != entry && null != entry.getValue()) { headers.add(entry.getKey(), entry.getValue()); } } String authHeader = org.restlet.engine.security.AuthenticatorUtils.formatResponse(challengeResponse, null, headers); for (Header header : headers) { if (null != header) { request.headers().add(header.getName(), header.getValue()); } } if (authHeader != null) { request.headers().add(HeaderConstants.HEADER_AUTHORIZATION, authHeader); } } RestResponse result = null; int retry = 3; boolean success = false; while (!success && retry > 0) { Channel channel = null; // ???????? if (longConn) { channel = getLongChannel(client); } else { LOGGER.info("open a new channel"); channel = client.connect(); } // channel? if (null == channel || null == channel.pipeline()) { LOGGER.error("channle is null or channel.pipeline is null."); return null; } final HttpClientHandler httpClientHandler = (HttpClientHandler) channel.pipeline().get("handler"); if (null == httpClientHandler) { LOGGER.error("httpClientHandler is null."); return null; } try { channel.writeAndFlush(request); result = httpClientHandler.getResult(msTimeout); success = true; } catch (Throwable t) { LOGGER.error("send request by channel raised a exception:", t); channel.close(); if (t instanceof SSLException) { success = false; } else { //?? success = true; } } finally { retry--; // ?? // ???????????? if (longConn && client.getChannelQueue().size() < client.connAmout && !isTimeOut(result)) { try { client.getChannelQueue().put(channel); } catch (InterruptedException e) { LOGGER.error("put channel to channelQueue had been interrupted."); } } else { LOGGER.info("Close channel."); channel.close(); } } } if (result == null) { LOGGER.error("response is null."); } return result; } private static boolean isTimeOut(RestResponse result) { return result != null && result.getStatus() != null && result.getStatus().getCode() == 10001; } private static Channel getLongChannel(CamelClient client) { client.initConns(); LOGGER.info("channelQueue size:{}", client.getChannelQueue().size()); Channel channel = null; try { channel = client.getChannelQueue().poll(2, TimeUnit.SECONDS); } catch (InterruptedException e) { LOGGER.error("get channel from queue had been interrupted."); } if (channel == null || !channel.isActive()) { if (channel != null) { channel.close(); } //channel channel = client.connect(); LOGGER.warn("channel is close,open a new one in the queue"); } return channel; } }