<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 |