基于Java web服务器简单实现一个Servlet容器
上篇写了一个简单的java web服务器实现,只能处理一些静态资源的请求,本篇文章实现的servlet容器基于前面的服务器做了个小改造,增加了servlet请求的处理。
程序执行步骤
1.创建一个serversocket对象;
2.调用serversocket对象的accept方法,等待连接,连接成功会返回一个socket对象,否则一直阻塞等待;
3.从socket对象中获取inputstream和outputstream字节流,这两个流分别对应request请求和response响应;
4.处理请求:读取inputstream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息;
5.处理响应(分两种类型,静态资源请求响应或servlet请求响应):如果是静态资源请求,则根据解析出来的uri信息,从web_root目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到outputstream字节流中;如果是servlet请求,则首先生成一个urlclassloader类加载器,加载请求的servlet类,创建servlet对象,执行service方法(往outputstream写入响应的数据);
6.关闭socket对象;
7.转到步骤2,继续等待连接请求;
代码实现:
添加依赖:
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api --> <dependency> <groupid>javax.servlet</groupid> <artifactid>servlet-api</artifactid> <version>2.3</version> </dependency>
服务器代码:
package ex02.pyrmont.first; import java.net.socket; import java.net.serversocket; import java.net.inetaddress; import java.io.inputstream; import java.io.outputstream; import java.io.ioexception; import ex02.pyrmont.request; import ex02.pyrmont.response; import ex02.pyrmont.staticresourceprocessor; public class httpserver1 { // 关闭服务命令 private static final string shutdown_command = "/shutdown"; 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); } // 循环等待请求 while (true) { socket socket = null; inputstream input = null; outputstream output = null; try { //等待连接,连接成功后,返回一个socket对象 socket = serversocket.accept(); input = socket.getinputstream(); output = socket.getoutputstream(); // 创建request对象并解析 request request = new request(input); request.parse(); // 检查是否是关闭服务命令 if (request.geturi().equals(shutdown_command)) { break; } // 创建 response 对象 response response = new response(output); response.setrequest(request); if (request.geturi().startswith("/servlet/")) { //请求uri以/servlet/开头,表示servlet请求 servletprocessor1 processor = new servletprocessor1(); processor.process(request, response); } else { //静态资源请求 staticresourceprocessor processor = new staticresourceprocessor(); processor.process(request, response); } // 关闭 socket socket.close(); } catch (exception e) { e.printstacktrace(); system.exit(1); } } } }
常量类:
package ex02.pyrmont; import java.io.file; public class constants { public static final string web_root = system.getproperty("user.dir") + file.separator + "webroot"; public static final string web_servlet_root = system.getproperty("user.dir") + file.separator + "target" + file.separator + "classes"; }
request:
package ex02.pyrmont; import java.io.inputstream; import java.io.ioexception; import java.io.bufferedreader; 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; } /** * * requeststring形式如下: * get /index.html http/1.1 * host: localhost:8080 * connection: keep-alive * cache-control: max-age=0 * ... * 该函数目的就是为了获取/index.html字符串 */ 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; } //从inputstream中读取request信息,并从request中获取uri值 public void parse() { // read a set of characters from the socket stringbuffer 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 the 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 { } }
response:
package ex02.pyrmont; import java.io.outputstream; import java.io.ioexception; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.file; import java.io.printwriter; import java.util.locale; import javax.servlet.servletresponse; import javax.servlet.servletoutputstream; public class response implements servletresponse { private static final int buffer_size = 1024; request request; outputstream output; printwriter writer; public response(outputstream output) { this.output = output; } public void setrequest(request request) { this.request = request; } //将web文件写入到outputstream字节流中 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(constants.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) { } public void setlocale(locale locale) { } }
静态资源请求处理:
package ex02.pyrmont; import java.io.ioexception; public class staticresourceprocessor { public void process(request request, response response) { try { response.sendstaticresource(); } catch (ioexception e) { e.printstacktrace(); } } }
servlet请求处理:
package ex02.pyrmont.first; import java.net.url; import java.net.urlclassloader; import java.net.urlstreamhandler; import java.io.ioexception; import javax.servlet.servlet; import javax.servlet.servletrequest; import javax.servlet.servletresponse; import ex02.pyrmont.constants; import ex02.pyrmont.request; import ex02.pyrmont.response; public class servletprocessor1 { public void process(request request, response response) { string uri = request.geturi(); string servletname = uri.substring(uri.lastindexof("/") + 1); //类加载器,用于从指定jar文件或目录加载类 urlclassloader loader = null; try { urlstreamhandler streamhandler = null; //创建类加载器 loader = new urlclassloader(new url[]{new url(null, "file:" + constants.web_servlet_root, streamhandler)}); } catch (ioexception e) { system.out.println(e.tostring()); } class<?> myclass = null; try { //加载对应的servlet类 myclass = loader.loadclass(servletname); } catch (classnotfoundexception e) { system.out.println(e.tostring()); } servlet servlet = null; try { //生产servlet实例 servlet = (servlet) myclass.newinstance(); //执行ervlet的service方法 servlet.service((servletrequest) request,(servletresponse) response); } catch (exception e) { system.out.println(e.tostring()); } catch (throwable e) { system.out.println(e.tostring()); } } }
servlet类:
import javax.servlet.*; import java.io.ioexception; import java.io.printwriter; public class primitiveservlet implements servlet { public void init(servletconfig config) throws servletexception { system.out.println("init"); } public void service(servletrequest request, servletresponse response) throws servletexception, ioexception { system.out.println("from service"); printwriter out = response.getwriter(); out.println("hello. roses are red."); out.print("violets are blue."); } public void destroy() { system.out.println("destroy"); } public string getservletinfo() { return null; } public servletconfig getservletconfig() { return null; } }
结果测试:
静态资源请求:
servlet请求(因为只是第一个字符串被刷新到浏览器,所以你不能看到第二个字符串violets are blue。我们将在后续完善该容器):
改进
前面实现的servlet容器有一个严重的问题,用户在servlet里可以直接将servletrequest、servletresponse向下转 型为request和response类型,并直接调用其内部的public方法,这是一个不好的设计,改进方法是给request、response 增加外观类,这样,用户只能访问外观类里定义的public方法。
request外观类
package ex02.pyrmont.second; import java.io.ioexception; import java.io.bufferedreader; 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; import ex02.pyrmont.request; 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(); } @suppresswarnings("deprecation") public string getrealpath(string path) { return request.getrealpath(path); } public requestdispatcher getrequestdispatcher(string path) { return request.getrequestdispatcher(path); } public boolean issecure() { return request.issecure(); } public string getcharacterencoding() { return request.getcharacterencoding(); } public int getcontentlength() { return request.getcontentlength(); } public string getcontenttype() { return request.getcontenttype(); } public servletinputstream getinputstream() throws ioexception { return request.getinputstream(); } public locale getlocale() { return request.getlocale(); } public enumeration<?> getlocales() { return request.getlocales(); } public string getparameter(string name) { return request.getparameter(name); } public map<?, ?> getparametermap() { return request.getparametermap(); } public enumeration<?> getparameternames() { return request.getparameternames(); } public string[] getparametervalues(string parameter) { return request.getparametervalues(parameter); } public string getprotocol() { return request.getprotocol(); } public bufferedreader getreader() throws ioexception { return request.getreader(); } public string getremoteaddr() { return request.getremoteaddr(); } public string getremotehost() { return request.getremotehost(); } public string getscheme() { return request.getscheme(); } public string getservername() { return request.getservername(); } public int getserverport() { return request.getserverport(); } public void removeattribute(string attribute) { request.removeattribute(attribute); } public void setattribute(string key, object value) { request.setattribute(key, value); } public void setcharacterencoding(string encoding) throws unsupportedencodingexception { request.setcharacterencoding(encoding); } }
response外观类
package ex02.pyrmont.second; import java.io.ioexception; import java.io.printwriter; import java.util.locale; import javax.servlet.servletresponse; import javax.servlet.servletoutputstream; import ex02.pyrmont.response; public class responsefacade implements servletresponse { private servletresponse response; public responsefacade(response response) { this.response = response; } public void flushbuffer() throws ioexception { response.flushbuffer(); } public int getbuffersize() { return response.getbuffersize(); } public string getcharacterencoding() { return response.getcharacterencoding(); } public locale getlocale() { return response.getlocale(); } public servletoutputstream getoutputstream() throws ioexception { return response.getoutputstream(); } public printwriter getwriter() throws ioexception { return response.getwriter(); } public boolean iscommitted() { return response.iscommitted(); } public void reset() { response.reset(); } public void resetbuffer() { response.resetbuffer(); } public void setbuffersize(int size) { response.setbuffersize(size); } public void setcontentlength(int length) { response.setcontentlength(length); } public void setcontenttype(string type) { response.setcontenttype(type); } public void setlocale(locale locale) { response.setlocale(locale); } }
处理servlet请求类:
package ex02.pyrmont.second; import java.net.url; import java.net.urlclassloader; import java.net.urlstreamhandler; import java.io.ioexception; import javax.servlet.servlet; import javax.servlet.servletrequest; import javax.servlet.servletresponse; import ex02.pyrmont.constants; import ex02.pyrmont.request; import ex02.pyrmont.response; public class servletprocessor2 { public void process(request request, response response) { string uri = request.geturi(); string servletname = uri.substring(uri.lastindexof("/") + 1); // 类加载器,用于从指定jar文件或目录加载类 urlclassloader loader = null; try { urlstreamhandler streamhandler = null; // 创建类加载器 loader = new urlclassloader(new url[] { new url(null, "file:" + constants.web_servlet_root, streamhandler) }); } catch (ioexception e) { system.out.println(e.tostring()); } class<?> myclass = null; try { // 加载对应的servlet类 myclass = loader.loadclass(servletname); } catch (classnotfoundexception e) { system.out.println(e.tostring()); } servlet servlet = null; //给request、response增加外观类,安全性考虑,防止用户在servlet里直接将servletrequest、servletresponse向下转型为request和response类型, //并直接调用其内部的public方法,因为requestfacade、responsefacade里不会有parse、sendstaticresource等方法; requestfacade requestfacade = new requestfacade(request); responsefacade responsefacade = new responsefacade(response); try { servlet = (servlet) myclass.newinstance(); servlet.service((servletrequest) requestfacade, (servletresponse) responsefacade); } catch (exception e) { system.out.println(e.tostring()); } catch (throwable e) { system.out.println(e.tostring()); } } }
其它代码与前面实现的servlet容器基本一致。
验证程序,分别请求静态资源和servlet,发现结果与前面实现的容器一致;
参考资料:《深入剖析tomcat》
@author 风一样的码农
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。