Implementation

Особенности имплементации NettyHttpServer

Класс: NettyHttpServer
	/*Класс NettyHttpServer создает сервер. */

// Конфигурирование сервера.
        ServerBootstrap bootstrap = new ServerBootstrap(
                new NioServerSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));

        // Включить TCP_NODELAY и обрабатывать конвейерные запросы без задержки.
        bootstrap.setOption("child.tcpNoDelay", true);

        //Задать pipeline factory event.
        bootstrap.setPipelineFactory(new NettyHttpServerPipelineFactory(sslCtx));
        
        // Привязать и начать принимать входящие соединения
        bootstrap.bind(new InetSocketAddress(PORT));

Класс: NettyHttpServerHandler

/*Класс NettyHttpServerHandler выполняет обработку всех входящих сообщений к серверу. */

    //Буфер, который хранит содержимое "простого" ответа ответа  
    
    private final StringBuilder buf = new StringBuilder();
	 
    
 //Обработка поступаюзих сообщений(запросив) метод messageReceived

	@Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws InterruptedException, SQLException, UnknownHostException {
		request = this.request = (HttpRequest) e.getMessage();

		
//Получение ip адреса хоста
          String hostAddress = InetAddress.getLocalHost().getHostAddress().toString();
         System.out.println(hostAddress);
//Отладочная информация с выводом в консоль
          try {
			System.out.println("getHostAddress - " + InetAddress.getLocalHost().getHostAddress());
			System.out.println("getHostName - " + InetAddress.getLocalHost().getHostName());
			System.out.println("getLocalHost - " + InetAddress.getLocalHost().getLocalHost());
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		}
        
          //Подсчет полученных байт   
        received_bytes = request.toString().length();
        System.out.println("received_bytes = " + received_bytes);
  		
        

//Задать url для подключения к базе MySQL через JDBC
        String urll = "jdbc:mysql://localhost:3306/netty_status?user=root&password=core i7" ;
		
        //Установка свойств подключения JDBC
  		Properties properties = new Properties();
	  		properties.setProperty("user", "root");
	  		properties.setProperty("password", "core i7");
	  		properties.setProperty("characterEncoding", "UTF-8"); 
	  		properties.setProperty("useUnocode", "true");
  		
	  	//Созданние конекшенов к базе	
  		DBConnection connect = new DBConnection(urll, properties);
  		DBConnection connect1 = new DBConnection(urll, properties);
  		//Инициализация конекшенов
		connect.init();
		connect1.init();
		
		//Определение значения URI
		locate = request.getUri();
	          
                    
          //Если запрос /hello отдать Hello world! через 10 секунд
          if(locate.equals(hello) ) {
        	  Thread.sleep(10000L);
        //Подсчитать размер ответа
        	  sent_bytes = buf.append("Hello world!!!\r\n").length(); 
          }
          
    	//Обработка запроса
    	if (!readingChunks) {
            HttpRequest request = this.request = (HttpRequest) e.getMessage();
            
            if (is100ContinueExpected(request)) {
                send100Continue(e);
            }
            String status = "/status";
//Получение URI
            locate = request.getUri();

           //Если запрос /status - отдать страницу статуса
            if(locate.equals(status) ) { 
    
            	 HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK);
                 
                 ChannelBuffer content = StatusPage.getContent(request.toString());
           
                 res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
                 setContentLength(res, content.readableBytes());
                 sent_bytes = content.readableBytes();
                 System.out.println("readableBytes - " + content.readableBytes());
                 res.setContent(content);
                 sendHttpResponse(ctx, request, res);
    }
            
            //Разобрать запрос на параметры если таковы имеются 
            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
            Map<String, List<String>> params = queryStringDecoder.getParameters();
            if (!params.isEmpty()) {
                for (Entry<String, List<String>> p: params.entrySet()) {
                    String key = p.getKey();
                    String redirect = request.getUri();
                    String redirectPath = "/redirect";
                    String url = "url";
                    System.out.println("key = " + key + "redirect" + redirect );
                    List<String> vals = p.getValue();
                   	 for (String val : vals) {
        
HttpRequest message = this.request = (HttpRequest) e.getMessage();
//Если параметры redirect, url присутствуют то отредиректить на страницу
//указанную в параметрах(редирект происходит как на внутренние так и на внешние страницы)
                       if((redirect.equals(redirectPath)) || (key.equals(url))){
                        	 
                            HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK);
                           
                            ChannelBuffer content = RedirectPage.getContent(val);
                      
                         res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
                            setContentLength(res, content.readableBytes());
                            sent_bytes = content.readableBytes();
                         
                            res.setContent(content);
                            sendHttpResponse(ctx, request, res);
                        	
//Ведение таблицы переадресаций
                    	if(!locate.equals("/favicon.ico")){
connect1.updateQuery("INSERT INTO redirect(redirect_url) VALUES('" + locate +"')");
                    	}
	 
                        }
                    }
                }
                buf.append("\r\n");
            }
            System.out.println("sent_bytes = " + sent_bytes );
            
     		if(!locate.equals("/favicon.ico")){
      			String query = hostName + locate;
      			System.out.println(request.getUri());
      
      			
//Запись в главную базу netty_status и главную таблицу status всей статистики сервера
      			connect.updateQuery("INSERT INTO status(query,ip,uri,sent_bytes, received_bytes ) VALUES('" +hostName + locate +"','" + InetAddress.getLocalHost().getHostAddress().toString() +"','"+ request.getUri() +"','"+ sent_bytes +','"+ received_bytes + "')");
      			
      		}
      
    
    private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {

        //Генерация на страницу с ошибкой, если код ответа не 200

        if (res.getStatus().getCode() != 200) {
            res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
            setContentLength(res, res.getContent().readableBytes());
        }


        // Отправить ответ и закрыть соединение, если это необходимо
        ChannelFuture f = ctx.getChannel().write(res);
        if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }


// Написать ответ
private void writeResponse(MessageEvent e) {
        //Нужно ли закрыть соединение или нет
        boolean keepAlive = isKeepAlive(request);

        // Построить объект ответа
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        response.setContent(ChannelBuffers.copiedBuffer(buf.toString(), CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");

        if (keepAlive) {
            // Добавить 'Content-Length' заголовок только для соединения Keep-Alive.
            response.headers().set(CONTENT_LENGTH, response.getContent().readableBytes());
            
            // Добавить сохранить живой заголовок
            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }

        // Кодирование cookie.
        String cookieString = request.headers().get(COOKIE);
        if (cookieString != null) {
            CookieDecoder cookieDecoder = new CookieDecoder();
            Set<Cookie> cookies = cookieDecoder.decode(cookieString);
            if (!cookies.isEmpty()) {
                // Сброс cookies если это необходимо.cookies 
                CookieEncoder cookieEncoder = new CookieEncoder(true);
                for (Cookie cookie : cookies) {
                    cookieEncoder.addCookie(cookie);
                    response.headers().add(SET_COOKIE, cookieEncoder.encode());
                }
            }
        } else {
            // Браузер не отправляет cookies. Установить некоторые cookies.
            CookieEncoder cookieEncoder = new CookieEncoder(true);
            cookieEncoder.addCookie("key1", "value1");
            response.headers().add(SET_COOKIE, cookieEncoder.encode());
            cookieEncoder.addCookie("key2", "value2");
            response.headers().add(SET_COOKIE, cookieEncoder.encode());
        }

        // Написать ответ
        ChannelFuture future = e.getChannel().write(response);

        // Закрыть соединение без сохранения активности после операции записи 
        if (!keepAlive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }


Класс: NettyHttpServerPipelineFactory
/*Класс NettyHttpServerPipelineFactory реализует фабрику серверных pipeline. */
//Конструктор фабрики	
public NettyHttpClientPipelineFactory(SslContext sslCtx, String host, int port) {
        this.sslCtx = sslCtx;
        this.host = host;
        this.port = port;
    }

    public ChannelPipeline getPipeline() {
        //Создать реализацию pipeline по умолчанию
        ChannelPipeline pipeline = pipeline();

        // Включение HTTPS если это необходимо
        if (sslCtx != null) {
            pipeline.addLast("ssl", sslCtx.newHandler(host, port));
        }

        pipeline.addLast("codec", new HttpClientCodec());

        // Удалить следующую строку, если не нужно автоматической декомпрессии содержимого
        pipeline.addLast("inflater", new HttpContentDecompressor());

        //Установка клиентского обработчика 
        pipeline.addLast("handler", new NettyHttpClientHandler());
        return pipeline;

Класс:  NettyHttpClient

 /*Класс NettyHttpClient реализует простой HTTP-клиент, распечатывает содержимое HTTP ответа, и проверяет доступность NettyHttpServer. */


//Установить свойста URL
	static final String URL = System.getProperty("url", "http://127.0.0.1:8080/");

public static void main(String[] args) throws Exception {
        URI uri = new URI(URL);
        String scheme = uri.getScheme() == null? "http" : uri.getScheme();
        String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
        int port = uri.getPort();
        if (port == -1) {
            if ("http".equalsIgnoreCase(scheme)) {
                port = 80;
            } else if ("https".equalsIgnoreCase(scheme)) {
                port = 443;
            }
        }

        if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
            System.err.println("Only HTTP(S) is supported.");
            return;
        }


        // Конфигурирование клиента
        ClientBootstrap bootstrap = new ClientBootstrap(
                new NioClientSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));


        try {
            // Задать pipeline 
            bootstrap.setPipelineFactory(new NettyHttpClientPipelineFactory(sslCtx, host, port));

            // Начать попытку подключения
            ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));

            // Подождать пока попытка не завершится успешно
            Channel channel = future.sync().getChannel();

            // Подготовить HTTP запрос 
            HttpRequest request = new DefaultHttpRequest(
                    HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath());
            request.headers().set(HttpHeaders.Names.HOST, host);
            request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
            request.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);

            // Установить cookies.
            CookieEncoder httpCookieEncoder = new CookieEncoder(false);
            httpCookieEncoder.addCookie("my-cookie", "foo");
            httpCookieEncoder.addCookie("another-cookie", "bar");
            request.headers().set(HttpHeaders.Names.COOKIE, httpCookieEncoder.encode());

            // Отправить HTTP запрос 
            channel.write(request);

            // Подождать пока сервер получит запрос и получения  закрыть соединение
            channel.getCloseFuture().sync();
        } finally {
            // Остановка выполения для выхода
            bootstrap.releaseExternalResources();
        }
    }
}
Класс: NettyHttpClientPipelineFactory
/*Класс NettyHttpClientPipelineFactory реализует фабрику pipeline  */

	public ChannelPipeline getPipeline() {
        //Создать реализацию pipeline по умолчанию.
        ChannelPipeline pipeline = pipeline();

        // Включение HTTPS если это необходимо
        if (sslCtx != null) {
            pipeline.addLast("ssl", sslCtx.newHandler(host, port));
        }

        pipeline.addLast("codec", new HttpClientCodec());

        // Удалить следующую строку, если вы не хотите автоматической декомпрессии содержимого
        pipeline.addLast("inflater", new HttpContentDecompressor());

        //Установка клиентского обработчика 
        pipeline.addLast("handler", new NettyHttpClientHandler());
        return pipeline;
    }

Класс: RedirectPage
/*Класс RedirectPage реализует redirect */
    private static final String NEW = "\r\n";
    //Отдать страницу редиректа, передать адресс на который нужно перевести 
    public static ChannelBuffer getContent(String value) {
    	return ChannelBuffers.copiedBuffer(
                "<html><head><title>Redirect Page</title>" + NEW +
                "<script type=\"text/javascript\">" + NEW +
                " setTimeout( 'location= \"" + value + "\";', 3000);" + NEW +
                "</script>" + NEW + 
                "</head>" + NEW +
                "<body>" + NEW + 
                "<P> Redirect to " + value + NEW +
                "</body>" + NEW +
                "</html>" + NEW, CharsetUtil.US_ASCII);
    }
Класс: StatusPage
/*Класс StatusPage реализует вывод основной статистики*/
//URL подключения к базе MySQL 
	 private static final String url = "jdbc:mysql://localhost:3306/netty_status?user=root&password=core i7";

	 private static String allQueri = "";
	 private static String allUniqueQueri = "";
	 private static String table1 = "";
	 private static String table2 = "";
	 private static String table3 = "";
	
	 //Отдать страницу status
	 public static ChannelBuffer getContent(String content) throws SQLException {	 
	 
		 //Установка свойств подключения к базе
		Properties properties = new Properties();
	  		properties.setProperty("user", "root");
	  		properties.setProperty("password", "core i7");
	  		properties.setProperty("characterEncoding", "UTF-8"); 
	  		properties.setProperty("useUnocode", "true");
		
	  	//Создание подключения к базе 
	    DBConnection connect = new DBConnection(url,properties);
	    
	    //Инициализация подключения	
	    connect.init();
	    
	    //Получть количество всех запросов на сервер	
	    ResultSet result  = connect.query("SELECT COUNT('query') FROM status;");
	    
	    //Получть количество уникальных запросов на сервер	
	    ResultSet result1 = connect.query("SELECT COUNT(DISTINCT  query) FROM status;");
	    
	    //Получть Лог из 16 последних соединений
	    ResultSet result2 = connect.query("SELECT * FROM status GROUP BY date_time DESC LIMIT 16;");
	 
	    //ResultSet result2 = connect.query("SELECT query, COUNT( ip), ip,date_time FROM status;");
	    
	    //Cчетчик запросов на каждый IP
	    ResultSet result3 = connect.query("SELECT  ip, COUNT( ip), MAX(date_time) FROM status;");
	    
	    //Получить количество переадресаций по URL
	    ResultSet result4 = connect.query("SELECT query, COUNT(*) FROM status GROUP BY query HAVING COUNT(*);");
	    
	    //Почения значений и ResultSet и построение таблиц
	    	try {
			while(result.next()){
				allQueri =  result.getString(1);
			}
			while(result1.next()){
				allUniqueQueri = result1.getString(1);
			}
///----------------Таблица: Лог из 16 последних обработанных соединений--------------
				table1+="<table border='1' align = 'CENTER'>\n";
				table1+=  "<tr>	<td align = 'CENTER'><b>SRC_IP</b></td>	<td align = 'CENTER'><b>URI</b></td>	"
						+ "<td align = 'CENTER'><b>TIME</b></td>	<td align = 'CENTER'><b>Sent_Bytes</b></td>"
						+ "<td align = 'CENTER'><b>Received_Bytes</b></td></tr>" + "\n";
	while(result2.next()){
		table1	+=	"<tr>\n";
		table1	+=  	"<td>" + result2.getString("ip")+ "</td>" + "\n";
		table1	+=  	"<td>" + result2.getString("uri")+ "</td>" + "\n";
		table1	+=  	"<td>" + result2.getString("date_time")+ "</td>" + "\n";
					table1	+=  	"<td>" + result2.getString("sent_bytes")+ "</td>" + "\n";
					table1	+=  	"<td>" + result2.getString("received_bytes")+ "</td>" + "\n";
					table1	+="	</tr>\n";
			}				
		table1 += "</table>\n<br><hr><br>";
				
	///----------------Таблица: Cчетчик запросов на каждый IP--------------------
		table2+="<table border='1'  align = 'CENTER'>\n";
		table2+=  "<tr>	<td align = 'CENTER'><b>IP</b></td>	<td align = 'CENTER'><b>COUNT QUERY</b></td>	"+ "<td align = 'CENTER'><b>LAST DATE</b></td>\n";
		while(result3.next()){
			table2+="<tr>\n";
			table2+=  "<td>" + result3.getString(1)+ "</td>" + "\n";
			table2+=  "<td>" + result3.getString(2)+ "</td>" + "\n";
			table2+=  "<td>" + result3.getString(3) + "</td>" + "\n";
			table2+="</tr>\n";
		}				
	table2 += "</table>\n<br><br>";
	///----------------Таблица: Количество переадресаций по URL--------------------
		table3+="<table border='1'  align = 'CENTER'>\n";
		table3+=  "<tr>	<td align = 'CENTER'><b>URL</b></td>	<td align = 'CENTER'><b>COUNT REDIRECT</b></td></tr>\n";
		while(result4.next()){
			table3+="<tr>\n";
			table3+=  "<td>http://" + result4.getString(1)+ "</td>" + "\n";
			table3+=  "<td>" + result4.getString(2)+ "</td>" + "\n";
			table3+="</tr>\n";
		}				
		table3 += "</table>\n<br><br>";
	    	}catch(Exception e){}
        	
	    	//Отдать страницу status
	       return ChannelBuffers.copiedBuffer(
	       "<html><head><title>Status</title>" + NEW +
	        "</head>" + NEW +
	         "<body>" + NEW +
	          "<H1 > STATUS</H1>" + NEW +
	           "<p> Количество запросов: "+ allQueri+ "</p><hr>" + NEW +
	           "<p> Количество уникальных запросов: " + allUniqueQueri + "</p><hr><br/>" + NEW +
	            "<h3 align = 'CENTER'> Cчетчик запросов на каждый IP</h3>" + NEW +
	                ""+  table2 +NEW +
	            "<h3 align = 'CENTER'> Количество переадресаций по URL</h3>" + NEW  +"" + table3 +NEW +
	          "<h3 align = 'CENTER'>  Лог из 16 последних обработанных соединений</h3>" + NEW +"" + table1 + NEW +
	                "<h3 align = 'CENTER'> Данные о клиенте</h3>" + NEW +
	                "<table border = '1' align = 'CENTER'><tr><td><pre>" + content + "</pre></td></tr></table>"+ NEW +
	                "</body>" +NEW +
	                "</html>" + NEW + table_null(), CharsetUtil.UTF_8);
	    }
	 
	 	//Обнулить значения таблиц
	    private static String table_null() {
	    	table1 = "";
	    	table2 = "";
	    	table3 = "";
		return "";
	}

Класс: DBConnection
/*Класс DBConnection реализует логику работы с базой данных*/
private Properties properties = new Properties();
	//Экземпляр подключения
private  Connection mysqlConnection
	//Конструктов класса 
	public DBConnection(String host, String port,String nameDb,String userDb, String password){
		this.host = host;
		this.port = port;
		this.nameDb = nameDb;
		this.userDb = userDb;
		this.password = password;
		
	}
	//Конструктов класса с двумя параметрами
	public DBConnection(String url, Properties properties){
		this.url = url;
		this.properties = properties;
	}
	
		//Установить свойства соединения
	public void initProperties(){
		
		url = "jdbc:mysql://" + host + ":"+ port + "/" + nameDb + "?user=" + userDb + "&password="+ password;
		
		properties.setProperty("user", userDb);
		properties.setProperty("password", password);
		properties.setProperty("characterEncoding", "UTF-8"); 
		properties.setProperty("useUnocode", "true");
		
		System.out.println("URL:" + url);
	}
	
	//Соединение
	public void init(){
	if(mysqlConnection == null)
		try {
			Class.forName("com.mysql.jdbc.Driver");
			mysqlConnection = (Connection) DriverManager.getConnection(url);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}		
	}
	//Соединение закрытие соединения
	public void closeConnection(){
		try {
			mysqlConnection.close();
		} catch (Exception e) {
			
		}
	}
	//Запрос на выборку
	public ResultSet query(String query){
		ResultSet result = null;
		try {
			Statement st = mysqlConnection.createStatement();
			result = st.executeQuery(query);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return result;
	}



	//Запрос на обновление
	public void updateQuery(String query){
		try {
			Statement st = mysqlConnection.createStatement();
			st.executeUpdate(query);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

Screenshot

 
Рис. 1 Результат выполнение запроса /hello
 
Рис. 2 Результат выполнение запроса /redirect

 
Рис. 3  Результат выполнение запроса /status

 
Рис.4  Результат выполнение запроса /status

 
Рис. 5  Результат выполнение запроса /status

Список литературы
1.	CodeInsider http://knowlix.github.io/ .
2.	Netty API - http://netty.io/3.9/api/index.html .
3.	Getting Started  - http://docs.jboss.org/netty/3.2/guide/html/start.html.