package com.neverwinterdp.demandspike.client; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ConnectTimeoutException; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.QueryStringEncoder; import java.nio.channels.ClosedChannelException; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; import com.neverwinterdp.netty.http.client.AsyncHttpClient; import com.neverwinterdp.netty.http.client.ResponseHandler; public class DemandSpikeClient { private String path ; private AsyncHttpClient client ; private Map<String, Request> waitingRequests ; private int bufferSize ; private Monitor monitor = new Monitor(); public DemandSpikeClient(String host, int port, String path, int bufferSize, boolean connect) throws Exception { client = new AsyncHttpClient(host, port, new ResponseHandlerImpl(), connect) ; this.path = path ; this.bufferSize = bufferSize ; waitingRequests = new ConcurrentHashMap<String, Request>() ; } public boolean isConnected() { return client.isConnected() ; } public void setNotConnected() { client.setNotConnected(); } public boolean connect() throws Exception { return client.connect(); } public boolean connect(long timeout, long tryPeriod) throws Exception { return client.connect(timeout, tryPeriod); } public Request getWaitingRequest(String key) { return waitingRequests.get(key) ; } public Monitor getMonitor() { return this.monitor ; } public void sendGet(String key, Map<String, String> params, long timeout) throws Exception { Request request = new Request("GET", key, params, null) ; MethodMonitor methodMonitor = monitor.getMethodMonitor("GET"); methodMonitor.incrCount(); synchronized(waitingRequests) { if(waitingRequests.size() >= bufferSize) { waitingRequests.wait(timeout); if(waitingRequests.size() >= bufferSize) { methodMonitor.incrClientLimitTimeoutCount(); ; throw new TimeoutException("fail to send the message in " + timeout + "ms") ; } } try { QueryStringEncoder encoder = new QueryStringEncoder(path); if(params != null) { for(Map.Entry<String, String> entry : params.entrySet()) { encoder.addParam(entry.getKey(), entry.getValue()); } } ChannelFuture future = client.get(encoder.toString()); handleFuture(future, request) ; } catch(Exception ex) { methodMonitor.incrUnknownErrorCount(); throw ex ; } } } public void sendPost(String key, byte[] data, long timeout) throws Exception { Request request = new Request("POST", key, null, data) ; MethodMonitor methodMonitor = monitor.getMethodMonitor(request.getMethod()) ; methodMonitor.incrCount(); synchronized(waitingRequests) { if(waitingRequests.size() >= bufferSize) { waitingRequests.wait(timeout); if(waitingRequests.size() >= bufferSize) { methodMonitor.incrClientLimitTimeoutCount(); throw new TimeoutException("fail to send the message in " + timeout + "ms") ; } } try { ChannelFuture future = client.post(path, data); handleFuture(future, request) ; } catch(Exception ex) { methodMonitor.incrUnknownErrorCount(); throw ex ; } } } public Map<String, Request> getWaitingRequests() { return this.waitingRequests ; } /** * This method will wait and check periodically to make sure that all the request to get the response */ public void waitAndClose(long waitTime) throws InterruptedException { if(waitingRequests.size() > 0) { synchronized(waitingRequests) { long stopTime = System.currentTimeMillis() + waitTime ; while(stopTime > System.currentTimeMillis() && waitingRequests.size() > 0) { long timeToWait = stopTime - System.currentTimeMillis() ; waitingRequests.wait(timeToWait); } } } for(Request sel : waitingRequests.values()) { MethodMonitor mMonitor = monitor.getMethodMonitor(sel.getMethod()) ; mMonitor.incrTimeoutExceptionCount(); } close(); } public void close() { if(waitingRequests.size() > 0) { System.err.println("There are " + waitingRequests.size() + " messages waitting for ack") ; } client.close(); } private void handleFuture(ChannelFuture future, final Request request) { waitingRequests.put(request.getKey(), request) ; //Be aware, this operation complete is called when the client the delivered sucessfully to the remote server. //Not when the client get the ack or response future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { Throwable cause = future.cause() ; if(cause != null) { MethodMonitor mMonitor = monitor.getMethodMonitor(request.getMethod()) ; if(cause instanceof ClosedChannelException) { mMonitor.incrCloseChannelExceptionCount(); } else if(cause instanceof ConnectTimeoutException) { mMonitor.incConnectionTimeoutExceptionCount(); } waitingRequests.remove(request.getKey()) ; } } }); } /** * This method should be overrided, convert the return response data into a structure object, process and verify * the response and find the Request of this response according to the key. * @param data * @return */ public Request handleResponse(HttpResponse response, byte[] data) { return null ; } class ResponseHandlerImpl implements ResponseHandler { public void onResponse(HttpResponse response) { if(response instanceof HttpContent) { HttpContent content = (HttpContent) response; ByteBuf byteBuf = content.content() ; byte[] data = new byte[byteBuf.readableBytes()] ; byteBuf.readBytes(data) ; Request request = handleResponse(response, data) ; if(request == null) { //TODO: to fix this Iterator<Map.Entry<String, Request>> iterator = waitingRequests.entrySet().iterator() ; iterator.next(); iterator.remove(); } else { MethodMonitor mMonitor = monitor.getMethodMonitor(request.getMethod()) ; mMonitor.incrResponseCount(); synchronized(waitingRequests) { waitingRequests.remove(request.getKey()) ; waitingRequests.notify(); } } } } } static public class Request { private String method ; private String key; private Map<String, String> params ; private byte[] data ; private long sendTime ; public Request(String method, String key, Map<String, String> params, byte[] data) { this.method = method ; this.key = key ; this.params = params ; this.data = data ; this.sendTime = System.currentTimeMillis(); } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public Map<String, String> getParams() { return params; } public void setParams(Map<String, String> params) { this.params = params; } public byte[] getData() { return data; } public void setData(byte[] data) { this.data = data; } public long getSendTime() { return this.sendTime ; } public void setSendTime(long time) { this.sendTime = time ; } } }