《深入剖析Tomcat》一1.3 应用程序
1.3 应用程序
本章的Web服务器应用程序位于ex01.pyrmont包下,包括三个类:
HttpServer
Request
Response
应用程序的入口点(静态main()方法)在HttpServer类中。main()方法创建一个HttpServer实例,然后,调用其await()方法。顾名思义,await()方法会在指定端口上等待HTTP请求,对其进行处理,然后发送响应信息回客户端。在接收到关闭命令前,它会保持等待状态。
该应用程序仅发送位于指定目录的静态资源的请求,如HTML文件和图像文件。它也可以将传入到的HTTP请求字节流显示到控制台上。但是,它并不发送任何头信息到浏览器,如日期或cookies等。
下面几节将分别展示三个类的具体实现。
1.3.1 HttpServer类
HttpServer类表示一个Web服务器,具体实现如代码清单1-1所示。注意,为了节省空间,await方法在代码清单1-2中列出,并没有在代码清单1-1中重复。
这个Web服务器可以处理对指定目录中的静态资源的请求,该目录包括由公有静态变量final WEB_ROOT指明的目录及其所有子目录。WEB_ROOT的初始值为:
该代码清单包含一个名为webroot的目录,用于测试该应用程序的一些静态资源都位于该目录下。在该目录下还可以找到用于测试后续章节中应用程序的几个servlet程序。
若要请求静态资源,可以在浏览器的地址栏或URL框中输入如下的URL:
http://machineName:port/staticResource
若从另一台机器(不是运行应用程序的那台机器)上向该应用程序发出请求,则machineName是应用程序所在计算机的名称或IP地址;若在同一台机器上发出的请求,则可以将machineName替换为localhost,此外,连接请求使用的端口为8080。staticResource是请求的文件的名字,该文件必须位于WEB_ROOT指向的目录下。
例如,如果你正使用同一台机器来测试该应用程序,你想让HttpServer对象发送index.html文件,就可以使用如下的URL:
http://localhost:8080/index.html
若要关闭服务器,可以通过Web浏览器的地址栏或URL框,在URL的host:port部分后面输入预先定义好的字符串,从Web浏览器发送一条关闭命令,这样服务器就会收到关闭命令了。关闭命令定义在HttpServer类的SHUTDOWN静态final变量中:
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
因此,若要关闭服务器,需要使用如下的URL:
http://localhost:8080/SHUTDOWN
接下来,看一下代码清单1-2中的await()方法。
该方法名之所以称为await(),而不是wait(),是因为wait()方法是java.lang.Object类中与使用线程相关的重要方法。
await()方法会先创建一个ServerSocket实例,然后进入一个while循环:
当从8080端口接收到HTTP请求后,ServerSocket类的accept()方法返回,等待结束:
socket = serverSocket.accept();
接收到请求后,await()方法会从accept()方法返回的Socket实例中获取java.io.InputStream对象和java.io.OutputStream对象:
input = socket.getInputStream();
output = socket.getOutputStream();
然后,await()方法会创建一个ex01.pyrmont.Request对象,并调用其parse()方法来解析HTTP请求的原始数据:
// create Request object and parse
Request request = new Request(input);
request.parse();
然后,await()方法会创建一个Response对象,并分别调用其setRequest()方法和sendStaticResource()方法:
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
最后,await()方法关闭套接字,调用Request类的getUri()方法来测试HTTP请求的URI是否是关闭命令。若是,则将变量shutdown设置为true,程序退出while循环。
// Close the socket
socket.close ();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
1.3.2 Request类
ex01.pyrmont.Request类表示一个HTTP请求。可以传递InputStream对象(从通过处理与客户端通信的Socket对象中获取的),来创建Request对象。可以调用InputStream对象中的read()方法来读取HTTP请求的原始数据。
Request类在代码清单1-3中给出。Request类有两个公共方法(parse()和getUri())和一个私有方法parseUri(),parse()方法和parseUri()方法分别在代码清单1-4和代码清单1-5中给出。
parse()方法用于解析HTTP请求中的原始数据。parse()方法会调用私有方法parseUri()来解析HTTP请求的URI,除此之外,并没有做太多的工作。parseUri()方法将URI存储在变量uri中。调用公共方法getUri()会返回HTTP请求的URI。
注意 处理HTTP请求原始数据的方法会在第3章和随后章节的应用程序中给出。
为了理解parse()和parseUri()方法是如何工作的,需要了解HTTP请求的结构,这在1.1节已经介绍过。在本章中,我们仅仅对HTTP请求的第一部分—请求行—感兴趣。请求行以请求方法开始,接着是请求的URI和请求所使用的协议及其版本,并以CRLF符结束。请求行中的元素以空格分开。例如,使用GET方法请求index.html文件的请求行如下所示:
GET /index.html HTTP/1.1
parse()方法从传入到Request对象中的套接字的InputStream对象中读取整个字节流,并将字节数组存储在缓冲区中。然后,它使用缓冲区字节数组中的数组填充StringBuffer对象request,并将StringBuffer的String表示传递给parseUri()方法。
parse()方法已经在代码清单1-4中给出。
parseUri()方法从请求行中获取URI。代码清单1-5展示了parseUri()方法的具体实现。parseUri()方法在请求中搜索第一个和第二个空格,从中找出URI。
1.3.3 Response类
ex01.pyrmont.Response类表示HTTP响应,其定义如代码清单1-6所示。
首先要注意的是Response类的构造函数会接收一个java.io.OutputStream对象,如下所示:
Response对象在HttpServer类的await()方法中通过传入从套接字中获取的OutputStream来创建。
Response类有两个公共方法:setRequest()和sendStaticResource()。setRequest()方法会接收一个Reuqest对象为参数。
sendStaticResource()方法用于发送一个静态资源到浏览器,如HTML文件。它首先会通过传入父路径和子路径到File类的构造函数中来实例化java.io.File类:
File file = new File(HttpServer.WEB_ROOT, request.getUri());
然后,它检查该文件是否存在。若存在,sendStaticResource()方法会使用File对象创建一个java.io.FileInputStream对象。然后它调用FileInputStream类的read()方法,并将字节数组写入到OutputStream输出中。注意,这种情况下,静态资源的内容是作为原始数据发送到浏览器的:
若文件不存在,sendStaticResource()会发送错误消息到浏览器:
1.3.4 运行应用程序
若要运行应用程序,需要在工作目录中执行下面的命令:
java ex01.pyrmont.HttpServer
若要测试应用程序,可以打开浏览器,在地址栏或URL框中输入如下URL:
http://localhost:8080/index.html
然后,就可以在浏览器中看到如下的index.html页面,如图1-1所示:
在控制台中,可以看到类似于如下的HTTP请求信息: