Site Search :
Standard Enterprise XML Methodology Pattern Setting Tunning Other
Article Contributors
GuestBook
Javapattern Maven
XSourceGen Dev
JetSpeed Test
JLook Image
jLook Family Site


Making the Http WebServer(Java로 web server를 구현하는 방법)
 
Network, Stream I/O, Multi-Thread Programming 이해
Server Application 구조, Thread Pooling, Logging, Dynamic Configuration 기능 이해 ( 2003/02/28 ) 332
Written by specular - 전홍성
3 of 3
 
<font face='굴림'>4. HttpHandler and HandlerPool(Thread Pooling)  
     HttpWebServer에서 client connection후 Handler thread를 start하지 않고,
     HandlerPool의 execute(Socket sck) method를 call했죠. HandlerPool 객체는 
     HttpHandler라는 Thread객체의 pooling이 구현되어 있습니다.
  

  Thread pooling에 대한 필요성을 위해서 언급했고, 구현 방법은
  HandlerPool의 source보면 되겠죠. 
package com.specular.http;

import java.util.*;
import java.net.*;
import com.specular.util.PropManager;
import com.specular.util.LogManager;

/*
*	HandlerPool.java - Thread Pooling
*	2001.05.17
* 	written by Jeon HongSeong
*/
public class HandlerPool 
{
private Vector pool;
//	Singleton
private static HandlerPool instance;
private ThreadGroup tGroup;

private static int HANDLER=5;
	
public HandlerPool() 
{
	pool = new Vector(1,3);
	tGroup = new ThreadGroup("HttWebServerHandler");
	init();
}

public static HandlerPool getInstance()
{
	//	Double checked Locking
	if(instance==null) {
		synchronized(HandlerPool.class) {
			if(instance==null) {
				instance=new HandlerPool();
			}
		}
	}
	return instance;
}

// 	initialize the pooling
private void init() {
	PropManager propMgr=PropManager.getInstance();
	try {
		HANDLER=Integer.parseInt(propMgr.getProperty("handler","5"));
	} catch(NumberFormatException e) {
		LogManager.log(e,"at the HandlerPool.init() method."+
			propMgr.getProperty("handler"));
		LogManager.log("The 'HANDLER' property is setted by 5.");	
	}
	
	for(int i=0;i<HANDLER;i++) 
	{
		HttpHandler newHandler=new HttpHandler();
		new Thread(tGroup,newHandler,"handler"+i).start();
		pool.addElement(newHandler);
	}
	LogManager.log("HandlerPool is initialized. - "+pool.size());
	//tGroup.list();
}

//	process the client request( notify a thread. )
public synchronized void execute(Socket sck) {
	if(pool.isEmpty()) {
		HttpHandler newHandler=new HttpHandler();
		newHandler.setSocket(sck);
		new Thread(tGroup,newHandler,"New Handler").start();
	} else {
		HttpHandler handler=(HttpHandler)pool.remove(pool.size()-1);
		handler.setSocket(sck);
	}	
}

public synchronized void release(HttpHandler handler) 
{
	if(pool.size()>=HANDLER) {
		LogManager.log("Too many threads, exit this thread.["+
			Thread.currentThread().getName()+"]");
		handler.stop();	
	} else {
		pool.addElement(handler);
	}
	//tGroup.list();
}	
}
▶ HandlerPool.java



위의 ThreadPool의 source만 봐서는 실재 pooling의 원리를 이해 하지는 못하겠죠. 
실제 thread의 wait, notify는 HttpHandler의 setSocket(), run() method에 구현되어 있습니다.
그래서, client request시에 setSocket() method에서 notify()를 시키고, notify된 thread는 
일어나 request를 처리하고, 다시 wait() 하게 되는 것입니다. 
그리고, HttpHandler는 processReqeust() method를 통해 실재 HttpRequest를 처리합니다. 
package com.specular.http;

import java.io.*;
import java.net.*;
import com.specular.util.LogManager;
import com.specular.util.PropManager;

/*
*	HttpHandler.java - Client의 reqeust를 처리하는 thread
*	2001.05.17
* 	written by Jeon HongSeong
*/
public class HttpHandler implements Runnable,HttpConstants 
{
private Socket sck;
private static int BUFF_SIZE=1024*2;
private byte[] buff;

private static boolean SHUTTDOWN;
private static final byte[] EOL={(byte)'\r',(byte)'\n'};
private static final byte GET=(byte)0;
private static final byte HEAD=(byte)1;
private static final String[] methodStr={"GET","HEAD"};

public HttpHandler() 
{
	String buffer=PropManager.getInstance().getProperty("buffer.size","2048");
	try {
		BUFF_SIZE=Integer.parseInt(buffer);	
	} catch(NumberFormatException e) {
		LogManager.log(e,"at the HttpHandler constructor.");
		LogManager.log("The Response buffer.size is setted 2048K");
	}
	buff = new byte[BUFF_SIZE];			
}

// 	client request시 call됨. ==> notify a thread
public synchronized void setSocket(Socket sck) 
{
	this.sck = sck;
	notify();	
}
	
public synchronized void run() 
{
	SHUTTDOWN=true;
	while(SHUTTDOWN) 
	{
		if(sck==null) 
		{
			try {
				wait();
			} catch(InterruptedException e) {
				LogManager.log(e,"at the HttpHandler.run() method.");
				continue;	
			}					
		}
		try {
			processRequest();
		} catch(IOException e) {
			LogManager.log(e,"at the HttpHandler.run() method.");
			LogManager.log("For processing the request, It is occured a Exception.");
		}
		sck=null;
		//	clear the buffer
		for(int i=0;i<BUFF_SIZE;i++)	buff[i]=0;
	
		HandlerPool pool = HandlerPool.getInstance(); 
		pool.release(this);
	}
		
}

protected void processRequest() 
	throws IOException 
{
	InputStream in = sck.getInputStream();
	OutputStream out = sck.getOutputStream();
	BufferedOutputStream bos = new BufferedOutputStream(out);
	PrintStream ps = new PrintStream(bos);
	PropManager propMgr = PropManager.getInstance();
	//get timeout
	String timeout = propMgr.getProperty("timeout","5000");
	
	sck.setSoTimeout(Integer.parseInt(timeout));
	sck.setTcpNoDelay(true);
			
	int reqLength=0,r=0;
	try {
		/* 	HTTP GET/HEAD만 지원,
		 *	request message 첫 라인을 read.
		 */
outer:		while(reqLength<BUFF_SIZE) 
	{
		r = in.read(buff,reqLength,BUFF_SIZE-reqLength);
		if(r==-1) return;
		
		int i=reqLength;
		reqLength+=r;
		for(;i<reqLength;i++) 
		{
			if(buff[i]==(byte)'\n'||buff[i]==(byte)'\r')
				break outer;	
		}					
	}
	
	byte reqMethod;
	int index=0;
	if(buff[0]==(byte)'G'&&
	   buff[1]==(byte)'E'&&
	   buff[2]==(byte)'T'&&
	   buff[3]==(byte)' ') {
		reqMethod = GET;
		index=4;   	
	} else if(buff[0]=='H'&&
			  buff[1]=='E'&&
			  buff[2]=='A'&&
			  buff[3]=='D'&&
			  buff[4]==' ') {
		reqMethod=HEAD;
		index=5;			  	
	} else {
		//send 405 : Bad method type
		String[] msg = {"unsupported method type:",new String(buff,0,5)};
		sendHeader(ps,HTTP_BAD_METHOD,msg[0]);
		sendError(ps,HTTP_BAD_METHOD,msg);
		return;	
	}
	
	/*	get request file name
	 */
	int i=0;
	for(i=index;i<reqLength;i++) {
		if(buff[i]==(byte)' ') {
			break;	
		}
	}
	
	String fname = new String(buff,index,i-index);
	fname=fname.replace('/',File.separatorChar);
	if(fname.startsWith(File.separator)) {
		fname=fname.substring(1);	
	}
	String docRoot = 
	  propMgr.getProperty("DocumentRoot",System.getProperty("user.dir"));
	File target = new File(docRoot,fname);
	
	//target resource : DocumentRoot일 경우
	if(target.isDirectory()) {
		File welcome = new File(target,"index.html");
		if(welcome.exists()) {
			target=welcome;	
		}	
	} 
	if( !target.exists() && !fname.equals("") ) {
		// 404 : NotFound 
		sendHeader(ps,HTTP_NOT_FOUND,"");
		return;
	}
	LogManager.log("From "+sck.getInetAddress().getHostAddress()+": "+
			methodStr[reqMethod]+" "+target.getAbsolutePath());
	
	switch(reqMethod) 
	{
	case GET : if( target.isDirectory() ) 
		   {
			   sendHeader(ps, HTTP_OK,"OK",target);
			   sendDirList(ps,target);
		   } else if(target.isFile()) {
			   sendHeader(ps, HTTP_OK,"OK",target);
			   sendFile(ps,target);
		   }
		break;
	case HEAD : 
		
		break;
	}
	
	LogManager.log("From "+sck.getInetAddress().getHostAddress()+": "+
			methodStr[reqMethod]+" "+target.getAbsolutePath()+"-->"+HTTP_OK);
} finally {
	if(ps!=null) {
		ps.flush();
		ps.close();	
	}
	if(bos!=null) bos.close();
	if(in!=null) in.close();
	if(out!=null) out.close();
	if(sck!=null) sck.close();
}
	
}

protected void sendHeader(PrintStream ps,int resCode,String msg) 
	throws IOException 
{		
	ps.print("HTTP/1.0 "+resCode+" "+msg);
	ps.write(EOL);
	ps.print("Date: "+new java.util.Date());
	ps.write(EOL);
	ps.print("Server: HttpWebServer 1.0");
	ps.write(EOL);
}

protected void sendHeader(PrintStream ps, int resCode, String msg, File target) 
	throws IOException 
{
	sendHeader(ps,resCode,msg);
	if(target.isDirectory()) {
		ps.print("Content-type: text/html");
		ps.write(EOL);	
	} else {
		ps.print("Content-length: "+target.length());
		ps.write(EOL);
		ps.print("Last Modified: "+new java.util.Date(target.lastModified()));
		ps.write(EOL);
		String fName=target.getName();
		int idx=fName.lastIndexOf('.');
		String mime=null;
		if(idx>0) mime = (String)MimeDB.get(fName.substring(idx));
		if(mime==null) mime="unknown/unknown";
		ps.print("Content-type: "+mime);
		ps.write(EOL);
	}	
}

protected void sendError(PrintStream ps, int resCode, String[] msg) 
	throws IOException 
{
	ps.print("Content-type: text/html");
	ps.write(EOL);
	ps.write(EOL);
	ps.print(resCode + " : "+msg[0]);
	for(int i=1;i<msg.length;i++) {
		ps.write(EOL);
		ps.print(msg[i]);	
	}
}

protected void sendFile(PrintStream ps, File target) 
	throws IOException 
{		
	ps.write(EOL);
	InputStream is = new FileInputStream(target.getAbsolutePath());

	try {
		int n;
		while ((n = is.read(buff)) > 0) {
			ps.write(buff, 0, n);
		}
	} finally {
		is.close();
	}
}
protected void sendDirList(PrintStream ps,File dir) 
	throws IOException 
{
	PropManager propMgr = PropManager.getInstance();
	String docRoot = propMgr.getProperty("DocumentRoot",System.getProperty("user.dir"));
	String path = dir.getPath();
	String viewPath=path.substring(docRoot.length()).replace(File.separatorChar,'/');

	ps.println("<TITLE>Directory listing</TITLE><P>\n");
	ps.println("<body>");
	ps.println("<A HREF=\"..\">Parent Directory</A><BR> \n");
	ps.println("<TABLE BORDER=\"0\">");
	ps.println("<TR><TD><u>File Name</u></TD>");
	ps.println("<TD><u>Size</u></TD>");
	ps.println("<TD><u>Last Modified</u></TD></TR>");
	File[] list = dir.listFiles();
	for (int i = 0; list != null && i < list.length; i++) {
	ps.println("<TR><TD width=40%>");
	if (list[i].isDirectory()) {
		ps.println("<li><A HREF=\""+viewPath+"/"+list[i].getName()+"/\">");
		ps.println("<font size=-1>"+list[i].getName()+
				"/</font></A><BR>");
	} else {
		ps.println("<li><A HREF=\""+viewPath+"/"+list[i].getName()+"\">");
		ps.println("<font size=-1>"+list[i].getName()+
				"</font></A><BR>");
	}
	ps.println("</TD><TD width=15%><font size=-1>"+
				list[i].length()+"</font></TD>");
	ps.println("<TD width=45%><font size=-1>"+
				new java.util.Date(list[i].lastModified()));
	ps.println("</font></TD></TR>");
	}
	ps.println("</TABLE>");
ps.println("<P><HR><BR><I>" + (new java.util.Date()) + 
				"</I></body>");
}

public void stop() 
{
	SHUTTDOWN=false;	
}	
}
▶ HttpHandler.java
▶ HttpConstants.java




이렇게 해서 Web Server에 대한 프로그램을 다 소개를 했네요.
Network Server programming 의 이해에 도움이 되었는지 모르겠네요.

▶ Source Code


2001.05.18 written by Jeon HongSeong 
 
1 2 3
References
 
This article Source
webserver.jar
Copyright ⓒ 2003 www.javapattern.info & www.jlook.com, an jLOOK co.,LTD