HTTP 리 셋 서버
14228 단어 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 요청,응답 메시지 형식 참조
패 키 지 를 풀 때 요청 수신 자가 완전한 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