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

koncept.http.server.nio2.ComposableHttpNIO2Server Maven / Gradle / Ivy

package koncept.http.server.nio2;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;

import koncept.http.server.Code;
import koncept.http.server.ComposableHttpServer;
import koncept.io.LineStreamer;
import koncept.io.StreamingSocketAcceptor;
import koncept.io.StreamingSocketConnection;
import koncept.nio.StreamedByteChannel;

public class ComposableHttpNIO2Server extends ComposableHttpServer {
	
	private KeepAlive keepAlive;
	
	public ComposableHttpNIO2Server() {
		keepAlive = new KeepAlive();
	}
	
	@Override
	public void start() {
		super.start();
		executor.submit(keepAlive);
	}
	
	@Override
	public StreamingSocketAcceptor openSocket(InetSocketAddress addr, int backlog) throws IOException {
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		ssChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
		ssChannel.bind(addr, backlog);
		ssChannel.configureBlocking(false);
		return new ServerSocketChannelAcceptor(ssChannel);
	}
	
	@Override
	public void keepAlive(StreamingSocketConnection connection) {
		try {
			keepAlive.add((StreamingSocketConnection)connection);
		} catch (IOException e) {
			//just don't add it (!!) if an excepton occurs
			e.printStackTrace();
		}
	}
	
	@Override
	public int keptAlive() {
		return keepAlive.keepAlives.size();
	}
	
	public static class ServerSocketChannelAcceptor implements StreamingSocketAcceptor {
		private final ServerSocketChannel ssChannel;
		public ServerSocketChannelAcceptor(ServerSocketChannel ssChannel) {
			this.ssChannel = ssChannel;
		}
		
		@Override
		public StreamingSocketConnection accept() throws SocketClosedException,
				IOException {
			SocketChannel channel = ssChannel.accept();
			if (channel == null) return null;
			return new SocketChannelConnection(channel);
		}
		
		@Override
		public void close() throws IOException {
			ssChannel.close();
		}
		
		@Override
		public ServerSocketChannel underlying() {
			return ssChannel;
		}
	}
	
	public static class SocketChannelConnection implements StreamingSocketConnection {
		private final SocketChannel channel;
		private final StreamedByteChannel streams;
		public SocketChannelConnection(SocketChannel channel) {
			this.channel = channel;
			streams = new StreamedByteChannel(channel);
		}
			
		public SocketChannel underlying() {
			return channel;
		}
		
		@Override
		public InetSocketAddress localAddress() {
			try {
				return (InetSocketAddress)channel.getLocalAddress();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
		
		@Override
		public InetSocketAddress remoteAddress() {
			try {
				return (InetSocketAddress)channel.getRemoteAddress();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
		
		@Override
		public void close() throws IOException {
			channel.close();
		}
		
		@Override
		public InputStream in() throws IOException {
			return streams.getIn();
		}
		
		@Override
		public OutputStream out() throws IOException {
			return streams.getOut();
		}
	}

	class KeepAlive implements Runnable {
		
		Collection keepAlives;
		
		public KeepAlive() {
			keepAlives = new CopyOnWriteArraySet<>();
		}
		
		public void add(StreamingSocketConnection channel) throws IOException {
			keepAlives.add(new KeepAliveDetails(channel));
		}
		
		@Override
		public void run() {
			Collection toClose = new ArrayList<>();
			Collection handled = new ArrayList<>();
			for(KeepAliveDetails details: keepAlives) {
				try {
					String requestLine = details.tryReadLine();
					if(requestLine != null) {
						reSubmit(details.channel, requestLine);
						handled.add(details);
					} else if (details.validTill < System.currentTimeMillis()) {
						toClose.add(details);
					}
				} catch (IOException e) {
					e.printStackTrace();
					toClose.add(details);
				}
			}

			Collection toRemove = new ArrayList<>();
			toRemove.addAll(toClose);
			toRemove.addAll(handled);
			if (!toClose.isEmpty()) {
				String newLine = "\r\n".intern();
				int rCode = 408;
				for(KeepAliveDetails details: toClose) try {
	//				http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408
					PrintStream p = new PrintStream(details.channel.out());
					p.print("HTTP/1.1 " + rCode + Code.msg(rCode));
					p.print(newLine);
					p.print("Connection: close");
					p.print(newLine);
					p.flush();
					details.channel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				if (!stopRequested.get()) //suppress the exception if the server had been stopped
					throw new RuntimeException(e);
			}
			keepAlives.removeAll(toRemove);
			
			if (!stopRequested.get())
				executor.execute(this);
		}
	}
	
	class KeepAliveDetails {
		final long validTill;
		final StreamingSocketConnection channel;
		final LineStreamer lineStreamer;
		public KeepAliveDetails(StreamingSocketConnection channel) throws IOException {
			this.channel = channel;
			validTill = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1);
			lineStreamer = new LineStreamer(channel.in());
		}
		
		public String tryReadLine() throws IOException {
			return lineStreamer.readLine(0);
		}
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy