org.apache.shindig.gadgets.servlet.ProxyHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.shindig.gadgets.servlet.ProxyHandler.java

Source

/*
 * 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.
 */
package org.apache.shindig.gadgets.servlet;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
import org.apache.shindig.gadgets.http.RequestPipeline;
import org.apache.shindig.gadgets.rewrite.ResponseRewriterRegistry;
import org.apache.shindig.gadgets.rewrite.RewritingException;
import org.apache.shindig.gadgets.uri.ProxyUriManager;
import org.apache.shindig.gadgets.uri.UriUtils;
import org.apache.shindig.gadgets.uri.UriUtils.DisallowedHeaders;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * Handles open proxy requests.
 */
@Singleton
public class ProxyHandler {
    // TODO: parameterize these.
    static final Integer LONG_LIVED_REFRESH = (365 * 24 * 60 * 60); // 1 year
    static final Integer DEFAULT_REFRESH = (60 * 60); // 1 hour

    private final RequestPipeline requestPipeline;
    private final ResponseRewriterRegistry contentRewriterRegistry;
    protected final boolean remapInternalServerError;

    @Inject
    public ProxyHandler(RequestPipeline requestPipeline, ResponseRewriterRegistry contentRewriterRegistry,
            @Named("shindig.proxy.remapInternalServerError") Boolean remapInternalServerError) {
        this.requestPipeline = requestPipeline;
        this.contentRewriterRegistry = contentRewriterRegistry;
        this.remapInternalServerError = remapInternalServerError;
    }

    /**
     * Generate a remote content request based on the parameters sent from the client.
     */
    private HttpRequest buildHttpRequest(ProxyUriManager.ProxyUri uriCtx, Uri tgt) throws GadgetException {
        ServletUtil.validateUrl(tgt);
        HttpRequest req = uriCtx.makeHttpRequest(tgt);
        req.setRewriteMimeType(uriCtx.getRewriteMimeType());
        return req;
    }

    public HttpResponse fetch(ProxyUriManager.ProxyUri proxyUri) throws IOException, GadgetException {
        HttpRequest rcr = buildHttpRequest(proxyUri, proxyUri.getResource());
        if (rcr == null) {
            throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, "No url parameter in request",
                    HttpResponse.SC_BAD_REQUEST);
        }

        HttpResponse results = requestPipeline.execute(rcr);

        if (results.isError()) {
            // Error: try the fallback. Particularly useful for proxied images.
            Uri fallbackUri = proxyUri.getFallbackUri();
            if (fallbackUri != null) {
                HttpRequest fallbackRcr = buildHttpRequest(proxyUri, fallbackUri);
                results = requestPipeline.execute(fallbackRcr);
            }
        }

        if (contentRewriterRegistry != null) {
            try {
                results = contentRewriterRegistry.rewriteHttpResponse(rcr, results);
            } catch (RewritingException e) {
                // Throw exception if the RETURN_ORIGINAL_CONTENT_ON_ERROR param is not
                // set to "true" or the error is irrecoverable from.
                if (!proxyUri.shouldReturnOrigOnErr() || !isRecoverable(results)) {
                    throw new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR, e, e.getHttpStatusCode());
                }
            }
        }

        HttpResponseBuilder response = new HttpResponseBuilder(results);
        response.clearAllHeaders();

        try {
            ServletUtil.setCachingHeaders(response,
                    proxyUri.translateStatusRefresh(LONG_LIVED_REFRESH, DEFAULT_REFRESH), false);
        } catch (GadgetException gex) {
            return ServletUtil.errorResponse(gex);
        }

        UriUtils.copyResponseHeadersAndStatusCode(results, response, remapInternalServerError, true,
                DisallowedHeaders.CACHING_DIRECTIVES, // Proxy sets its own caching headers.
                DisallowedHeaders.CLIENT_STATE_DIRECTIVES, // Overridden or irrelevant to proxy.
                DisallowedHeaders.OUTPUT_TRANSFER_DIRECTIVES);

        // Set Content-Type and Content-Disposition. Do this after copy results headers,
        // in order to prevent those from overwriting the correct values.
        setResponseContentHeaders(response, results);

        UriUtils.maybeRewriteContentType(rcr, response);

        // TODO: replace this with streaming APIs when ready
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        IOUtils.copy(results.getResponse(), baos);
        response.setResponse(baos.toByteArray());
        return response.create();
    }

    protected void setResponseContentHeaders(HttpResponseBuilder response, HttpResponse results) {
        // We're skipping the content disposition header for flash due to an issue with Flash player 10
        // This does make some sites a higher value phishing target, but this can be mitigated by
        // additional referer checks.
        if (!isFlash(response.getHeader("Content-Type"), results.getHeader("Content-Type"))) {
            response.setHeader("Content-Disposition", "attachment;filename=p.txt");
        }
        if (results.getHeader("Content-Type") == null) {
            response.setHeader("Content-Type", "application/octet-stream");
        }
    }

    private static final String FLASH_CONTENT_TYPE = "application/x-shockwave-flash";

    /**
     * Test for presence of flash
     *
     * @param responseContentType the Content-Type header from the HttpResponseBuilder
     * @param resultsContentType the Content-Type header from the HttpResponse
     * @return true if either content type matches that of Flash
     */
    private boolean isFlash(String responseContentType, String resultsContentType) {
        return StringUtils.startsWithIgnoreCase(responseContentType, FLASH_CONTENT_TYPE)
                || StringUtils.startsWithIgnoreCase(resultsContentType, FLASH_CONTENT_TYPE);
    }

    /**
     * Returns true in case the error encountered while rewriting the content
     * is recoverable. The rationale behind it is that errors should be thrown
     * only in case of serious grave errors (defined to be un recoverable).
     * It should always be preferred to handle errors and return the original
     * content at least.
     *
     * @param results The result of rewriting.
     * @return True if the error is recoverable, false otherwise.
     */
    public boolean isRecoverable(HttpResponse results) {
        return !(StringUtils.isEmpty(results.getResponseAsString()) && results.getHeaders() == null);
    }
}