通过各款JSP HTTP服务器的对比,定位本JSP HTTP服务器的应用方向
对于企业选择或者设计HTTP服务器,需要考虑很多因素,主要的因素有:稳定性,安全性,执行效率,易用性,可扩展性等。笔者对当前业界中较为流行的HTTP服务器按照常用指标进行了分析和对比,如表1。
HTTP服务器 | 支持服务页 | 运行平台 | 安全性 | 执行效率 | 易用性 |
MS IIS | ASP | MS Windows | 一般 | 一般 | 容易 |
MS IIS | ISAPI | MS Windows | 一般 | 好 | 容易 |
Apache | CGI | MS Windows UNIX,Linux | 好 | 一般 | 一般 |
Tomcat | JSP | MS Windows UNIX,Linux | 好 | 好 | 不易 |
通过表1我们可以知道,MS IIS只能运行在MS Windows平台下,且由于IIS的体系设计很大程度上依赖于Windows系统,由于MS Windows系统存在一定的漏洞,从而导致IIS体系的安全性能也比较低。但是IIS安装使用比较简单,适用于对安全性和扩展性要求不高的 Windows用户。
对于Apache和Tomcat(都由Apache Software Foundation研究开发)这两款当前互联网上比较流行的HTTP服务器,不仅可以支持MS Windows平台,也可以支持当前所有主流非Windows平台(例如:Linux,各种UNIX操作系统),并且安全性能较IIS强。但是对于一般的用户而言,Apache和Tomcat的配置使用较为复杂一些。所以Apache和Tomcat适用于专业的,对系统安全性和扩展性要求较高的用户。
另外需要考虑的因素是JSP HTTP服务器的执行效率。在表1中,ASP和CGI都采用即时调用模式,即当客户端请求该资源时,服务器端会即时解释并执行该模块,执行完毕之后即时释放该模块,每次获取请求时都必须解释和执行该模块,这样过多地与磁盘系统进行交互,会造成系统的执行效率的降低。
对于ISAPI而言,其执行模块是作为动态链接库(DLL)模块的形态进行调用,初次调用完毕后该模块将存在于内存中。后续再收到客户端请求时直接从内存中调用执行该模块,从而效率较高。但这种情形可能带来代码更新的问题。当修改本地代码时,必须从内存中清空该动态链接库模块,即需要先关闭服务器后才能更新本地代码,否则服务器内存中执行的还是旧的代码模块。
而对于Tomcat系统而言,这样的问题都得到了避免。Tomcat将初次执行的Java类模块载入到内存,后续调用时,直接从内存中调用执行模块,减少了与磁盘系统的交互。同时通过自动判断本地代码是否受到修改而更新载入内存中旧的类模块。从而不仅执行效率较高,且修改本地代码也比较方便。
基于上述的研究分析,笔者拟采用扩展性和安全性良好的Java体系来实现一款支持JSP服务页的HTTP服务器,其功能实现基本覆盖Tomcat,并在其基础上增强对CGI的支持和易用性的提高。
一、设计过程
1.搭建HTTP服务器框架
1.1 设计思路
通过建立TCP套接字(端口为80)向客户端提供HTTP服务。分析客户端请求(GET,POST请求等),建立请求资源与本地资源的映射关系,实现请求应答。
1.2 设计要点
(1)客户端请求的多线程支持。
(2)客户端请求的分析。
(3)请求资源与本地资源映射以及本地资源的应答。
(4)对CGI以及JSP类似请求的接受分析与处理返回。
(5)扩展服务以及特殊指令。
1.3 实施前准备
(1)确定JDK版本并下载JDK
考虑到JDK1.4.2的稳定性,我们考虑使用版本为1.4.2或以上的JDK。从SUN的网站上http://java.sun.com/javase/downloads/index.jsp下载当前平台支持的JDK。
(2)安装JDK并配置编译环境
安装JDK并设置java路径($(installation_dir)/bin)到系统PATH变量中。
1.4 设计实施
(1)创建服务套接字
- //Create server socket ServerSocket serv = new ServerSocket(SERVER_PORT);
System.out.println("HTTP server(port: " + Integer.toString(SERVER_PORT) + ")
running...");
(2)接受客户端请求并创建请求处理线程
- while(true)
- {
- //Accept the client connections
- Socket clnt = serv.accept();
- //Create thread for each client
- HTTPThread HTTPThd = new HTTPThread(clnt, props, ht);
- HTTPThd.start();
- }
以上代码中,创建了多线程构架的客户端请求处理体系。可以及时处理多客户端连接。
(3)分析请求
客户端处理线程从客户端套接字中读取相应的请求内容,并对请求进行分析。
- //Create client socket input stream reader
- m_sin = new BufferedReader(new InputStreamReader(_s.getInputStream() ) );
- ……
- //Get the first line of output from client socket
- request = m_sin.readLine().trim();
- if(request != null)
- {
- //The method is GET
- if(request.startsWith(METHOD_GET) == true)
- {
- parseGetRequest(request);
- }
- //The method is POST
- else if(request.startsWith(METHOD_POST) == true)
- {
- params = m_sin.readLine();
- //Skip the middle lines of POST request
- while( (params != null) && (params.equals("") == false) )
- {
- params = m_sin.readLine();
- }
- //The last line contains those parameters
- params = m_sin.readLine();
- paramsparams = params.trim();
- parsePostRequest(request, params);
- }
- //Close client socket input stream and client socket itself
- m_sin.close();
- m_s.close();
- }
通过请求内容的第一行就可以知道请求方式是GET还是POST。如果是GET请求(例如很多CGI都是GET请求),就可以直接从请求字符串中获取请求的资源内容。GET请求的格式为:GET <URL> HTTP/1.X。其中URL为请求的资源内容,而1.X是用于指明客户端所支持的HTTP的版本,当前有1.0和1.1两个标准。
如果是POST请求,除了请求的资源内容(例如JSP文件)外,在请求的末行中还包含请求资源将要用到的参数行。所以上述代码中存在掠过中间部分的请求内容,只需要获取资源内容和参数行即可。
(4)请求资源与本地资源的映射
一般出于安全性考虑,JSP HTTP服务器端不可能将本地的资源路径和服务提供的路径相同,而是将本地路径的某一目录映射为HTTP服务资源的根目录,该目录一般称之为服务页根目录(ServerPageDir)。当客户端请求资源映射到本地资源时,必须使用本地被映射的目录替换请求中的根目录。例如HTTP服务器端将本地路径“/usr/paul/paul.home”映射为服务页根目录。那么客户端的请求“/sample.jpg”对应本地的路径资源为“ /usr/paul/paul.home/sample.jpg”。
当请求内容的末尾字符为“/”时,即请求为目录而不是具体文件时,还存在默认请求的问题,一般目录的默认请求为该目录下的index.htm,index.html,index.jsp等文件。
特别是的,出于安全性和习惯考虑,对CGI目录也进行了映射(一般映射为/cgi-bin/),所以如果请求中包含CGI映射时还必须替换为CGI程序所在目录。
- //If the request start with /cgi-bin,
- //then need replace /cgi-bin with true CGI directory
- if(fname.startsWith(PATH_SEPARATOR + CGI_BIN_DIR) == true)
- {
- fnamefname = fname.replaceFirst(PATH_SEPARATOR + CGI_BIN_DIR, cgiBinDir);
- }
- else //else, file name need append to server documents directory
- {
- fname = serverPageDir + fname;
- }
- //If request is for directory,
- //then need respond default page in the directory
- if(fname.endsWith(PATH_SEPARATOR) == true)
- {
- fnamefname = fname + defaultPage;
- }