/* * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * */ package com.apigee.sdk.apm.http.impl.client.cache; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.protocol.HTTP; import com.apigee.sdk.apm.http.annotation.Immutable; import com.apigee.sdk.apm.http.client.cache.HeaderConstants; import com.apigee.sdk.apm.http.client.cache.HttpCacheEntry; /** * @since 4.1 */ @Immutable class URIExtractor { /** * For a given {@link HttpHost} and {@link HttpRequest} get a URI from the * pair that I can use as an identifier KEY into my HttpCache * * @param host * The host for this request * @param req * the {@link HttpRequest} * @return String the extracted URI */ public String getURI(HttpHost host, HttpRequest req) { if (isRelativeRequest(req)) { return canonicalizeUri(String.format("%s%s", host.toString(), req .getRequestLine().getUri())); } return canonicalizeUri(req.getRequestLine().getUri()); } public String canonicalizeUri(String uri) { try { URL u = new URL(uri); String protocol = u.getProtocol().toLowerCase(); String hostname = u.getHost().toLowerCase(); int port = canonicalizePort(u.getPort(), protocol); String path = canonicalizePath(u.getPath()); if ("".equals(path)) path = "/"; String query = u.getQuery(); String file = (query != null) ? (path + "?" + query) : path; URL out = new URL(protocol, hostname, port, file); return out.toString(); } catch (MalformedURLException e) { return uri; } } private String canonicalizePath(String path) { try { String decoded = URLDecoder.decode(path, "UTF-8"); return (new URI(decoded)).getPath(); } catch (UnsupportedEncodingException e) { } catch (URISyntaxException e) { } return path; } private int canonicalizePort(int port, String protocol) { if (port == -1 && "http".equalsIgnoreCase(protocol)) { return 80; } else if (port == -1 && "https".equalsIgnoreCase(protocol)) { return 443; } return port; } private boolean isRelativeRequest(HttpRequest req) { String requestUri = req.getRequestLine().getUri(); return ("*".equals(requestUri) || requestUri.startsWith("/")); } protected String getFullHeaderValue(Header[] headers) { if (headers == null) return ""; StringBuilder buf = new StringBuilder(""); boolean first = true; for (Header hdr : headers) { if (!first) { buf.append(", "); } buf.append(hdr.getValue().trim()); first = false; } return buf.toString(); } /** * For a given {@link HttpHost} and {@link HttpRequest} if the request has a * VARY header - I need to get an additional URI from the pair of host and * request so that I can also store the variant into my HttpCache. * * @param host * The host for this request * @param req * the {@link HttpRequest} * @param entry * the parent entry used to track the varients * @return String the extracted variant URI */ public String getVariantURI(HttpHost host, HttpRequest req, HttpCacheEntry entry) { Header[] varyHdrs = entry.getHeaders(HeaderConstants.VARY); if (varyHdrs == null || varyHdrs.length == 0) { return getURI(host, req); } List<String> variantHeaderNames = new ArrayList<String>(); for (Header varyHdr : varyHdrs) { for (HeaderElement elt : varyHdr.getElements()) { variantHeaderNames.add(elt.getName()); } } Collections.sort(variantHeaderNames); try { StringBuilder buf = new StringBuilder("{"); boolean first = true; for (String headerName : variantHeaderNames) { if (!first) { buf.append("&"); } buf.append(URLEncoder.encode(headerName, HTTP.UTF_8)); buf.append("="); buf.append(URLEncoder.encode( getFullHeaderValue(req.getHeaders(headerName)), HTTP.UTF_8)); first = false; } buf.append("}"); buf.append(getURI(host, req)); return buf.toString(); } catch (UnsupportedEncodingException uee) { throw new RuntimeException("couldn't encode to UTF-8", uee); } } }