Java tutorial
/* * #%L * Janus * %% * Copyright (C) 2014 KIXEYE, Inc * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package com.kixeye.janus.client.http.async; import java.io.Closeable; import java.io.IOException; import java.net.URI; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.nio.client.HttpAsyncClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.kixeye.janus.Janus; import com.kixeye.janus.ServerStats; import com.kixeye.janus.client.exception.NoServerAvailableException; import com.kixeye.janus.client.exception.RetriesExceededException; import com.kixeye.janus.client.http.HttpRequest; import com.kixeye.janus.client.http.HttpResponse; import com.kixeye.relax.util.UrlUtils; /** * An Asynchronous HTTP client which uses Janus for service discovery. * * @author ebahtijaragic@kixeye.com */ public class AsyncHttpClient implements Closeable { private static final Logger logger = LoggerFactory.getLogger(AsyncHttpClient.class); private final Janus janus; private final int numRetries; private final CloseableHttpAsyncClient httpClient; private final ExecutorService executor; /** * Creates an async http client. * * @param janus reference to Janus * @param numRetries number of retry attempts that should be made in the event of http request error. requests that fail to complete * are considered errors (ie. IOExceptions) and are eligible for retry. Requests that receive a response (regardless of http status) are * considered successful and are not eligible for retry. */ public AsyncHttpClient(Janus janus, int numRetries) { Preconditions.checkNotNull(janus, "'janus' is required but was null"); Preconditions.checkArgument(numRetries >= 0, "'numRetries' must be >= 0"); this.janus = janus; this.numRetries = numRetries; this.executor = Executors.newCachedThreadPool(); this.httpClient = HttpAsyncClients.createDefault(); this.httpClient.start(); } /** * Closes this client. * * @throws IOException */ public void close() throws IOException { httpClient.close(); } /** * Executes the given request. * * @param request the HttpRequest to execute * @param path the path to send the request to. the path variables should be enclosed with "{}" Ex. /stores/{storeId}/items/{itemId} * @param urlVariables variables that will be substituted into the given path. Order of the given urlVariables is significant. For example, * variables 5, 10 will be substituted into /stores/{storeId}/items/{itemId} with a result of /stores/5/items/10. * * @return ListenableFuture clients can register a listener to be notified when the http request has been completed. * @throws IOException */ public ListenableFuture<HttpResponse> execute(HttpRequest request, String path, Object... urlVariables) throws IOException { SettableFuture<HttpResponse> response = SettableFuture.create(); executor.submit( new ExecuteTask(response, request, path, urlVariables, janus, httpClient, executor, numRetries)); return response; } private static class ExecuteTask implements Runnable { private final Janus janus; private final HttpAsyncClient httpClient; private final ExecutorService executor; private final SettableFuture<HttpResponse> response; private final HttpRequest request; private final String url; private final Object[] urlVariables; private final Runnable self; private final int maxRetryCount; private int retryCount; /** * @param responseFuture the response future * @param request the http request * @param maxRetryCount maximum number of retries * @param path the http request path * @param urlVariables path substitution variables */ public ExecuteTask(SettableFuture<HttpResponse> responseFuture, HttpRequest request, String path, Object[] urlVariables, Janus janus, HttpAsyncClient httpClient, ExecutorService executor, int maxRetryCount) { this.response = responseFuture; this.request = request; this.url = path; this.urlVariables = urlVariables.clone(); this.janus = janus; this.httpClient = httpClient; this.executor = executor; this.maxRetryCount = maxRetryCount; this.self = this; this.retryCount = 0; } public void run() { try { final ServerStats server = janus.getServer(); if (server == null) { throw new NoServerAvailableException(janus.getServiceName()); } URI formattedUrl = new URI(UrlUtils.expand( server.getServerInstance().getUrl() + (url.startsWith("/") ? "" : "/") + url, urlVariables)); // map to the required type HttpUriRequest httpClientRequest = null; switch (request.getMethod()) { case DELETE: httpClientRequest = new HttpDelete(formattedUrl); break; case GET: httpClientRequest = new HttpGet(formattedUrl); break; case HEAD: httpClientRequest = new HttpHead(formattedUrl); break; case OPTIONS: httpClientRequest = new HttpOptions(formattedUrl); break; case PATCH: httpClientRequest = new HttpPatch(formattedUrl); break; case POST: httpClientRequest = new HttpPost(formattedUrl); break; case PUT: httpClientRequest = new HttpPut(formattedUrl); break; case TRACE: httpClientRequest = new HttpTrace(formattedUrl); break; default: break; } // set the body if (httpClientRequest instanceof HttpEntityEnclosingRequest) { ((HttpEntityEnclosingRequest) httpClientRequest) .setEntity(new InputStreamEntity(request.getBody())); } // set the headers for (String headerName : request.getHeaderNames()) { for (String headerValue : request.getHeader(headerName)) { httpClientRequest.addHeader(headerName, headerValue); } } // execute! FutureCallback<org.apache.http.HttpResponse> completeListener = new FutureCallback<org.apache.http.HttpResponse>() { private long startTime = System.currentTimeMillis(); @Override public void failed(Exception ex) { if (retryCount >= maxRetryCount) { response.setException( new RetriesExceededException(janus.getServiceName(), maxRetryCount)); } else { logger.warn("Error while processing request, will retry", ex); retryCount++; executor.submit(self); } server.incrementErrors(); server.decrementOpenRequests(); } @Override public void completed(org.apache.http.HttpResponse result) { long latency = System.currentTimeMillis() - startTime; Map<String, Collection<String>> headers = new HashMap<>(); for (Header header : result.getAllHeaders()) { Collection<String> headerValues = headers.get(header.getName()); if (headerValues == null) { headerValues = Lists.newArrayList(); headers.put(header.getName(), headerValues); } headerValues.add(header.getValue()); } try { response.set(new HttpResponse(result.getStatusLine().getStatusCode(), headers, result.getEntity().getContent())); } catch (Exception e) { logger.debug("Passing exception to response", e); response.setException(e); } server.decrementOpenRequests(); server.recordLatency(latency); } @Override public void cancelled() { response.set(null); } }; httpClient.execute(httpClientRequest, completeListener); server.incrementSentMessages(); server.incrementOpenRequests(); } catch (Exception e) { logger.debug("Passing exception to response", e); response.setException(e); } } } }