HTTP 리 셋 서버

14228 단어 HTTP 리 셋 서버
프로그램의 기본 절 차 는 다음 과 같다.
HTTP重定向服务器_第1张图片
코드 조직 구 조 는 다음 과 같다.
HTTP重定向服务器_第2张图片
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 요청,응답 메시지 형식 참조
패 키 지 를 풀 때 요청 수신 자가 완전한 HTTP 요청 메시지 정 보 를 받 지 못 하도록 반복 적 으로 읽 습 니 다.
한 바이트,바이트 값 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
")); 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("
"); 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
" + "Content-Type:text/html\r
"; String body = "<html><SCRIPT type=text/javascript>" + "window.location.href=\"" + redirectUrl + "\";</script></html>"; head += "Content-length:"+body.getBytes().length+"\r
\r
"; 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 디 코딩 을 해 야 합 니 다.[DNS]Punycode 와 중국어 의 상호 전환 을 참조 하 십시오.
socket.setSoLinger(true,0)가 설정 되 어 있 기 때문에 socket.close()방법 을 호출 할 때 바 텀 socket 연결 이 즉시 닫 힙 니 다.이때 HTTP 응답 결과 가 모두 전송 되 지 않 았 을 수도 있 습 니 다.따라서 socket 연결 을 닫 기 전에 바 텀 socket 이 한동안 HTTP 응답 정 보 를 보 낼 수 있 도록 200 밀리초 동안 스 레 드 휴면 을 처리 합 니 다.
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

좋은 웹페이지 즐겨찾기