All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.smallmind.web.reverse.http1_1.ReverseProxyService Maven / Gradle / Ivy

/*
 * Copyright (c) 2007 through 2024 David Berkman
 *
 * This file is part of the SmallMind Code Project.
 *
 * The SmallMind Code Project is free software, you can redistribute
 * it and/or modify it under either, at your discretion...
 *
 * 1) The terms of GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * ...or...
 *
 * 2) The terms of the Apache License, Version 2.0.
 *
 * The SmallMind Code Project is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License or Apache License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * and the Apache License along with the SmallMind Code Project. If not, see
 *  or .
 *
 * Additional permission under the GNU Affero GPL version 3 section 7
 * ------------------------------------------------------------------
 * If you modify this Program, or any covered work, by linking or
 * combining it with other code, such other code is not for that reason
 * alone subject to any of the requirements of the GNU Affero GPL
 * version 3.
 */
package org.smallmind.web.reverse.http1_1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.smallmind.scribe.pen.LoggerManager;

public class ReverseProxyService {

  private final CountDownLatch terminationLatch = new CountDownLatch(1);
  private final AtomicBoolean closed = new AtomicBoolean(false);
  private final ServerSocketChannel serverSocketChannel;
  private final Selector selector;
  private final ProxyDictionary dictionary;
  private final ProxyExecutor proxyExecutor;
  private final Lock selectLock = new ReentrantLock(true);
  private final Lock loopLock = new ReentrantLock(true);
  private final ByteBuffer byteBuffer = ByteBuffer.allocate(8192);
  private final int connectTimeoutMillis;
  private EventLoop eventLoop;

  public ReverseProxyService (String host, int port, ProxyDictionary dictionary, int connectTimeoutMillis, int concurrencyLimit)
    throws IOException {

    this.dictionary = dictionary;
    this.connectTimeoutMillis = connectTimeoutMillis;

    proxyExecutor = new ProxyExecutor(concurrencyLimit);

    serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.configureBlocking(false);
    serverSocketChannel.socket().bind(new InetSocketAddress(host, port));

    serverSocketChannel.register(selector = Selector.open(), SelectionKey.OP_ACCEPT);

    startEventLoop();
  }

  private void startEventLoop () {

    Thread eventThread;

    eventThread = new Thread(eventLoop = new EventLoop());
    eventThread.setDaemon(true);
    eventThread.start();
  }

  public void destroy ()
    throws IOException, InterruptedException {

    eventLoop.stop();

    if (closed.compareAndSet(false, true)) {
      selector.close();
      serverSocketChannel.close();
      proxyExecutor.shutdown();
    }
  }

  public SelectionKey keyFor (SelectableChannel selectableChannel) {

    loopLock.lock();
    try {
      selectLock.lock();
      try {

        return selectableChannel.keyFor(selector);
      } finally {
        selectLock.unlock();
      }
    } finally {
      loopLock.unlock();
    }
  }

  public void execute (SocketChannel sourceChannel, Runnable runnable) {

    proxyExecutor.execute(sourceChannel, runnable);
  }

  public ProxyTarget lookup (HttpRequestFrame httpRequestFrame)
    throws ProtocolException {

    ProxyTarget target;

    if ((target = dictionary.lookup(httpRequestFrame)) == null) {
      throw new ProtocolException(CannedResponse.NOT_FOUND);
    }

    return target;
  }

  public void connectDestination (final SocketChannel sourceSocketChannel, final HttpRequestFrameReader httpRequestFrameReader, final ProxyTarget target) {

    execute(sourceSocketChannel, () -> {

      SocketChannel destinationChannel = null;

      try {
        destinationChannel = SocketChannel.open();
        destinationChannel.socket().connect(new InetSocketAddress(target.getHost(), target.getPort()), connectTimeoutMillis);
        destinationChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true).setOption(StandardSocketOptions.TCP_NODELAY, true).configureBlocking(false);

        loopLock.lock();
        try {
          selectLock.lock();
          try {
            destinationChannel.register(selector, SelectionKey.OP_READ, new HttpResponseFrameReader(ReverseProxyService.this, sourceSocketChannel, destinationChannel));
          } finally {
            selectLock.unlock();
          }
        } finally {
          loopLock.unlock();
        }

        httpRequestFrameReader.registerDestination(target, destinationChannel);
      } catch (IOException ioException) {
        try {
          if (destinationChannel != null) {
            destinationChannel.close();
          }
        } catch (IOException closeException) {
          LoggerManager.getLogger(ReverseProxyService.class).error(closeException);
        }

        httpRequestFrameReader.fail(CannedResponse.NOT_FOUND, null);
      }
    });
  }

  private class EventLoop implements Runnable {

    private Thread runnableThread;
    private boolean stopped = false;

    private void stop ()
      throws InterruptedException {

      stopped = true;

      if (runnableThread != null) {
        runnableThread.interrupt();
      }

      terminationLatch.await();
    }

    @Override
    public void run () {

      try {
        runnableThread = Thread.currentThread();

        while (!stopped) {
          try {

            int selectedKeyCount = 0;

            selectLock.lock();
            try {
              selectedKeyCount = selector.select(100);
            } catch (IOException ioException) {
              LoggerManager.getLogger(ReverseProxyService.class).error(ioException);
            } finally {
              selectLock.unlock();
            }

            if ((!stopped) && (selectedKeyCount > 0)) {

              Iterator selectionKeyIter = selector.selectedKeys().iterator();

              loopLock.lock();
              try {
                while (selectionKeyIter.hasNext()) {

                  final SelectionKey selectionKey = selectionKeyIter.next();

                  try {
                    if (selectionKey.isValid()) {
                      if (selectionKey.isAcceptable()) {

                        SocketChannel sourceChannel = null;

                        try {
                          sourceChannel = (SocketChannel)((ServerSocketChannel)selectionKey.channel()).accept().setOption(StandardSocketOptions.SO_KEEPALIVE, true).setOption(StandardSocketOptions.TCP_NODELAY, true).configureBlocking(false);
                          sourceChannel.register(selector, SelectionKey.OP_READ, new HttpRequestFrameReader(ReverseProxyService.this, sourceChannel, connectTimeoutMillis));
                        } catch (IOException ioException) {
                          try {
                            if (sourceChannel != null) {
                              sourceChannel.close();
                            }
                          } catch (IOException closeException) {
                            LoggerManager.getLogger(ReverseProxyService.class).error(closeException);
                          }
                        }
                      } else if (selectionKey.isReadable() && selectionKey.channel().isOpen()) {

                        int bytesRead = 0;

                        try {
                          byteBuffer.clear();
                          bytesRead = ((SocketChannel)selectionKey.channel()).read(byteBuffer);
                        } catch (IOException ioException) {
                          ((FrameReader)selectionKey.attachment()).fail(CannedResponse.BAD_REQUEST, (SocketChannel)selectionKey.channel());
                        }

                        if (bytesRead > 0) {
                          byteBuffer.flip();
                          ((FrameReader)selectionKey.attachment()).processInput(selectionKey, byteBuffer);
                        } else if (bytesRead < 0) {
                          ((FrameReader)selectionKey.attachment()).closeChannels();
                        }
                      }
                    } else {
                      selectionKey.cancel();
                    }
                  } finally {
                    selectionKeyIter.remove();
                  }
                }
              } finally {
                loopLock.unlock();
              }
            }
          } catch (Exception exception) {
            LoggerManager.getLogger(ReverseProxyService.class).error(exception);
          }
        }
      } finally {
        terminationLatch.countDown();
      }
    }
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy