HTTP重定向服务器

程序基本流程如下:

代码组织结构如下:

HTTP重定向服务主线程:

package com.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;

import com.conf.Config;

public class HttpServer implements Runnable {
	private static ServerSocket server_socket = null; 
	private static ExecutorService pool;
	private static int requestNum = 0;
	
	private static Logger serverLog = Logger.getLogger("HttpServerLog");
	private static Logger requestNumLog = Logger.getLogger("RequestNumber");
	
	public void run() {
		startServer(Config.serverListenPort);
	}

	private void startServer(int port){
		try {
			pool = Executors.newFixedThreadPool(Config.threadPoolSize);
			server_socket = new ServerSocket(port,Config.serverQueueSize);
			serverLog.info("HTTP Server starts on port:" 
					+ server_socket.getLocalPort());
			while (true) {
				try {
					if(Config.curThreadsNum.get() >= Config.maxThreadsNum.get()){
						serverLog.info("HTTP Server sleep for 1 second!");
						Thread.sleep(1000);
						continue;
					}
				} catch (Exception e) {
					serverLog.error(e);
					continue;
				}
				serverLog.debug("Get client request!");
				Socket socket = server_socket.accept();
				serverLog.debug("Create socket successfully!");
				//socket.setReuseAddress(true);
				//某些HTTP客户端建立连接后不发送数据
				//如果这种连接过多,系统线程将被耗尽
				//所以必须设置连接超时时间
				socket.setSoTimeout(2*1000);
				socket.setSoLinger(true, 0);
				serverLog.debug("New connection:" + socket.getInetAddress() 
						+ ":" + socket.getPort());
				serverLog.info("Max:" + Config.maxThreadsNum 
						+ ";Cur:" + Config.curThreadsNum);
				requestNum++;
				if(requestNum > 10000){
					requestNumLog.info("10000 requests");
					requestNum = 0;
				}
				try {	
					DealThread dt = new DealThread(socket);
					serverLog.debug("Deal thread create successfully!");
					pool.execute(dt);
					Config.curThreadsNum.incrementAndGet();
				} catch (Exception e) {
					serverLog.error(e);
				}
			}
		} catch (IOException e) {
			serverLog.error(e);
		}
	}
	
	public static void main(String[] args){
		HttpServer hs = new HttpServer();
		Thread t = new Thread(hs);
		t.start();
	}
}

HTTP请求封装类:

HTTP请求报文格式和HTTP响应报文格式参照http://blog.csdn.net/a19881029/article/details/14002273

在解包时进行循环读取,以避免请求接收端没有接收到完整的HTTP请求报文信息

\n占一个字节,字节值为10,\r也占一个字节,字节值为13

package com.request;

import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;

import com.server.DealThread;

public class Request {
	private InputStream input;

	private String headerString = "";
	private String bodyString = "";
	
	public Request(Socket socket) throws Exception{
		this.input = socket.getInputStream();
		DealThread.threadLog.debug("Thread[" 
				+ Thread.currentThread().getId()
				+ "]get input stream");
	} 
	
	public void resolvePackage(Socket socket) throws Exception{
		DealThread.threadLog.debug("Thread[" 
				+ Thread.currentThread().getId() 
				+ "]analysis package begin");
        String line = null;  
        // HTTP request body length
        int contentLength = 0;
        // get HTTP request head   
        do {  
            line = readLine(input, 0);  
            if (line.startsWith("Content-Length")) {  
                contentLength = Integer.parseInt(line.split(":")[1].trim());  
            }  
            headerString += line;
            //如果遇到了一个单独的回车换行,则表示请求头结束   
        } while (!line.equals("\r\n"));
        if(contentLength != 0){
        	bodyString = readLine(input,contentLength);
        }
        DealThread.threadLog.debug("HTTP request head:" + headerString); 
        DealThread.threadLog.debug("HTTP request body:" + bodyString); 
        DealThread.threadLog.debug("Thread[" 
        		+ Thread.currentThread().getId() 
				+ "]analysis package end");
	}
	
    private String readLine(InputStream is, int contentLe) throws Exception {  
    	ArrayList<Byte> lineByteList = new ArrayList<Byte>();  
        byte[] readByte;  
        byte b;
        if (contentLe != 0 && contentLe > 0) {
        	readByte = new byte[contentLe];
        	int num = 0;
        	int totalnum = contentLe;
        	int realreadnum = 0;
        	while(num < totalnum){
        		realreadnum = is.read(readByte, num, totalnum-num);
        		if(realreadnum > 0){
        			num += realreadnum;
        		}else{
        			break;
        		}
        	}
        	return new String(readByte);
        } else { //读请求头
           do {  
                b = (byte)is.read();  
                lineByteList.add(Byte.valueOf(b));
            } while (b != 10);  
            byte[] tmpByteArr = new byte[lineByteList.size()];  
            for (int i = 0; i < lineByteList.size(); i++) {  
                tmpByteArr[i] = lineByteList.get(i).byteValue();  
            }  
            lineByteList.clear();  
      
            return new String(tmpByteArr);
        }  
    }    

	public String getHeader(String name) {
		if (name == null || name.equals(""))
			return null;
		name = name + ": ";
		try {
			String[] item = headerString.split("\n");
			String headerLine = null;
			for(int i=0;i<item.length;i++){
				headerLine = item[i];
				if (headerLine.indexOf(name) == 0) {
					return headerLine.substring(name.length());
				}
			}
		} catch (Exception e) {
			DealThread.threadLog.error(e);
		}
		return null;
	}
}

HTTP响应封装类:

package com.response;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

import com.server.DealThread;

public class Response {
	private OutputStream output;
	private String ip;

	public Response(Socket socket) throws Exception{
		this.output = socket.getOutputStream();
		DealThread.threadLog.debug("Thread[" 
				+ Thread.currentThread().getId()
				+ "]get output stream");
		ip = socket.getInetAddress().toString();
	}

	public void sendRedirect(String redirectUrl) {
		String head = "HTTP/1.1 200 OK\r\n"
				+ "Content-Type:text/html\r\n";
		String body = "<html><SCRIPT type=text/javascript>"
				+ "window.location.href=\"" + redirectUrl 
				+ "\";</script></html>";
		head += "Content-length:"+body.getBytes().length+"\r\n\r\n";
		try {
			output.write(head.getBytes());
			output.write(body.getBytes());
			output.flush();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("Thread[" + Thread.currentThread().getId()
					+ "]["+ip+"]Redirect Send Error:"+redirectUrl);
		}
	}
}

HTTP请求处理线程:

需要注意的是,这里并没有针对中文域名进行处理,也就是系统并不支持中文域名,如果需要支持中文域名,需要进行punycode转码,参见http://blog.csdn.net/a19881029/article/details/18262671

由于设置了socket.setSoLinger(true, 0),当调用socket.close()方法时,底层socket连接会立即关闭,此时HTTP响应结果有可能还未全部发送完毕,故在关闭socket连接前,处理线程休眠200毫秒以便底层socket有一段时间用来发送HTTP响应信息

package com.server;

import java.net.InetAddress;
import java.net.Socket;

import org.apache.log4j.Logger;

import com.conf.Config;
import com.request.Request;
import com.response.Response;

public class DealThread implements Runnable {
	private Socket socket;
	private Response response;
	private Request request;
	
	public static Logger threadLog = Logger.getLogger("ThreadLog"); 

	public DealThread(Socket socket) throws Exception {
		this.socket = socket;
		this.request = new Request(this.socket);
		this.response = new Response(this.socket);
	}
	
	public void run() {
		try {
			threadLog.debug("thread " 
					+ Thread.currentThread().getName() + " open");
			request.resolvePackage(socket);
			processRequest();
		} catch (Exception e) {
			threadLog.error(e);
		}finally{
			try {
				String identify = socket.getInetAddress() + ":" 
						+ socket.getLocalPort();
				Thread.sleep(200);
				socket.shutdownInput();
				socket.shutdownOutput();
				socket.close();
				if(socket.isClosed()){
					threadLog.debug("socket [" + identify + "] closed");
				}
			} catch (Exception e) {
				threadLog.error(e);
			}
			Config.curThreadsNum.decrementAndGet();
			threadLog.debug("[Thread " + Thread.currentThread().getId()
					+ "] closed");
		}
	}

	private void processRequest() throws Exception {
		//没有对中文域名进行转码
		String host = request.getHeader("Host");
		String user_agent = request.getHeader("User-Agent");	
		
		InetAddress netAddress = socket.getInetAddress();
		String address = netAddress.getHostAddress();
		threadLog.info("HOST:" + host + " User-Agent:" 
				+ user_agent + " IP:" + address);
		
		String redirectUrl = "http://www.baidu.com";
		response.sendRedirect(new String(redirectUrl.getBytes("GBK"),"ISO-8859-1"));
		threadLog.info("redirectUrl:"+redirectUrl);
	}
}

配置文件读取类:

HTTP请求的默认监听端口为80

package com.conf;

import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class Config {
	static{
		SAXReader reader = new SAXReader();
		Document document;
        try {
        	String filePath = "./conf/config.xml";
			document = reader.read(new File(filePath));
			Element root = document.getRootElement();
			int max_threads_num = Integer.valueOf(
					root.element("max_threads_num").getTextTrim()).intValue();
			maxThreadsNum = new AtomicInteger(max_threads_num);
			int cur_threads_num = Integer.valueOf(
					root.element("cur_threads_num").getTextTrim()).intValue();
			curThreadsNum = new AtomicInteger(cur_threads_num);
			serverListenPort = Integer.valueOf(
					root.element("server_listen_port").getTextTrim()).intValue();
			serverQueueSize = Integer.valueOf(
					root.element("server_queue_size").getTextTrim()).intValue();
			threadPoolSize = Integer.valueOf(
					root.element("thread_pool_size").getTextTrim()).intValue();
        } catch (DocumentException e) {
			e.printStackTrace();
			maxThreadsNum = new AtomicInteger(50);
			curThreadsNum = new AtomicInteger(0);
			serverListenPort = 80;
			serverQueueSize = 200;
			threadPoolSize = 60;
        }
	}
	
	public static AtomicInteger maxThreadsNum;
	public static AtomicInteger curThreadsNum;
	public static int serverListenPort;
	public static int serverQueueSize;
	public static int threadPoolSize;
}

日志配置文件log4j.properties:

HttpServerLog:记录HTTP重定向服务主线程的运行状况

ThreadLog:记录每一个HTTP请求处理线程的运行状况

RequestNum:记录系统负载情况

log4j.rootLogger=INFO,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{MM-dd HH:mm:ss}->%m%n

log4j.logger.HttpServerLog=debug,HttpServerLog
log4j.appender.HttpServerLog=org.apache.log4j.RollingFileAppender 
log4j.additivity.HttpServerLog=false
log4j.appender.HttpServerLog.File=./log/HttpServer.log
log4j.appender.HttpServerLog.MaxFileSize=10MB
log4j.appender.HttpServerLog.MaxBackupIndex=0
log4j.appender.HttpServerLog.layout=org.apache.log4j.PatternLayout
log4j.appender.HttpServerLog.layout.ConversionPattern=%d{MM-dd HH:mm:ss}->%m%n

log4j.logger.ThreadLog=debug,ThreadLog
log4j.appender.ThreadLog=org.apache.log4j.RollingFileAppender 
log4j.additivity.ThreadLog=false
log4j.appender.ThreadLog.File=./log/Thread.log
log4j.appender.ThreadLog.MaxFileSize=10MB
log4j.appender.ThreadLog.MaxBackupIndex=0
log4j.appender.ThreadLog.layout=org.apache.log4j.PatternLayout
log4j.appender.ThreadLog.layout.ConversionPattern=%d{MM-dd HH:mm:ss}[%t]->%m%n

log4j.logger.RequestNumber=info,RequestNumber
log4j.appender.RequestNumber=org.apache.log4j.RollingFileAppender 
log4j.additivity.RequestNumber=false
log4j.appender.RequestNumber.File=./log/RequestNum.log
log4j.appender.RequestNumber.MaxFileSize=1MB
log4j.appender.RequestNumber.MaxBackupIndex=0
log4j.appender.RequestNumber.layout=org.apache.log4j.PatternLayout
log4j.appender.RequestNumber.layout.ConversionPattern=%d{MM-dd HH:mm:ss}->%m%n

系统参数配置文件config.xml:

这些参数需要根据服务器配置、系统负载进行配置,以便程序达到最佳性能

<?xml version="1.0" encoding="UTF-8"?>
<server_config>
	<!-- 最大处理线程数 -->
	<max_threads_num>50</max_threads_num>
	<!-- 当前处理线程数 -->
	<cur_threads_num>0</cur_threads_num>
	<!-- 服务监听端口 -->
	<server_listen_port>80</server_listen_port>
	<!-- 服务请求接收队列长度 -->
	<server_queue_size>200</server_queue_size>
	<!-- 处理线程线程池大小 -->
	<thread_pool_size>60</thread_pool_size>
</server_config>

通过HttpServer类启动服务,此时在浏览器中输入http://localhost,页面最终将被重定向至http://www.baidu.com

 

posted @ 2014-04-16 16:16  心意合一  阅读(725)  评论(0编辑  收藏  举报