/*******************************************************************************
* Copyright (c) 2017 Alex Xu and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alex Xu - initial API and implementation
*******************************************************************************/
package org.eclipse.php.internal.server.core.builtin.debugger;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpServerConnection;
import org.apache.http.impl.DefaultBHttpServerConnection;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.http.protocol.UriHttpRequestHandlerMapper;
@SuppressWarnings("restriction")
public class HttpReverseProxyServer {
private static final String HTTP_CONN_KEEPALIVE = "http.proxy.conn-keepalive"; //$NON-NLS-1$
private static final String HTTP_IN_CONN = "HTTP_IN_CONN"; //$NON-NLS-1$
private RequestListenerThread fThread;
private IHttpRequestHandler fHandler;
public HttpReverseProxyServer(IHttpRequestHandler handler) {
fHandler = handler;
}
public void start(int port) throws Exception {
fThread = new RequestListenerThread(port);
fThread.setHttpRequestHandler(fHandler);
fThread.setDaemon(false);
fThread.start();
}
public void stop() {
try {
if (fThread != null) {
fThread.stopServer();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public interface ConnectionClosedHandler {
public void notifyConnectionClosed(HttpServerConnection connection);
}
public interface IHttpRequestHandler {
public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context)
throws HttpException, IOException;
public void close(HttpServerConnection connection) throws IOException;
}
static class ProxyHandler implements HttpRequestHandler, ConnectionClosedHandler {
private IHttpRequestHandler fHttpRequestHandler;
@Override
public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context)
throws HttpException, IOException {
if (fHttpRequestHandler != null) {
fHttpRequestHandler.handle(request, response, context);
}
}
public void setHttpRequestHandler(IHttpRequestHandler requestHandler) {
fHttpRequestHandler = requestHandler;
}
@Override
public void notifyConnectionClosed(HttpServerConnection connection) {
try {
if (fHttpRequestHandler != null) {
fHttpRequestHandler.close(connection);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
static class RequestListenerThread extends Thread {
private ServerSocket serversocket;
private HttpService httpService;
private ProxyHandler handler;
private boolean isRunning = false;
public RequestListenerThread(final int port) throws IOException {
setName("PHP Debugger Proxy Server"); //$NON-NLS-1$
this.serversocket = new ServerSocket(port);
final HttpProcessor inhttpproc = new ImmutableHttpProcessor(new HttpRequestInterceptor[] {
new RequestContent(), new RequestTargetHost(), new RequestConnControl(),
new RequestUserAgent("PHP Debugger Proxy Server/1.1"), new RequestExpectContinue(true) }); //$NON-NLS-1$
handler = new ProxyHandler();
final UriHttpRequestHandlerMapper reqistry = new UriHttpRequestHandlerMapper();
reqistry.register("*", handler); //$NON-NLS-1$
this.httpService = new HttpService(inhttpproc, reqistry);
}
public void setHttpRequestHandler(IHttpRequestHandler requestHandler) {
handler.setHttpRequestHandler(requestHandler);
}
@Override
public void run() {
isRunning = true;
while (isRunning) {
try {
final int bufsize = 512 * 1024;
// Set up incoming HTTP connection
final Socket insocket = this.serversocket.accept();
final DefaultBHttpServerConnection inconn = new DefaultBHttpServerConnection(bufsize);
inconn.bind(insocket);
// Start worker thread
final Thread t = new ProxyThread(this.httpService, inconn, handler);
t.setDaemon(true);
t.start();
} catch (final InterruptedIOException ex) {
break;
} catch (final IOException e) {
break;
}
}
}
public void stopServer() throws IOException {
if (serversocket != null) {
isRunning = false;
serversocket.close();
serversocket = null;
}
}
}
static class ProxyThread extends Thread {
private final HttpService httpservice;
private final HttpServerConnection inconn;
private final ProxyHandler handler;
public ProxyThread(final HttpService httpservice, final HttpServerConnection inconn,
final ProxyHandler handler) {
super();
this.httpservice = httpservice;
this.inconn = inconn;
this.handler = handler;
}
@Override
public void run() {
final HttpContext context = new BasicHttpContext(null);
try {
while (!Thread.interrupted()) {
if (!this.inconn.isOpen()) {
handler.notifyConnectionClosed(inconn);
break;
}
context.setAttribute(HTTP_IN_CONN, this.inconn);
this.httpservice.handleRequest(this.inconn, context);
context.setAttribute(HTTP_CONN_KEEPALIVE, true);
// final Boolean keepalive = (Boolean)
// context.getAttribute(HTTP_CONN_KEEPALIVE);
// if (!Boolean.TRUE.equals(keepalive)) {
// handler.notifyConnectionClosed(inconn);
// this.inconn.close();
// break;
// }
}
} catch (final ConnectionClosedException ex) {
} catch (final IOException ex) {
} catch (final HttpException ex) {
} finally {
handler.notifyConnectionClosed(inconn);
try {
this.inconn.shutdown();
} catch (final IOException ignore) {
}
}
}
}
}