您的位置:首页 > 编程语言 > Java开发

基于JAVA的WEB服务器工作机制

2004-10-20 11:48 531 查看
基于JAVA的WEB服务器工作机制

一个WEB服务器也被称为一个HTTP服务器,因为它使用HTTP协议和它的客户进行通讯,而这些客户通常是浏览器。 一个基于JAVA的WEB服务器使用了两个重要的类:
java.net.Socket和
java.net.ServerSocket
,并且是通过HTTP消息进行通讯的。本文开头将讨论HTTP和这两个类,后面,将解释一个简单WEB服务器应用程序的工作机制。

超文本传输协议 (HTTP)

HTTP协议允许服务器和客户机通过INTERNET接收和发送数据。它是个请求和回应协议----客户机发送请求,服务器对请求给出回应。HTTP 使用可靠的TCP 连接,默认TCP端口是80。HTTP的第一版是HTTP/0.9,随后被 HTTP/1.0所取代。当前最新的版本是HTTP/1.1,这个在RPC2616规范文档中给出了定义。

这一章节简单讲叙了HTTP 1.1, 对于你理解WEB服务器应用程序发送的消息还是足够的。如果你很感兴趣,可以参考RFC 2616文档。

使用HTTP,客户端通过建立一个连接和发送一个HTTP请求来初始化事务会话,服务器联系客户端或者回应一个callback连接给客户端。 它们都可以中断连接。比如,在使用WEB浏览器时,你可以点击浏览器上的STOP按钮来停止文件下载进程,就有效的关闭了和这个WEB服务器的HTTP连接。

HTTP 请求(Requests)

一个HTTP request包含三个部分:

方法,URL,协议/版本(Method-URI-Protocol/Version)

请求包头Request headers

实体包(Entity body)
下面给出一个HTTP请求的范例:

请求的第一行就是method-URI-Protocol/Version。

请求的是POST方法,后面的
/servlet/default.jsp
表示一个URL地址,
HTTP/1.1
表示协议的版本。

HTTP标准规范定义了一些请求方法,用来给每个HTTP请求所使用。HTTP 1.1支持7中请求方法:
GET
,
POST
,
HEAD
,
OPTIONS
,
PUT
,
DELETE
, 和
TRACE。
GET
POST
在INTERNET的应用程序中是使用最普遍的两个方法。

URI完整的指明了一个INTERNET资源。一个URI通常是相对于服务器的根目录被解释的。 因此,它总是使用符号(
/
)开头。一个URL实际是一个URI类型。协议版本表示当前正在使用的HTTP协议的版本。

请求包头(request header)包含了一些有用的客户机环境的信息和请求的实体(entity body)信息。比如,它可以包含浏览器使用的语言和实体的长度等等。每个请求包头都被CRLF(回车换行)序列所分离。

在先前的HTTP请求中,实体是下面简单的一行:

在一个典型的HTTP请求中,这个实体能够很容易地变得更长。

HTTP响应(Responses)

和请求类似,一个HTTP响应也包含三个部分:

协议状态 代码描叙(Protocol-Status code-Description)

响应包头(Response headers)

实体(Entity body)
下面是HTTP响应的一个简单范例:

第一行的响应包头和上面的请求包头很相似。 第一行告诉我们,协议是使用的HTTP1.1,响应请求已成功(200表示成功),一切已OK。

响应包头和请求包头相似,也包含一些有用的信息。响应的实体是HTML那一部分的内容。包头和实体也都是被CRLF序列分离开的。

Socket类

套接字(socket)是网络连接的一个端点。它使得应用程序能够通过网络进行读和写的操作。 通过在连接上发送和接受字节流,两个位于不同计算机的软件程序能够彼此相互通讯。为了发送一个消息到另一个程序,你需要知道对方机器的IP地址和socket端口号。在JAVA中,一个socket是由
java.net.Socket类所表示的。


为了创建一个套接字,你可以使用
Socket类的构造函数来完成。
这些构造函数接受主机名和端口:

host表示远程计算机名或者IP地址,
port
表示该远程应用的端口号。比如,要在80端口连接到yahoo.com,你需要构造下面的socket:

一旦你成功创建了一个
Socket
类的实例,就可以使用它来发送和接受字节流了。 要发送字节流,必须首先调用
Socket
类的
getOutputStream
方法来获得一个
java.io.OutputStream
对象。要发送一个文本到远程应用程序,经常要构造一个从
OutputStream
对象返回的
java.io.PrintWriter
对象。要接收连接另一端的字节流,要调用
Socket
类的
getInputStream
方法,该方法是从
java.io.InputStream
返回的。

下面的程序段创建了一个socket,和本地HTTP服务器(127.0.0.1代表本地)进行通讯,发送一个HTTP请求,然后从服务器接收一个响应。它创建了一个
StringBuffer
 来保存响应,并将它打印到控制台。

要从服务器得到一个确切的响应,你需要发送一个遵循HTTP协议规则的HTTP请求。如果你阅读了上面的那段"超文本传输协议(HTTP)" ,那么你就应该能够理解刚才上面建立socket的代码。

ServerSocket

Socket
 类表示的是客户端的socket。无论什么时候,只要你想连接到一个远程服务器的应用,你都要构建一个socket。如果你想执行一个服务器应用程序,比如HTTP服务或者FTP服务的程序,那么你需要使用不同的途径。因为你的服务器必须一直是开机闲置,所以它不知道什么时候客户机试图来连接它。

这个时候,需要使用
java.net.ServerSocket
类。它会实现一个服务器socket。一个服务器socket会等待来自客户端的连接。一旦它接收到一个连接请求,它就会创建一个 
Socket
实例来处理和客户端通讯的问题。

要创建一个服务器socket,可以使用四种ServerSocket类构造方法中的一种来实现。你需要制定服务器socket监听的IP地址和端口。 典型的,IP地址如果是127.0.0.1,意味着服务器socket将监听本地机器。这个被监听的IP地址被认为是一种绑定地址。server socket的另一个重要属性是它的 backlog属性,它是在server socket拒绝连接请求前,能够接受的连接请求的最大队列长度。

ServerSocket类的构造函数之一如下:

对于这个构造函数而言,绑定地址必须是
java.net.InetAddress
的一个实例。一个简单的办法是通过调用它的静态方法getByName来构造一个
InetAddres
对象。该方法来一个包含主机名的字符串参数:

下面一行代码构造一个
ServerSocket
,它监听本地机器的8080端口,backlog设置为1。

一旦有了一个 
ServerSocket
实例,可以通过调用accept方法来告诉它等待进来的连接请求。这个方法只有在有一个连接请求时才返回。它返回的是
Socket
类的实例。这个
Socket
对象能够发送和接受来自客户端应用的字节流,就是第一节所讲到的socket类。实际上,
accept
是本文提及的唯一一个在应用中使用的方法。

Application应用

我们的web服务器应用是
ex01.pyrmont
包的一部分,包含三个类:

HttpServer


Request


Response

这个应用的入口(静态
main
方法)是
HttpServer
类。它创建了一个
HttpServer
 实例来调用它的
await
方法。 就象这个方法名所暗示的,
await
方法在一个指定的端口等待一个HTTP请求,并处理它们,然后发送回应给客户端。它保持等待状态,直到收到一个shutdown命令。 (命令名
await
来代替
wait
的原因是
wait是
System.Object
类中的一个用于线程方面的重要方法)

应用仅仅只发送静态资源,比如来自特定目录的HTML和图片文件。不支持动态包头 (比如日期或者cookie) 。

在下面的段落中,让我们来看看这三个类吧。

HttpServer

HttpServer
类表示一个web服务器,且在公共静态目录WEB_ROOT及它的子目录中能为找到的那些静态资源而服务。
WEB_ROOT
用以下方式初始化:

这段代码指明了一个包含静态资源的webroot目录,这些资源可用来测试该应用。在该目录中也能找到servlet容器。

要请求一个静态资源,在浏览器中输入如下地址或URL:

machineName
 是运行这个应用的计算机名或者IP地址。如果你的浏览器是在同一台机器上,可以使用
localhost
作为机器名。端口是8080。
staticResource
是请求的文件夹名,它必须位于WEB-ROOT目录中。

必然,如果你使用同一个计算机来测试应用,你想向HttpServer请求发送一个index.html 文件,那么使用如下URL:

想要停止服务器,可以通过发送一个shutdown命令。该命令是被
HttpServer
类中的静态SHUTDOWN_COMMAND变量所定义:

因此,要停止服务,你可以使用命令:

现在让我们来看看前面提到的
await
方法。下面一个程序清单给出了解释。

Listing 1.1. The
HttpServer
class'
await
method

await
方法是通过创建一个
ServerSocket
实例而开始的。然后它进入了一个WHILE循环:

在收到一个请求后,
await
方法从accept方法返回的socket实例中获得
java.io.InputStream
java.io.OutputStream
对象。

await
于是就创建一个
Request
对象并调用它的
parse
方法来解析原始的HTTP请求信息。

接下来,
await
 方法创建了一个
Response
 对象,使用set
Request
方法并调用它的
sendStaticResource
 方法。

最后,
await
关闭该
Socket
。调用Request的
getUri
方法来检查HTTP请求的URI是否是一个shutdown命令。如果是,shutdown变量被设置为true,程序退出while循环。

Request
类[/code]

Request类代表一个HTTP请求。Socket处理客户端的通讯,将返回一个InputStream对象,通过传递该对象,可以构造一个Request类的实例。通过调用
InputStream
 对象的read方法来获得这个HTTP请求的原始数据(raw data)。[/code]
Request
 有两个公共方法:
parse
 和
getUri。
parse方法解释HTTP请求的原始数据。它不做很多事情----它能够利用的唯一信息只是HTTP请求的URI ,这个URI是从私有方法
parseUri
.得到的。
parseUri
 方法保存URI 到
uri
 变量中,然后调用公共方法
getUri
来返回一个HTTP请求的URI。[/code]
为了理解
parse
 和 [code]parseUri
方法是如何工作的,需要知道HTTP请求的内部结构。这个结构是在RFC2616文档中定义的。[/code]
一个HTTP请求包含三个部分:

请求行(Request line) 请求包头(Headers) 消息体(Message body)
现在,我们仅仅只对HTTP请求的第一部分请求行(Request line)感兴趣。一个请求行由方法标记开始,后面根请求的URI和协议版本,最后由CRLF字符结束。请求行中的元素被空格字符分开。比如,使用GET方法请求的index.html文件的请求行如下:

[/code]
方法parse
从socket的
InputStream
中读取整个字节流,该字节流是
Request
对象传递进来的,然后parse将这些字节流存储在一个缓冲区里, 在缓冲区中组装一个称为request的
StringBuffer
对象。[/code]
下面的Listing 1.2.显示了parse方法的用法:

Listing 1.2. The 
Request
class' [code]parse
method[/code]
[/code]
parseUri
 方法从请求行那里得到URI。Listing 1.3 展示了
parseUri
 方法的用途。
parseUri
 减缩请求中的第一个和第二个空格来获得URI。 [/code]
Listing 1.3. The 
Request
class' [code]parseUri
method[/code]
[/code]

Response
类[/code]

Response
表示一个HTTP响应。它的构造函数接受一个
OutputStream
对象,比如下面的:[/code]
[/code]
Response
 对象被
HttpServer
类的
await
方法构造,该方法被传递的参数是从socket那里得到的
OutputStream
对象。[/code]
Response
类有两个公共方法:
setRequest
sendStaticResource.
 setRequest方法传递一个Request对象给Response对象。Listing 1.4中的代码显示了这个:[/code]
Listing 1.4. The 
Response
class' [code]setRequest
method[/code]
[/code]
sendStaticResource
 方法用来发送一个静态资源,比如HTML文件。Listing 1.5给出了它的实现过程:[/code]
Listing 1.5. The 
Response
class' [code]sendStaticResource
method[/code]
[/code]
sendStaticResource
方法是非常简单的。它首先传递父路径和子路径给File类的构造器,从而对
java.io.File
类进行了实例化。[/code]
[/code]
然后它检查文件是否存在。如果存在,
sendStaticResource
 方法通过传递File对象来构造一个[code]java.io.FileInputStream
对象。然后调用
FileInputStream
的read方法,将字节流写如到
OutputStream
输出。注意这种情况下, 静态资源的内容也被作为原始数据被发送给了浏览器。[/code]
[/code]
如果这个文件不存在,
sendStaticResource
方法发送一个错误消息给浏览器。

[/code]

编译和运行应用程序

为了编译和运行应用,你首先需要解压包含本文应用程序的.zip文件。你解压的目录成为工作目录(working directory),它有三个子目录: src/, classes/, 和 lib/。 要编译应用程序需要在工作目录输入如下语句:

[/code]
这个
-d
 选项参数将结果写到当前目录,而不是src/ 目录。

要运行应用程序,在工作目录中输入如下语句:

[/code]
要测试你的应用程序,打开浏览器,在地址栏中输入如下URL:

[/code]
你将可以看到浏览器中显示的index.html 页面,如Figure 1所示。



Figure 1. The output from the web server

在控制台(Console),你能看到如下内容:

[/code]

 

概要总结

在本文中,你了解了一个简单的WEB服务器的工作机制。本文附带的应用程序源代码只包含三个类,但并不是所有的都有用。尽管如此,它还是能被作为一种很好的学习工具为我们服务。

Translated by Willpower,2003.11.24


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息