Java tutorial
/* * 2012-3 Red Hat Inc. and/or its affiliates and other contributors. * * 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. */ package org.overlord.apiman.service.client.http; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.AbortableHttpRequest; import org.apache.http.client.utils.URIUtils; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.HeaderGroup; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.overlord.apiman.Request; import org.overlord.apiman.Response; import org.overlord.apiman.gateway.ServiceClient; import org.overlord.apiman.gateway.http.HTTPGatewayRequest; import org.overlord.apiman.gateway.http.HTTPGatewayResponse; /** * The HTTP based implementation of the service client. * * Based on the http servlet proxy implemented by David Smiley: * https://github.com/dsmiley/HTTP-Proxy-Servlet * */ public class HTTPServiceClient implements ServiceClient { private static final Logger LOG = Logger.getLogger(HTTPServiceClient.class.getName()); private static final String APIKEY = "apikey"; private HttpClient _proxyClient; /** * The default constructor. */ public HTTPServiceClient() { } /** * {@inheritDoc} */ @Override public boolean isSupported(Request request) { return (request.getServiceURI().startsWith("http:") || request.getServiceURI().startsWith("https:")); } /** * {@inheritDoc} */ public void init() { HttpParams hcParams = new BasicHttpParams(); //readConfigParam(hcParams, ClientPNames.HANDLE_REDIRECTS, Boolean.class); _proxyClient = new DefaultHttpClient(new PoolingClientConnectionManager(), hcParams); } /** * {@inheritDoc} */ @Override public Response process(Request request) throws Exception { String method = "GET"; if (request instanceof HTTPGatewayRequest) { method = ((HTTPGatewayRequest) request).getHTTPMethod(); } String proxyRequestUri = rewriteUrlFromRequest(request); HttpRequest proxyRequest; //spec: RFC 2616, sec 4.3: either of these two headers signal that there is a message body. if (request.getHeader(HttpHeaders.CONTENT_LENGTH) != null || request.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) { HttpEntityEnclosingRequest eProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri); java.io.InputStream is = new java.io.ByteArrayInputStream(request.getContent()); InputStreamEntity entity = new InputStreamEntity(is, request.getContent().length); is.close(); eProxyRequest.setEntity(entity); proxyRequest = eProxyRequest; } else { proxyRequest = new BasicHttpRequest(method, proxyRequestUri); } copyRequestHeaders(request, proxyRequest); try { // Execute the request if (LOG.isLoggable(Level.FINER)) { LOG.finer("proxy " + method + " uri: " + request.getSourceURI() + " -- " + proxyRequest.getRequestLine().getUri()); } HttpResponse proxyResponse = _proxyClient .execute(URIUtils.extractHost(new java.net.URI(request.getServiceURI())), proxyRequest); Response resp = new HTTPGatewayResponse(proxyResponse); return (resp); } catch (Exception e) { //abort request, according to best practice with HttpClient if (proxyRequest instanceof AbortableHttpRequest) { AbortableHttpRequest abortableHttpRequest = (AbortableHttpRequest) proxyRequest; abortableHttpRequest.abort(); } if (e instanceof RuntimeException) { throw (RuntimeException) e; } //noinspection ConstantConditions if (e instanceof IOException) { throw (IOException) e; } throw new RuntimeException(e); } } /** * {@inheritDoc} */ public void close() { if (_proxyClient != null) { _proxyClient.getConnectionManager().shutdown(); } } /** These are the "hop-by-hop" headers that should not be copied. * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html * I use an HttpClient HeaderGroup class instead of Set<String> because this * approach does case insensitive lookup faster. */ protected static final HeaderGroup hopByHopHeaders; static { hopByHopHeaders = new HeaderGroup(); String[] headers = new String[] { "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade" }; for (String header : headers) { hopByHopHeaders.addHeader(new BasicHeader(header, null)); } } /** Copy request headers from the servlet client to the proxy request. */ protected void copyRequestHeaders(Request request, HttpRequest proxyRequest) throws Exception { for (int i = 0; i < request.getHeaders().size(); i++) { org.overlord.apiman.NameValuePair nvp = request.getHeaders().get(i); if (nvp.getName().equalsIgnoreCase(APIKEY)) { continue; } //Instead the content-length is effectively set via InputStreamEntity if (nvp.getName().equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { continue; } if (hopByHopHeaders.containsHeader(nvp.getName())) { continue; } // In case the proxy host is running multiple virtual servers, // rewrite the Host header to ensure that we get content from // the correct virtual server // TODO: Assumes string for now String headerValue = (String) nvp.getValue(); if (nvp.getName().equalsIgnoreCase(HttpHeaders.HOST)) { HttpHost host = URIUtils.extractHost(new java.net.URI(request.getServiceURI())); headerValue = host.getHostName(); if (host.getPort() != -1) { headerValue += ":" + host.getPort(); } } proxyRequest.addHeader(nvp.getName(), headerValue); } } /** Reads the request URI from {@code servletRequest} and rewrites it, considering {@link * #targetUri}. It's used to make the new request. */ protected String rewriteUrlFromRequest(Request request) { StringBuilder uri = new StringBuilder(500); uri.append(request.getServiceURI().toString()); // Append optional operation if (request.getOperation() != null) { if (uri.charAt(uri.length() - 1) != '/') { uri.append('/'); } uri.append(request.getOperation()); } // Handle the query string if (request.getParameters().size() > 0) { uri.append('?'); boolean f_first = true; for (int i = 0; i < request.getParameters().size(); i++) { org.overlord.apiman.NameValuePair nvp = request.getParameters().get(i); // Skip api key if (nvp.getName().equalsIgnoreCase(APIKEY)) { continue; } if (!f_first) { uri.append('&'); } uri.append(nvp.getName()); uri.append('='); uri.append(nvp.getValue().toString()); f_first = false; } } /* String queryString = request.getQueryString();//ex:(following '?'): name=value&foo=bar#fragment if (queryString != null && queryString.length() > 0) { uri.append('?'); int fragIdx = queryString.indexOf('#'); String queryNoFrag = (fragIdx < 0 ? queryString : queryString.substring(0,fragIdx)); uri.append(encodeUriQuery(queryNoFrag)); if (fragIdx >= 0) { uri.append('#'); uri.append(encodeUriQuery(queryString.substring(fragIdx + 1))); } } */ return uri.toString(); } /** * Encodes characters in the query or fragment part of the URI. * * <p>Unfortunately, an incoming URI sometimes has characters disallowed by the spec. HttpClient * insists that the outgoing proxied request has a valid URI because it uses Java's {@link URI}. * To be more forgiving, we must escape the problematic characters. See the URI class for the * spec. * * @param in example: name=value&foo=bar#fragment */ /* protected static CharSequence encodeUriQuery(CharSequence in) { //Note that I can't simply use URI.java to encode because it will escape pre-existing escaped things. StringBuilder outBuf = null; Formatter formatter = null; for(int i = 0; i < in.length(); i++) { char c = in.charAt(i); boolean escape = true; if (c < 128) { if (asciiQueryChars.get((int)c)) { escape = false; } } else if (!Character.isISOControl(c) && !Character.isSpaceChar(c)) {//not-ascii escape = false; } if (!escape) { if (outBuf != null) { outBuf.append(c); } } else { //escape if (outBuf == null) { outBuf = new StringBuilder(in.length() + 5*3); outBuf.append(in,0,i); formatter = new Formatter(outBuf); } //leading %, 0 padded, width 2, capital hex formatter.format("%%%02X",(int)c);//TODO } } if (formatter != null) { formatter.close(); } return outBuf != null ? outBuf : in; } protected static final BitSet asciiQueryChars; static { char[] c_unreserved = "_-!.~'()*".toCharArray();//plus alphanum char[] c_punct = ",;:$&+=".toCharArray(); char[] c_reserved = "?/[]@".toCharArray();//plus punct asciiQueryChars = new BitSet(128); for(char c = 'a'; c <= 'z'; c++) asciiQueryChars.set((int)c); for(char c = 'A'; c <= 'Z'; c++) asciiQueryChars.set((int)c); for(char c = '0'; c <= '9'; c++) asciiQueryChars.set((int)c); for(char c : c_unreserved) asciiQueryChars.set((int)c); for(char c : c_punct) asciiQueryChars.set((int)c); for(char c : c_reserved) asciiQueryChars.set((int)c); asciiQueryChars.set((int)'%');//leave existing percent escapes in place } */ }