eu.fusepool.p3.webid.proxy.ProxyServlet.java Source code

Java tutorial

Introduction

Here is the source code for eu.fusepool.p3.webid.proxy.ProxyServlet.java

Source

/*
 * Copyright 2014 Bern University of Applied Sciences.
 *
 * 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 eu.fusepool.p3.webid.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.log.LogService;

/**
 * A servlet which proxies HTTP-Requests to another HTTP-Server.
 *
 * @author Pascal Mainini
 */
// refer to https://felix.apache.org/documentation/subprojects/apache-felix-http-service.html
@Component(service = Servlet.class, property = { "alias=/", "TargetBaseURI=http://localhost:8088" })
@SuppressWarnings("serial")
public class ProxyServlet extends HttpServlet {

    private static final String PROPERTY_TARGET_BASE_URI = "TargetBaseURI";

    private LogService log;
    private final CloseableHttpClient httpclient;
    private String targetBaseUri;

    //////////////////////////////////////// Constructors
    /**
     * Initializes the servlet by setting up the HTTP-client.
     */
    public ProxyServlet() {
        final HttpClientBuilder hcb = HttpClientBuilder.create();
        hcb.setRedirectStrategy(new NeverRedirectStrategy());
        httpclient = hcb.build();
        targetBaseUri = null;
    }

    //////////////////////////////////////// Interaction with the container
    /**
     * DS-binding for setting the log-service when it becomes available.
     *
     * @param service the log-service to set
     */
    @Reference(name = "log.service", service = LogService.class, unbind = "unsetLogService")
    protected void setLogService(LogService service) {
        this.log = service;
        log.log(LogService.LOG_INFO, "Obtained logservice!");
    }

    /**
     * DS-binding for removing the log-service when it's no longer available.
     *
     * @param service the log-service being removed
     */
    protected void unsetLogService(LogService service) {
        this.log = null;
    }

    /**
     * DS-binding run on activation, configures the service
     *
     * @param properties The configuration (or none) obtained from the container
     */
    @Activate
    public void activate(final Map<String, ?> properties) {
        configure(properties);
        log(LogService.LOG_INFO, "Service configured.");
    }

    /**
     * DS-binding called when configuration changes, updates internal config.
     *
     * @param properties The new configuration (or none) obtained from the
     * container.
     */
    @Modified
    void modified(final Map<String, ?> properties) {
        configure(properties);
        log(LogService.LOG_INFO, "Configuration modified.");
    }

    /**
     * DS-binding called when the service is deactivated.
     */
    @Deactivate
    public void deactivate() {
        configure(null);
        log(LogService.LOG_INFO, "Service deconfigured.");
    }

    //////////////////////////////////////// Service-Method
    /**
     * The service method from HttpServlet, performs handling of all
     * HTTP-requests independent of their method. Requests and responses within
     * the method can be distinguished by belonging to the "frontend" (i.e. the
     * client connecting to the proxy) or the "backend" (the server being
     * contacted on behalf of the client)
     *
     * @param frontendRequest Request coming in from the client
     * @param frontendResponse Response being returned to the client
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void service(final HttpServletRequest frontendRequest, final HttpServletResponse frontendResponse)
            throws ServletException, IOException {
        log(LogService.LOG_INFO,
                "Proxying request: " + frontendRequest.getRemoteAddr() + ":" + frontendRequest.getRemotePort()
                        + " (" + frontendRequest.getHeader("Host") + ") " + frontendRequest.getMethod() + " "
                        + frontendRequest.getRequestURI());

        if (targetBaseUri == null) {
            // FIXME return status page
            return;
        }

        //////////////////// Setup backend request
        final HttpEntityEnclosingRequestBase backendRequest = new HttpEntityEnclosingRequestBase() {
            @Override
            public String getMethod() {
                return frontendRequest.getMethod();
            }
        };
        try {
            backendRequest.setURI(new URL(targetBaseUri + frontendRequest.getRequestURI()).toURI());
        } catch (URISyntaxException ex) {
            throw new IOException(ex);
        }

        //////////////////// Copy headers to backend request
        final Enumeration<String> frontendHeaderNames = frontendRequest.getHeaderNames();
        while (frontendHeaderNames.hasMoreElements()) {
            final String headerName = frontendHeaderNames.nextElement();
            final Enumeration<String> headerValues = frontendRequest.getHeaders(headerName);
            while (headerValues.hasMoreElements()) {
                final String headerValue = headerValues.nextElement();
                if (!headerName.equalsIgnoreCase("Content-Length")) {
                    backendRequest.setHeader(headerName, headerValue);
                }
            }
        }

        //////////////////// Copy Entity - if any
        final byte[] inEntityBytes = IOUtils.toByteArray(frontendRequest.getInputStream());
        if (inEntityBytes.length > 0) {
            backendRequest.setEntity(new ByteArrayEntity(inEntityBytes));
        }

        //////////////////// Execute request to backend
        try (CloseableHttpResponse backendResponse = httpclient.execute(backendRequest)) {
            frontendResponse.setStatus(backendResponse.getStatusLine().getStatusCode());

            // Copy back headers
            final Header[] backendHeaders = backendResponse.getAllHeaders();
            final Set<String> backendHeaderNames = new HashSet<>(backendHeaders.length);
            for (Header header : backendHeaders) {
                if (backendHeaderNames.add(header.getName())) {
                    frontendResponse.setHeader(header.getName(), header.getValue());
                } else {
                    frontendResponse.addHeader(header.getName(), header.getValue());
                }
            }

            final ServletOutputStream outStream = frontendResponse.getOutputStream();

            // Copy back entity
            final HttpEntity entity = backendResponse.getEntity();
            if (entity != null) {
                try (InputStream inStream = entity.getContent()) {
                    IOUtils.copy(inStream, outStream);
                }
            }
            outStream.flush();
        }
    }

    //////////////////////////////////////// Helpers
    private void configure(Map<String, ?> properties) {
        if (properties == null) {
            targetBaseUri = null;
        } else {
            log(LogService.LOG_INFO, "Configuring service...");
            if (properties.containsKey(PROPERTY_TARGET_BASE_URI)) {
                targetBaseUri = (String) properties.get(PROPERTY_TARGET_BASE_URI);
                log(LogService.LOG_INFO, "Proxy enabled, target: " + targetBaseUri);
            } else {
                targetBaseUri = null;
            }
        }
    }

    private void log(int level, String message) {
        if (log != null) {
            log.log(level, message);
        }
    }

    private static class NeverRedirectStrategy implements RedirectStrategy {
        @Override
        public HttpUriRequest getRedirect(HttpRequest hr, HttpResponse hr1, org.apache.http.protocol.HttpContext hc)
                throws org.apache.http.ProtocolException {
            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        }

        @Override
        public boolean isRedirected(HttpRequest hr, HttpResponse hr1, org.apache.http.protocol.HttpContext hc)
                throws org.apache.http.ProtocolException {
            return false;
        }
    }
}