自己模拟的一个简单的tomcat

来源:互联网 发布:苹果mac输入法 编辑:程序博客网 时间:2024/05/16 15:47
  • servlet容器的职责
总的来说,一个全功能的servlet容器会为servlet的每个HTTP请求做下面的一些工作:
1,当第一次调用servlet的时候,加载该servlet类并调用servlet的init方法,只有一次,
2,对于每次请求,都要new出一个request请求和response相应实例,
3,调用servlet的service方法,同时传递ServletRequest和ServletResponse对象,
4,当servlet类被关闭的时候,调用servlet的destroy方法写在servlet类。


现在我自己模拟实现了一个最简单的tomcat,除了上面的第1点和第4点,基本的功能还是实现了,代码如下:

package linkin;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.InetAddress;import java.net.ServerSocket;import java.net.Socket;public class HttpServer1{/** * WEB_ROOT is the directory where our HTML and other files reside. For this * package, WEB_ROOT is the "webroot" directory under the working directory. * The working directory is the location in the file system from where the * java command was invoked. */// shutdown commandprivate static final String SHUTDOWN_COMMAND = "/SHUTDOWN";// the shutdown command receivedprivate boolean shutdown = false;public static void main(String[] args){HttpServer1 server = new HttpServer1();server.await();}public void await(){ServerSocket serverSocket = null;int port = 8080;try{serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));}catch (IOException e){e.printStackTrace();System.exit(1);}// Loop waiting for a requestwhile (!shutdown){Socket socket = null;InputStream input = null;OutputStream output = null;try{socket = serverSocket.accept();input = socket.getInputStream();output = socket.getOutputStream();// create Request object and parseRequest request = new Request(input);request.parse();// create Response objectResponse response = new Response(output);response.setRequest(request);// check if this is a request for a servlet or// a static resource// a request for a servlet begins with "/servlet/"if (request.getUri().startsWith("/servlet/")){ServletProcessor1 processor = new ServletProcessor1();processor.process(request, response);}else{StaticResourceProcessor processor = new StaticResourceProcessor();processor.process(request, response);}// Close the socketsocket.close();// check if the previous URI is a shutdown commandshutdown = request.getUri().equals(SHUTDOWN_COMMAND);}catch (Exception e){e.printStackTrace();System.exit(1);}}}}

package linkin;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java.util.Enumeration;import java.util.Locale;import java.util.Map;import javax.servlet.RequestDispatcher;import javax.servlet.ServletInputStream;import javax.servlet.ServletRequest;public class Request implements ServletRequest{private InputStream input;private String uri;public Request(InputStream input){this.input = input;}public String getUri(){return uri;}private String parseUri(String requestString){int index1, index2;index1 = requestString.indexOf(' ');if (index1 != -1){index2 = requestString.indexOf(' ', index1 + 1);if (index2 > index1)return requestString.substring(index1 + 1, index2);}return null;}public void parse(){// Read a set of characters from the socketStringBuffer request = new StringBuffer(2048);int i;byte[] buffer = new byte[2048];try{i = input.read(buffer);}catch (IOException e){e.printStackTrace();i = -1;}for (int j = 0; j < i; j++){request.append((char) buffer[j]);}System.out.print(request.toString());uri = parseUri(request.toString());}/* implementation of ServletRequest */public Object getAttribute(String attribute){return null;}public Enumeration getAttributeNames(){return null;}public String getRealPath(String path){return null;}public RequestDispatcher getRequestDispatcher(String path){return null;}public boolean isSecure(){return false;}public String getCharacterEncoding(){return null;}public int getContentLength(){return 0;}public String getContentType(){return null;}public ServletInputStream getInputStream() throws IOException{return null;}public Locale getLocale(){return null;}public Enumeration getLocales(){return null;}public String getParameter(String name){return null;}public Map getParameterMap(){return null;}public Enumeration getParameterNames(){return null;}public String[] getParameterValues(String parameter){return null;}public String getProtocol(){return null;}public BufferedReader getReader() throws IOException{return null;}public String getRemoteAddr(){return null;}public String getRemoteHost(){return null;}public String getScheme(){return null;}public String getServerName(){return null;}public int getServerPort(){return 0;}public void removeAttribute(String attribute){}public void setAttribute(String key, Object value){}public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException{}@Overridepublic String getLocalAddr(){return null;}@Overridepublic String getLocalName(){return null;}@Overridepublic int getLocalPort(){return 0;}@Overridepublic int getRemotePort(){return 0;}}

package linkin;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.OutputStream;import java.io.PrintWriter;import java.util.Locale;import javax.servlet.ServletOutputStream;import javax.servlet.ServletResponse;public class Response implements ServletResponse{private static final int BUFFER_SIZE = 1024;public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";Request request;OutputStream output;PrintWriter writer;public Response(OutputStream output){this.output = output;}public void setRequest(Request request){this.request = request;}/* This method is used to serve static pages */public void sendStaticResource() throws IOException{byte[] bytes = new byte[BUFFER_SIZE];FileInputStream fis = null;try{/* request.getUri has been replaced by request.getRequestURI */File file = new File(WEB_ROOT, request.getUri());fis = new FileInputStream(file);/* * HTTP Response = Status-Line(( general-header | response-header | * entity-header ) CRLF) CRLF [ message-body ] Status-Line = * HTTP-Version SP Status-Code SP Reason-Phrase CRLF */int ch = fis.read(bytes, 0, BUFFER_SIZE);while (ch != -1){output.write(bytes, 0, ch);ch = fis.read(bytes, 0, BUFFER_SIZE);}}catch (FileNotFoundException e){String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>";output.write(errorMessage.getBytes());}finally{if (fis != null)fis.close();}}/** implementation of ServletResponse */public void flushBuffer() throws IOException{}public int getBufferSize(){return 0;}public String getCharacterEncoding(){return null;}public Locale getLocale(){return null;}public ServletOutputStream getOutputStream() throws IOException{return null;}public PrintWriter getWriter() throws IOException{// autoflush is true, println() will flush,// but print() will not.writer = new PrintWriter(output, true);return writer;}public boolean isCommitted(){return false;}public void reset(){}public void resetBuffer(){}public void setBufferSize(int size){}public void setContentLength(int length){}public void setContentType(String type){}@Overridepublic void setLocale(Locale locale){}@Overridepublic String getContentType(){return null;}@Overridepublic void setCharacterEncoding(String arg0){}}

package linkin;import java.io.IOException;public class StaticResourceProcessor{public void process(Request request, Response response){try{response.sendStaticResource();}catch (IOException e){e.printStackTrace();}}}

package linkin;import java.io.File;import java.io.IOException;import java.net.URL;import java.net.URLClassLoader;import java.net.URLStreamHandler;import javax.servlet.Servlet;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;public class ServletProcessor1{public void process(Request request, Response response){String uri = request.getUri();String servletName = uri.substring(uri.lastIndexOf("/") + 1);URLClassLoader loader = null;try{// create a URLClassLoaderURL[] urls = new URL[1];URLStreamHandler streamHandler = null;File classPath = new File(Response.WEB_ROOT);// the forming of repository is taken from the// createClassLoader method in// org.apache.catalina.startup.ClassLoaderFactoryString repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();// the code for forming the URL is taken from// the addRepository method in// org.apache.catalina.loader.StandardClassLoader.urls[0] = new URL(null, repository, streamHandler);loader = new URLClassLoader(urls);}catch (IOException e){System.out.println(e.toString());}Class myClass = null;try{System.out.println(servletName);myClass = loader.loadClass(servletName);}catch (ClassNotFoundException e){System.out.println(e.toString());}Servlet servlet = null;try{//跑一下反射,初始化一个实例,然后调用这个servlet的service方法。servlet = (Servlet) myClass.newInstance();servlet.service((ServletRequest) request, (ServletResponse) response);}catch (Exception e){System.out.println(e.toString());}catch (Throwable e){System.out.println(e.toString());}}}

package linkin;import java.io.IOException;import java.io.PrintWriter;import javax.servlet.Servlet;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;public class PrimitiveServlet implements Servlet{@Overridepublic void init(ServletConfig config) throws ServletException{System.out.println("init");}@Overridepublic void service(ServletRequest request, ServletResponse response) throws ServletException, IOException{System.out.println("from service");PrintWriter out = response.getWriter();out.println("<html><head><title>linkin.html</title>"+"<meta http-equiv=\"keywords\" content=\"keyword1,keyword2,keyword3\">"+"<meta http-equiv='description' content='this is my page'>"+"<meta http-equiv='content-type' content='text/html; charset=UTF-8'>"+"<!--<link rel='stylesheet' type='text/css' href='./styles.css'>-->"+"</head><h1>NightWish,气质在作祟。。。</h1><br></body></html>");out.println("值得注意的是,要是去web应用去找一个文件那么直接指定物理路径找就好了,要是用类加载加载一个class文件,是要在classpath里面去找的");out.println("现在一般不需要配置java的classpath了,默认情况就是当前的工作路径,也就是这个项目的bin下面的路径,在类加载找这个文件的时候一定要写上包名");}public void destroy(){System.out.println("destroy");}public String getServletInfo(){return null;}public ServletConfig getServletConfig(){return null;}}




补充说明:

首先先看上面的servlet的处理器里面调用servlet的service方法这2行代码:

try {servlet = (Servlet) myClass.newInstance();servlet.service((ServletRequest) request,(ServletResponse) response);}

这里有一个问题:我们写的这个处理器并不是说只能接受servlet的请求,所以这里我们传入的参数就是request和response类型的,但是我们现在在调用servlet的时候,又要传入servletRequest和servletResponse类型的参数,这里向下强转是很危险的,万一人家这个请求和相应不是servlet类型的呢,对吧,所以我们就是要达到一种效果就是说:我贴出的这request和response这2块代码呢只能在我们自己的类里面使用,在我自己的包下面使用,在其他的地方不能被访问到,这样子我就随便向下转保证肯定不会出错了,而且也可以保证这2个类里面的方法也是安全的了,比如说外部的类肯定不能调用request的parseURI方法了,怎么办呢?一共有2种实现方式:

1,让上面的request类和response类拥有默认的访问权限,也就是说只能在自己的包中被访问到,那么这样子就安全了

2,使用门面设计模式,具体的讲解请查看我设计模式里面的那篇文章。大致的意思就是在上面的request和response外面再自己封装一次门面,将这个request和response私有化,里面所有的方法呢其实还是用这个request和response类来调用,这样子的话包外面就不能直接访问到request和response这2个类了。代码如下:


package linkin;import java.io.BufferedReader;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.util.Enumeration;import java.util.Locale;import java.util.Map;import javax.servlet.RequestDispatcher;import javax.servlet.ServletInputStream;import javax.servlet.ServletRequest;public class RequestFacade implements ServletRequest{private ServletRequest request = null;public RequestFacade(Request request){this.request = request;}/* implementation of the ServletRequest */public Object getAttribute(String attribute){return request.getAttribute(attribute);}public Enumeration getAttributeNames(){return request.getAttributeNames();}......................}




1 0