Java Web服务器都是如何工作的
2007-11-17 15:15
309 查看
How Java Web Servers Work
by Budi Kurniawan04/23/2003编辑批注: [b]本文改编自 Budi [b]个人出版的关于TOMCAT[b]内部实现原理的书.[b]你可以到他的网站上找到更多这方面的信息.[/b][/b][/b][/b]web server 也叫 Hypertext Transfer Protocol (HTTP) server, 因为它使用的是HTTP协议与客户端通信, 通常是指那些 web 浏览器. 基于 Java 的web server 使用2个重要的类,java.net.Socket和
java.net.ServerSocket, 进行HTTP消息通信. 因此,本文先讨论HTTP协议和这2个类. 之后, 我会讲解本书附带的一个简单的web server 程序.
The Hypertext Transfer Protocol (HTTP)
HTTP 协议能够让 web servers 和浏览器通过 Internet 发送和接收数据. 它是一种请求-响应式的协议—客户端发起一个请求,服务器响应这个请求. HTTP 使用可信赖的TCP 连接, 默认使用的是 TCP 的80 端口. HTTP协议的第一个版本是 HTTP/0.9, 然后是 HTTP/1.0. 当前版本是由 RFC 2616(.pdf) 定义的HTTP/1.1.这部分简短的介绍了一下 HTTP 1.1; 足可以让你理解 web server 应用程序发出的消息. 如果你有兴趣了解更多的细节,请阅读 RFC 2616.使用 HTTP 的时候, 客户端总是通过建立一个连接来开始一个事务,然后发送一个 HTTP 请求. server 不会主动联系客户端,也不会建立一个回调连接到客户端(is in no position to contact a client or to make a callback connection to the client). 客户端和服务器都可以提前中止一个连接. 例如, 当使用 web 浏览器的时候, 你可以点击浏览器上的停止 按钮结束文件的下载过程, 有效关闭与 web 服务器的HTTP 连接.HTTP Requests
一个 HTTP 请求由3部分组成:Method-URI-Protocol/VersionRequest headersEntity body一个 HTTP 请求的例子如下:method-URI-Protocol/Version 出现在请求的第1行.POST是请求的方式,
/servlet/default.jsp表示 URI,
HTTP/1.1是 Protocol/Version 这段的值.每个 HTTP 请求都可以使用诸多请求方式中的任何一种, 这些请求方式都由HTTP 标准制定. HTTP 1.1 支持7种请求:
GET,
POST,
HEAD,
OPTIONS,
PUT,
DELETE, 和
TRACE.
GET和
POST是在 Internet 程序中最常使用的2种请求.URI 完整的指定了一个 Internet 资源. URI 通常以相对于服务器的根目录来进行解析. 因此, 应该总是以
/
开始. URL 实际是 URI 的一种. 协议版本表示正在使用的HTTP 协议的版本号.请求头包含了客户端环境和请求主体的有用信息. 例如, 包含浏览器的语言设置,请求主体的长度, 等. 每个请求头被 CRLF 序列分隔开.在头和主体之间有一个非常重要的空白行 (CRLF sequence) . 这一行标志着请求主体的开始. 一些 Internet 编程书认为这个 CRLF 是 HTTP 请求的第4个组成部分.在前面的HTTP 请求, 请求主体只是简单的如下这么一行:在一个更典型的HTTP 请求里, 请求主体可以比这更长.
HTTP Responses
类似于请求, HTTP 响应也由3部分组成:Protocol-Status code-DescriptionResponse headersEntity body下面是一个 HTTP 响应的例子:响应头的第一行很类似于请求头的第一行. 第一行告诉你使用的协议是 HTTP version 1.1, 请求成功 (200 = success), 一切都完全正确.类似于请求头, 响应头也包含了有用的信息. 响应主体是 HTML 内容. 头和主体也以 CRLFs 序列分隔.The Socket Class
一个socket是一次网络连接的一个端点. socket 能够使程序从网络中读取数据, 向网络中发送数据. 处于2台不同机器上的2个程序能够通过一次连接发送和接收字节流而进行通信. 要想发送一条消息给另一个程序, 你必须知道它的IP 地址, 和它的socket 端口. Java 中的socket 使用java.net.Socket类来表示.要想创建一个 socket, 你可以使用
Socket类的诸多构造器中任一种. 其中一个构造器接受主机名和端口号:
host是远程机器名或者 IP 地址,
port是远程程序的端口号. 例如, 要连接 yahoo.com 的80 端口, 你应当如下构建 socket:一旦你成功创建了
Socket类的一个实例, 你就可以使用它发送和接收字节流. 发送字节流的时候, 你必须首先调用
Socket类的
getOutputStream方法获得
java.io.OutputStream对象. 当向远程程序发送文本的时候, 通常要用返回得到的
OutputStream构造一个
java.io.PrintWriter对象. 当要接收来自连接另一端的字节流的时候, 需调用
Socket类的
getInputStream方法得到一个
java.io.InputStream
对象.下面代码片段建立了一个 socket 与本地 HTTP server (127.0.0.1 意味着一个本地主机) 通信, 发送 HTTP 请求, 从服务器接收响应. 它创建了一个
StringBuffer对象容纳响应内容, 然后打印到控制台.注意要想从 web 服务器获得正确的响应, 你必须发送一个遵从 HTTP 协议的HTTP 请求. 如果你已经阅读过了前面章节, "The Hypertext Transfer Protocol (HTTP)," 你就会理解上面代码里的HTTP 请求.
The ServerSocket
Class
Socket类代表一个 “客户端“("client")socket; 就是当你想连接到一个远端服务器程序时所构建的socket. 如果你想实现一个服务器程序, 例如 HTTP server 或者 FTP server, 你需要一种不同的方法. 这是因为你的服务器必须持久存在随时准备接收请求, 因为它根本不知道什么时候一个客户端程序要连接它.因为这个原因, 你必须使用
java.net.ServerSocket类. 这是服务器端 socket 的一个实现. 服务器端 socket 等待来自客户端的连接请求. 每当它接受了连接请求, 它就会创建一个
Socket实例去处理和客户端的通信.为创建一个服务器端 socket, 你必须使用
ServerSocket类提供的四个构造器中的一个. 你需要指定 IP 地址和这个服务器端 socket 监听的端口号码. 典型地, IP 地址会是 127.0.0.1, 意思是这个服务器端 socket 将在本地机器上监听. 服务器端 socket 所在的机器的IP 地址也被称做绑定地址. 服务器端 socket 的另一个重要属性是它的backlog, 它是指在服务器端 socket 开始拒绝新来的请求前所能忍受的连接请求队列的最大长度.
ServerSocket类的其中一个构造器有如下方法声明:对于这个构造器, 绑定地址必须是
java.net.InetAddress
的一个实例. 一种简单的构建
InetAddress对象的方式是通过调用它的静态方法
getByName, 传递一个包含主机名称的字符串给它:下面这行代码构造了一个
ServerSocket,
监听本地机器的8080 端口, backlog值设置为1.一旦你已经有了一个
ServerSocket实例, 你就可以调用它的
accept方法告诉它等待进入的连接请求. 这个方法只有在有连接请求的时候才会有返回. 它返回一个
Socket类的实例. 这个
Socket对象随后就可以用来发送和接收来自客户端程序的字节流, 就象在 The Socket Class 里解释的那样. 实际上, 在本文所附带的这个程序里
accept方法是唯一用到的方法.
The Application
我们的web 服务器程序位于包ex01.pyrmont,
由
3
个类组成:
HttpServer
Request
Response这个程序的入口点 (the static
mainmethod) 在
HttpServer类里. 它创建一个
HttpServer实例并调用它的
await方法. 就象名字所隐含的意思那样, 在指定的端口上
await等待 HTTP 请求, 处理请求, 然后向这些客户端发回响应. 它会一直保持等待直到接收到一个关闭命令为止. (方法名称使用
await而不是
wait是因为
wait是
System.Object类中一个非常重要的用于处理多线程的方法.)这个程序只发送来自指定目录下的静态资源, 象 HTML 和图片文件. 它不支持头 (像时间或者 cookies).现在, 在下面子章节里, 我们来看看这3个类.
The HttpServer
Class
HttpServer类代表一个 web server, 能提供在由public static final 变量
WEB_ROOT表示的目录下所找到的静态资源以及这个目录下的所有子目录下的静态资源.
WEB_ROOT按如下方式初始化:上面代码包含一个叫 webroot 的目录, 包含一些静态资源用于测试这个程序. 你也会发现一个 servlet, 在我的下一篇文章里将会用到, "How Servlet Containers Work."要想请求一个静态资源, 请在你的浏览器地址栏中敲入如下 URL:如果你在另一台不同的机器上跑着你的程序并发送请求, 那么
machineName是运行这个程序的计算机名称或者 IP 地址. 如果你使用的浏览器是在同一台机器上, 你可以使用
localhost作为
machineName. 端口是 8080,
staticResource是要请求的文件名, 并且该文件必须位于
WEB_ROOT.例如, 如果你使用同一台计算机测试这个程序, 并且你想要求
HttpServer返回 index.html 文件, 使用如下 URL:要想停止服务器, 在地址栏中敲入预先定义好的关闭字符串发送关闭命令. 关闭命令在
HttpServer类中定义为
SHUTDOWNstatic final 变量:因此, 要关闭服务器, 你可以使用:现在, 我们来看看 Listing 1.1 中给出的
await方法. 对代码的解释就在这个 listing 的后面.Listing 1.1. The
HttpServerclass'
awaitmethod
await开始先创建一个
ServerSocket实例, 然后进入一个
while循环.while 循环在
ServerSocket
的
accept
方法处停止, 这个方法只有当在 8080 端口接收到了一个 HTTP请求时才会返回 Socket 对象:接收到一个请求后,
await方法从
accept方法返回的
Socket实例获取一个
java.io.InputStream和
java.io.OutputStream对象.
await方法接着会创建
Request对象并调用
parse方法解析原始的HTTP 请求.再往下,
await方法创建了一个
Response对象, 把
Request对象给它, 然后调用
sendStaticResource方法.最后,
await方法关闭了
Socket并调用 Request 的
getUri方法检查是否这个 HTTP 请求是一个关闭命令. 如果是, shutdown 变量被设置为
true,
程序退出
while循环.
The Request
Class
Request类代表一个 HTTP 请求. 这个类的实例通过传递一个
InputStream对象而构建,
InputStream对象正是从处理与客户端通信的Socket 处得到的. 调用
InputStream对象的一个
read方法获取 HTTP 请求的原始数据.
Request类有2个 public 方法:
parse和
getUri.
parse方法解析 HTTP 请求的原始数据. 除此以外它不会做得更多--它的工作成果中唯一有用的是 HTTP 请求的URI, 它也是通过调用 private 方法
parseUri
而获得这个
URI.
parseUri方法使用
uri变量存储 URI. 调用 public
getUri方法以返回得到 HTTP 请求的URI.要想搞明白
parse和
parseUri方法是怎样工作的, 你需要知道 HTTP 请求的结构, 这是由 RFC 2616(.pdf) 定义的.一个 HTTP 请求包含3部分:Request lineHeadersMessage body现在, 我们只对 HTTP 请求的第一部分感兴趣, request line. request line 以请求方式开头, 跟着是请求 URI 和协议版本, 以 (CRLF) 结束. request line 中的各部分以空格分隔. 例如, 对于请求 index.html 文件的请求的request line 使用
GET方式:
parse方法从传递给
Request对象的得自socket 的InputStream 读取全部字节流, 保存这些字节数组到 buffer. 然后将 buffer 字节数组中的bytes 移到一个叫 request 的
StringBuffer对象中, 接着把这个
StringBuffer对象的
String形式传递给
parseUri方法.Listing 1.2 给出了
parse方法.Listing 1.2. The
Requestclass'
parsemethod
parseUri方法随后从 request line 获得 URI. Listing 1.3 展示了
parseUri方法.
parseUri方法搜索 request 中的第一个和第二个空白, 2者之间就是 URI.Listing 1.3.
Request类的
parseUri方法
The Response
Class
Response类表示 HTTP 响应. 它的构造器接收一个
OutputStream对象, 如下:
Response对象在
HttpServer类的
await方法中通过传递
OutputStream对象(从 socket 获得)来构造.
Response类有2个公共方法:
setRequest和
sendStaticResource.
setRequest方法用于传递一个
Request对象给
Response对象. 就象 Listing 1.4 中的代码那样简单.Listing 1.4. The
Responseclass'
setRequestmethod
sendStaticResource方法用于发送静态资源, 象一个 HTML 文件. Listing 1.5 给出了它的实现.Listing 1.5. The
Responseclass'
sendStaticResourcemethod
sendStaticResource方法非常简单. 首先传递父和孩子路径给 File 类的构造器实例化一个
java.io.File类.然后检查文件是否存在. 如果存在,
sendStaticResource方法通过传递
File对象构造一个
java.io.FileInputStream对象. 接着调用
FileInputStream的
read方法, 向
OutputStream输出写入 byte 数组. 注意, 在这种情况下, 静态资源的内容将作为原始数据发送给浏览器.如果文件不存在,
sendStaticResource方法发送一个错误消息给浏览器.
Compiling and Running the Application
要想编译和运行程序, 首先需要解压包含本文示例程序的.zip 文件. 解压 .zip 文件所在的目录叫做工作目录, 而且会有3个子目录: src/, classes/, 和 lib/. 在工作路径下敲入如下命令编译程序:-d选项将结果写到当前而非 src/目录下.在工作目录敲下如下输入运行程序:测试程序时, 打开你的浏览器并在地址栏中输入如下 URL :你会看到 index.html 页面展示于你的浏览器中, 如 Figure 1.Figure 1. The output from the web server在控制台上, 你会看见如下输出:
Summary
在这篇文章里, 你已经看到了一个简单的web server是如何工作. 本文所带的程序只由3个类组成, 功能并不完善. 不管怎样, 它是好的学习工具.Budi Kurniawan is a senior J2EE architect and author.相关文章推荐
- 关于如何在java的web应用获取服务器资源。
- (译)Web是如何工作的(2):客户端-服务器模型,以及Web应用程序的结构
- 如何用Java实现Web服务器
- 如何在Tomcat或其他基于Java的Web服务器下安装SSL证书
- 关于如何将java web项目上传至腾讯云服务器
- Java本地web项目页面如何直接访问另一台服务器的图片文件
- 基于JAVA的WEB服务器工作机制(2)
- 如何做好WEB服务器后门的防范工作
- 浅谈用java搭建web服务器之Socket编程——更好的理解Apache、Tomcat等软件的工作模式
- 基于JAVA的WEB服务器工作机制
- 【javaweb】Session原理以及浏览器禁止Cookie之后服务器如何获取Session
- 如何通过Java的Web服务器把DataSet或DataTable数据通过json格式传回给C#的客户端
- 基于JAVA的WEB服务器工作机制(3)完
- 如何用Java实现Web服务器(转-->)
- 当JAVA WEB服务器端有一个执行时间长的方法,应该如何处理?
- 如何用java代码访问远程web服务器(手写一个自己的httpClient)
- 如何把自己的JavaWeb放到自己的服务器上
- Java中如何让web服务器启动的时候自动运行web程序中某个类的某个方法
- Java中如何让web服务器启动的时候自动运行web程序中某个类的某个方法
- 如何用Java实现Web服务器(转-->)