HTTPMetadataProvider

/*
 * Licensed to the University Corporation for Advanced Internet Development,
 * Inc. (UCAID) under one or more contributor license agreements.  See the
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID 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.
 */

package org.opensaml.saml2.metadata.provider;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Timer;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**使用HTTP GET提取元数据的元数据提供程序。 缓存元数据直到满足以下条件之一:超过了元数据内的最小cacheDuration。超过了元数据内最早的有效直到时间。超过了最大的缓存持续时间。在确定缓存到期数据之前对元数据进行了过滤。 这允许过滤器删除可能影响缓存持续时间但此提供程序的用户不关心的XMLObject。 如果此提供程序的任何属性发生更改,则调用者有责任通过initialize()重新初始化。
 * A metadata provider that pulls metadata using an HTTP GET. Metadata is cached until one of these criteria is met:
 * <ul>
 * <li>The smallest cacheDuration within the metadata is exceeded</li>
 * <li>The earliest validUntil time within the metadata is exceeded</li>
 * <li>The maximum cache duration is exceeded</li>
 * </ul>
 *
 * Metadata is filtered prior to determining the cache expiration data. This allows a filter to remove XMLObjects that
 * may effect the cache duration but for which the user of this provider does not care about.
 *
 * It is the responsibility of the caller to re-initialize, via {@link #initialize()}, if any properties of this
 * provider are changed.
 */
public class HTTPMetadataProvider extends AbstractReloadingMetadataProvider {

    /** Class logger. */
    private final Logger log = LoggerFactory.getLogger(HTTPMetadataProvider.class);

    /** HTTP Client used to pull the metadata. */
    private HttpClient httpClient;

    /** URL to the Metadata. */
    private URI metadataURI;

    /** 当获取当前缓存的元数据时提供的ETag。The ETag provided when the currently cached metadata was fetched. */
    private String cachedMetadataETag;

    /** The Last-Modified information provided when the currently cached metadata was fetched. */
    private String cachedMetadataLastModified;

    /** URL scope that requires authentication. */
    private AuthScope authScope;

    /**
     * Constructor.
     *
     * @param metadataURL the URL to fetch the metadata
     * @param requestTimeout the time, in milliseconds, to wait for the metadata server to respond
     *
     * @throws MetadataProviderException thrown if the URL is not a valid URL or the metadata can not be retrieved from
     *             the URL
     */
    @Deprecated
    public HTTPMetadataProvider(String metadataURL, int requestTimeout) throws MetadataProviderException {
        super();
        try {
            metadataURI = new URI(metadataURL);
        } catch (URISyntaxException e) {
            throw new MetadataProviderException("Illegal URL syntax", e);
        }

        HttpClientParams clientParams = new HttpClientParams();
        clientParams.setSoTimeout(requestTimeout);
        httpClient = new HttpClient(clientParams);
        httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(requestTimeout);
        authScope = new AuthScope(metadataURI.getHost(), metadataURI.getPort());

    }

    /**
     * Constructor.
     *
     * @param client HTTP client used to pull in remote metadata
     * @param backgroundTaskTimer timer used to schedule background metadata refresh tasks
     * @param metadataURL URL to the remove remote metadata
     *
     * @throws MetadataProviderException thrown if the HTTP client is null or the metadata URL provided is invalid
     */
    public HTTPMetadataProvider(Timer backgroundTaskTimer, HttpClient client, String metadataURL)
            throws MetadataProviderException {
        super(backgroundTaskTimer);

        if (client == null) {
            throw new MetadataProviderException("HTTP client may not be null");
        }
        httpClient = client;

        try {
            metadataURI = new URI(metadataURL);
        } catch (URISyntaxException e) {
            throw new MetadataProviderException("Illegal URL syntax", e);
        }

        authScope = new AuthScope(metadataURI.getHost(), metadataURI.getPort());
    }

    /**
     * Gets the URL to fetch the metadata.
     *
     * @return the URL to fetch the metadata
     */
    public String getMetadataURI() {
        return metadataURI.toASCIIString();
    }

    /**
     * Sets the username and password used to access the metadata URL. To disable BASIC authentication set the username
     * and password to null;
     *
     * @param username the username
     * @param password the password
     */
    public void setBasicCredentials(String username, String password) {
        if (username == null && password == null) {
            httpClient.getState().setCredentials(null, null);
        } else {
            UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
            httpClient.getState().setCredentials(authScope, credentials);
        }
    }

    /**
     * Gets the length of time in milliseconds to wait for the server to respond.
     *
     * @return length of time in milliseconds to wait for the server to respond
     */
    public int getRequestTimeout() {
        return httpClient.getParams().getSoTimeout();
    }

    /**
     * Sets the socket factory used to create sockets to the HTTP server.
     *
     * @see <a href="http://jakarta.apache.org/commons/httpclient/sslguide.html">HTTPClient SSL guide</a>
     *
     * @param newSocketFactory the socket factory used to produce sockets used to connect to the server
     *
     * @deprecated set this information on HTTP client used by provider
     */
    public void setSocketFactory(ProtocolSocketFactory newSocketFactory) {
        log.debug("Using the custom socket factory {} to connect to the HTTP server", newSocketFactory.getClass()
                .getName());
        Protocol protocol = new Protocol(metadataURI.getScheme(), newSocketFactory, metadataURI.getPort());
        httpClient.getHostConfiguration().setHost(metadataURI.getHost(), metadataURI.getPort(), protocol);
    }

    /**
     * Gets the maximum amount of time, in seconds, metadata will be cached for.
     *
     * @return maximum amount of time, in seconds, metadata will be cached for
     *
     * @deprecated use {@link #getMaxRefreshDelay()} instead
     */
    public int getMaxCacheDuration() {
        return (int) getMaxRefreshDelay();
    }

    /**
     * Sets the maximum amount of time, in seconds, metadata will be cached for.
     *
     * @param newDuration maximum amount of time, in seconds, metadata will be cached for
     *
     * @deprecated use {@link #setMaxRefreshDelay(long)} instead
     */
    public void setMaxCacheDuration(int newDuration) {
        setMaxRefreshDelay(newDuration * 1000);
    }

    /**
     * Gets whether cached metadata should be discarded if it expires and can not be refreshed.
     *
     * @return whether cached metadata should be discarded if it expires and can not be refreshed.
     *
     * @deprecated use {@link #requireValidMetadata()} instead
     */
    public boolean maintainExpiredMetadata() {
        return !requireValidMetadata();
    }

    /**
     * Sets whether cached metadata should be discarded if it expires and can not be refreshed.
     *
     * @param maintain whether cached metadata should be discarded if it expires and can not be refreshed.
     *
     * @deprecated use {@link #setRequireValidMetadata(boolean)} instead
     */
    public void setMaintainExpiredMetadata(boolean maintain) {
        setRequireValidMetadata(!maintain);
    }

    /** {@inheritDoc} */
    public synchronized void destroy() {
        httpClient = null;
        metadataURI = null;
        cachedMetadataETag = null;
        cachedMetadataLastModified = null;
        authScope = null;

        super.destroy();
    }

    /** {@inheritDoc} */
    protected String getMetadataIdentifier() {
        return metadataURI.toString();
    }

    /**
     * Gets the metadata document from the remote server.
     *
     * @return the metadata from remote server, or null if the metadata document has not changed since the last
     *         retrieval
     *
     * @throws MetadataProviderException thrown if there is a problem retrieving the metadata from the remote server
     */
    protected byte[] fetchMetadata() throws MetadataProviderException {
        GetMethod getMethod = buildGetMethod();

        try {
            log.debug("Attempting to fetch metadata document from '{}'", metadataURI);
            httpClient.executeMethod(getMethod);
            int httpStatus = getMethod.getStatusCode();

            if (httpStatus == HttpStatus.SC_NOT_MODIFIED) {
                log.debug("Metadata document from '{}' has not changed since last retrieval", getMetadataURI());
                return null;
            }

            if (getMethod.getStatusCode() != HttpStatus.SC_OK) {
                String errMsg = "Non-ok status code " + getMethod.getStatusCode()
                        + " returned from remote metadata source " + metadataURI;
                log.error(errMsg);
                throw new MetadataProviderException(errMsg);
            }

            processConditionalRetrievalHeaders(getMethod);

            byte[] rawMetadata = getMetadataBytesFromResponse(getMethod);
            log.debug("Successfully fetched {}bytes of metadata from {}", rawMetadata.length, getMetadataURI());

            return rawMetadata;
        } catch (IOException e) {
            String errMsg = "Error retrieving metadata from " + metadataURI;
            log.error(errMsg, e);
            throw new MetadataProviderException(errMsg, e);
        }finally{
            getMethod.releaseConnection();
        }
    }

    /**
     * Builds the HTTP GET method used to fetch the metadata. The returned method advertises support for GZIP and
     * deflate compression, enables conditional GETs if the cached metadata came with either an ETag or Last-Modified
     * information, and sets up basic authentication if such is configured.
     *
     * @return the constructed GET method
     */
    protected GetMethod buildGetMethod() {
        GetMethod getMethod = new GetMethod(getMetadataURI());
        getMethod.addRequestHeader("Connection", "close");

        getMethod.setRequestHeader("Accept-Encoding", "gzip,deflate");
        if (cachedMetadataETag != null) {
            getMethod.setRequestHeader("If-None-Match", cachedMetadataETag);
        }
        if (cachedMetadataLastModified != null) {
            getMethod.setRequestHeader("If-Modified-Since", cachedMetadataLastModified);
        }

        if (httpClient.getState().getCredentials(authScope) != null) {
            log.debug("Using BASIC authentication when retrieving metadata from '{}", metadataURI);
            getMethod.setDoAuthentication(true);
        }

        return getMethod;
    }

    /**
     * Records the ETag and Last-Modified headers, from the response, if they are present.
     *
     * @param getMethod GetMethod containing a valid HTTP response
     */
    protected void processConditionalRetrievalHeaders(GetMethod getMethod) {
        Header httpHeader = getMethod.getResponseHeader("ETag");
        if (httpHeader != null) {
            cachedMetadataETag = httpHeader.getValue();
        }

        httpHeader = getMethod.getResponseHeader("Last-Modified");
        if (httpHeader != null) {
            cachedMetadataLastModified = httpHeader.getValue();
        }
    }

    /**
     * Extracts the raw metadata bytes from the response taking in to account possible deflate and GZip compression.
     *
     * @param getMethod GetMethod containing a valid HTTP response
     *
     * @return the raw metadata bytes
     *
     * @throws MetadataProviderException thrown if there is a problem getting the raw metadata bytes from the response
     */
    protected byte[] getMetadataBytesFromResponse(GetMethod getMethod) throws MetadataProviderException {
        log.debug("Attempting to extract metadata from response to request for metadata from '{}'", getMetadataURI());
        try {
            InputStream ins = getMethod.getResponseBodyAsStream();

            Header httpHeader = getMethod.getResponseHeader("Content-Encoding");
            if (httpHeader != null) {
                String contentEncoding = httpHeader.getValue();
                if ("deflate".equalsIgnoreCase(contentEncoding)) {
                    log.debug("Metadata document from '{}' was deflate compressed, decompressing it", metadataURI);
                    ins = new InflaterInputStream(ins);
                }

                if ("gzip".equalsIgnoreCase(contentEncoding)) {
                    log.debug("Metadata document from '{}' was GZip compressed, decompressing it", metadataURI);
                    ins = new GZIPInputStream(ins);
                }
            }

            return inputstreamToByteArray(ins);
        } catch (IOException e) {
            log.error("Unable to read response", e);
            throw new MetadataProviderException("Unable to read response", e);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值