- 浏览: 19994 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
yangqianyu222:
<div class="quote_title ...
译How Tomcat Works(第一章) -
yangqianyu222:
gosin 写道这么厉害啊。
你把代码跑起来没有?
哈哈,没 ...
译How Tomcat Works(第四章) -
yaobiao753:
有ModernServlet的源代码么?我下的怎么没有这个了? ...
译How Tomcat Works(第一章) -
yaobiao753:
翻译的不错~刚看了英文的前四章。看看你的翻译复习一下,呵呵
译How Tomcat Works(第一章) -
gosin:
这么厉害啊。
你把代码跑起来没有?
译How Tomcat Works(第四章)
第一章:简单的web server
这章将解释Java web server 是如何工作的。Web server 也叫超文本传输协议(HTTP)server,因为它使用HTTP 连接客户端,客户端一般指web 浏览器。基于java的web server使用两个重要的类:java.net.Socket 和 java.net.ServerSocket,连接工作是通过HTTP 消息完成的。因此自然我们从HTTP和这两个类开始讨论这章。在这章我们将继续解释简单web server应用。
超文本传输协议(HTTP)
HTTP是允许web server 和浏览器之间通过Internet发送和接收数据的协议,它是一个request和response的协议。客户端请求一个文件,服务器端响应这个请求。HTTP使用可靠的TCP连接,默认端口为80。HTTP的第一个版本是0.9,后来被HTTP/1.0所取代。HTTP/1.1是目前的版本。在Request for Comments(RFC)2616中有定义,可以从这里下载http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf。
注:这部分关于HTTP1.1的简介是为了帮助你理解web server应用上的消息的发送。如果你感兴趣,可以阅读RFC 2616。
HTTP,是客户端通过建立连接发起一个事务并发送HTTP请求。Web server 适时的连接客户端或者做回调连接给客户端。客户端和server都可以提早结束一个连接。例如,当用web 浏览器时,你可以点击浏览器上的停止按钮来停止下载一个文件的进程,有效地关闭与web server间HTTP 连接。
HTTP request
一个HTTP请求包括三部分:
方法—URI—协议/版本号(Method—Uniform Resource Identifier (URI)—Protocol/Version)
请求头部信息(Request headers)
实体(Entity body)
下面是一个HTTP请求的例子:
POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
lastName=Franks&firstName=Michael
方法—URI—协议/版本号在request的第一行显示
POST /examples/default.jsp HTTP/1.1
POST是request的方法,/examples/default.jsp是URI和HTTP/1.1是协议/版本号部分。
每个HTTP 请求可以使用多个request方法中的一个, HTTP标准中有详细的说明。HTTP1.1支持七种类型的请求:GET,POST,HEAD,PUT,DELETE和TRACE。GET和POST是最常用的Internet应用。
URI详细指明了一个完整的Internet资源。URI通常是指server的相对根路径。因此,URI总是以“/”开头。URL是URI的一个实例(详看http://www.ietf.org/rfc/rfc2396.txt)。
请求头包含了客户端环境和request实体的有用信息。例如,它可能包括浏览器设置的语言,实体的长度等等。每个header被一个CRLF序列分隔的。
在headers 与实体之间,有一个空行(CRLF),这HTTP 请求格式是非常重要的。CRLF 告诉HTTP server 实体从哪里开始。有些Internet编程书籍中,CRLF被认为是HTTP 请求的第四个组成部分。
在上面的HTTP 请求中,实体仅有下面这行:
lastName=Franks&firstName=Michael
在典型的HTTP请求中,实体可能变得很长。
HTTP Response
类似于HTTP request,一个HTTP response 也包括三个部分:
协议—状态码—描述(Protocol—Status code—Description)
响应头(Response headers)
实体(Entity body)
下面是一个HTTP response的例子:
HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
Content-Length: 112
<html>
<head>
<title>HTTP Response Example</title>
</head>
<body>
Welcome to Brainy Software
</body>
</html> Response 头的第一行与request头的第一行类似,第一行告诉你使用的HTTP第一行告诉我们HTTP版本号是1.1,请求成功(200=成功),万事OK。
Response头与request头一样都包含了很多有用的信息。实体部分是response本身的HTML内容。Response头和实体通过CRLF序列分割开。
Socket类
Socket 是网络连接的一个终端。Socket使应用程序从网络读和写成为可能。在两个不同计算机上的应用软件连接后可以通过发送和接收字节流彼此通信。两个不同主机上的应用程序通过一个连接可以彼此发送和接收字节流。从你的应用程序上发送一个消息到另外一个应用程序上,你需要知道另外一个应用的IP地址和Socket端口号。在java中,socket由java.net.Socket类展现。
要创建一个socket,你可以使用socket类的构造函数,有一个构造函数接收主机名和端口号:
public Socket (java.lang.String host, int port)
host是远程计算机的名字或IP地址,port是远程应用程序的端口号。例如,要通过80端口连接yahoo.com.,你可以这样创建Socket 对象:
new Socket ("yahoo.com", 80);
一旦你成功创建了Socket实例,你就可以用它发送和接收字节流了。要发送字节流,必须首先调用Socket类的getOutputStream方法以获得一个java.io.OutputStream对象。要发送文本到一个远程应用程序,通常要构建一个java.io.PrintWriter对象,它是从OutputStream对象返回的。要从连接的其他终端接收字节流,你须要调用Socket类的getInputStream方法,这个方法返回一个java.io.InputStream对象。
下面的代码片段创建了一个Socket,它能与本地HTTP server(127.0.0.1 表示本地主机)通信,发送一个HTTP请求,并且从服务器接收响应。创建一个StringBuffer对象来存储响应,并在控制台打印出来。
Socket socket = new Socket("127.0.0.1", "8080");
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(
socket.getOutputStream(), autoflush);
BufferedReader in = new BufferedReader(
new InputStreamReader( socket.getInputstream() ));
// send an HTTP request to the web server
out.println("GET /index.jsp HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
// read the response
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
while (loop) {
if ( in.ready() ) {
int i=0; while (i!=-1) {
i = in.read();
sb.append((char) i);
}
loop = false;
}
Thread.currentThread().sleep(50);
}
// display the response to the out console
System.out.println(sb.toString());
socket.close();
注意要从web server 获得正确的response,你需要发送一个遵循HTTP协议的HTTP请求。如果你读了前一章节HTTP,你应该会理解上面的代码。
注:如果你使用本书中的com.brainysoftware.pyrmont.util.HttpSniffer类发送HTTP请求并显示响应,你必须连接到Internet。如果你使用防火墙将发出警告,可能无法运行该程序。
ServerSocket 类
Socket类展现的是一个“客户端”socket,例如,只要你想要连接一个远程应用服务,你就要创建一个socket。现在,如果你想要实现一个应用服务,如一个HTTP server 或者一个FTP server,这就要用不同的方法。这是因为你的服务器必须一直待命,因为不知道客户端应用程序会在什么时候连接它。为了使你的应用程序一直待命,需要使用java.net.ServerSocket类。它是server socket 的一个实现。
ServerSocket 和 Socket不同。Server Socket等待来自客户端的连接请求。一旦Server Socket 获得一个连接请求,便创建一个Socket 实例来处理与客户端的通信。
要创建一个 Server Socket,可以使用ServerSocket类的一个构造方法。需要写明IP地址和Server Socket将要监听的端口号。最具代表性的IP地址是127.0.0.1,意味着Server Socket 将监听本地计算机。ServerSocket 监听中的这个IP地址被作为绑定地址。ServerSocket的另一个重要的属性是它的backlog,backlog是在ServerSocket开始拒绝连接请求前的最大队列长度。
ServerSocket类的其中一个构造函数如下:
public ServerSocket(int port, int backLog, InetAddress bindingAddress);
注意这个构造函数,绑定的地址必须是java.net.InetAddress的一个实例。构造一个InetAddress对象的最简单的方法是调用静态方法getByName,传递一个包含主机名的字符串,类似下面代码:
InetAddress.getByName("127.0.0.1");
下面这行代码是构建一个ServerSocket在本地主机8080端口上的一个监听。这个ServerSocket的backlog 是1。
new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
一旦你创建了ServerSocket的实例,你可以通知他等待来自绑定地址的监听端口上的连接请求。要做到这点,你可以调用ServerSocket类的 accept 方法。这个方法只有当有一个连接请求时才返回,返回的值是一个Socket类实例。这个Socket对象可以用来从客户端应用程序发送和接收字节流。前一部分“Socket类”已有解释。实际上,这个接收方法是这章使用的唯一一个方法。
应用程序
该应用程序阻碍ex01.pyrmont 包中,它由三部分组成
HttpServer
Request
Response
该程序的切入点(静态main方法) 可以在HttpServer 类中找到。main方法创建了一个HttpServer的实例,并调用await方法。await方法,如名字所示,等待指定端口上的HTTP请求,处理他们,然后返回给客户端。它将保持等待状态直到接到关闭命令。
该应用程序不能发送所有的静态资源,如指定路径下的HTML文件和图片文件。它可以在控制台上打印出HTTP 请求字节流。但是它不发送任何header,如date 或者Cookies等到浏览器。
现在就让我们来看一下这三个类。
HttpServer类
HttpServer类展示了一个web server,清单1.1。await方法在清单1.2中给出。
清单1.1:HttpServer 类
package ex01.pyrmont;
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 java.io.File;
public class HttpServer {
/** WEB_ROOT is the directory where our HTML and other files reside.
* For this package, WEB_ROOT is the "webroot" directory under the
* working directory.
* The working directory is the location in the file system
* from where the java command was invoked.
*/
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
// shutdown command
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
// the shutdown command received
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
public void await() {
...
}
}
清单1.2: HttpServer 类 的 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);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace ();
continue;
}
}
}
这个web server可以用于WEB_ROOT目录及其子目录下的静态资源,WEB_ROOT初始化如下:
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
上面代码监听路径是webroot ,webroot下其包括一些静态资源,你可以使用它们测试,你也可以在此目录下找到其他Servlet测试下一章的应用程序。要请求一个静态资源,你可以在浏览器地址栏中输入:
http://machineName:port/staticResource
如果你是在不同的主机上发送请求运行程序, machineName是运行此程序的主机名或者IP地址。 如果你是在同一机器上运行,你可以用 localhost 代替machineName,端口是 8080,staticResource是请求的文件的名字,它必须位于 WEB_ROOT下。
例如,如果你使用同一计算机运行此程序,你想要请求 HttpServer 对象发送index.html 文件, 你应该输入如下URL
http://localhost:8080/index.html
要停止该服务,你可以在浏览器地址栏的 host:port后输入字符串发送关闭命令
关闭命令是HttpServer 类中定义的static final变量 SHUTDOWN_COMMAND:
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
因此,要停止server,可以用以下的URL:
http://localhost:8080/SHUTDOWN
现在让我们看看代码清单1.2中await方法。
用await这个名字代替wait,是因为wait 是一个重要的方法。在 java.lang.Object 类中是与线程相关的方法。
await 方法从创建ServerSocket 实例开始,然后进入while循环。
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
// Loop waiting for a request
while (!shutdown) {
...
}
while循环内,accept方法仅当有一个HTTP请求在8080端口上被接收时才返回socket。
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 对象,将 Request 对象设置进去, 并调用sendStaticResource 方法。
// create Response object...
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
最后, await 方法关闭Socket 并调用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);
Request 类
ex01.pyrmont.Request 类展示了一个HTTP 请求。该类的一个实例通过传入一个InputStream对象创建,该InputStream对象从处理与客户端通信的Socket获得。调用InputStream 对象的read方法获得HTTP 请求的原始数据。
Request 类代码清单如1.3所示。该Request 类有两个公用方法, parse and getUri, 这两个方法的清单在1.4 and 1.5中分别给出。
清单1.3: Request类
package ex01.pyrmont;
import java.io.InputStream;
import java.io.IOException;
public class Request {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public void parse() {
...
}
private String parseUri(String requestString) {
...
}
public String getUri() {
return uri;
}
}
清单 1.4:Request 类的 parse 方法
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());
}
清单1.5:Request类的 parseUri 方法
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;
}
parse 方法解析HTTP 请求中的原始数据。这个方法没有做很多事。唯一有用的信息就是HTTP请求的 URI,调用parseUri可以获得URI。parseUri 方法用uri 变量存储URI。调用getUri 方法返回 HTTP 请求的URI。
注:更多关于处理HTTP请求的原始数据的程序将在第三章介绍
想要理解parseUri如何解析,你需要知道HTTP Request 的结构,在前一部分我们讨论了 "超文本传输协议 (HTTP)"。 这一章,我们只研究 HTTP request的第一部分,request行。 request 行以方法的记开始,紧跟着是request URI和协议版本号信息,以CRLF结束。request 行中的元素通过空格分隔。例如,index.html文件使用GET 方法的Request行写法如下:
GET /index.html HTTP/1.1
解析方法读取来自socket的 InputStream的整个字节流, InputStream作为 Request对象的参数,并且以bytes数组的形式存储在buffer中。然后被组成一个StringBuffer对象,Request使用buffer中的byte 数组, 并将该StringBuffer的字符串形式(toString()方法)作为 parseUri 方法的参数。解析方法代码在清单1.4中给出。
parseUri 方法从request行获取。 清单 1.5展示了parseUri 方法。parseUri 方法在request中查找第一个和第二个空格以获取URI 。
Response 类
ex01.pyrmont.Response 类 展示的是一个 HTTP response 将在清单1.6中给出
Listing 1.6: Response 类
package ex01.pyrmont;
import java.io.OutputStream; import java.io.IOException;
import java.io.FileInputStream;
import java.io.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
*/
public class Response {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(HttpServer.WEB_ROOT, request.getUri());
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
else {
// file not found
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());
}
}
catch (Exception e) {
// thrown if cannot instantiate a File object
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
}
首先,我们看这个类的构造函数需要一个java.io.OutputStream 参数
public Response(OutputStream output) {
this.output = output;
}
传递一个OutputStream对象给HttpServer,HttpServer类就会创建一个Response对象。这个OutputStream对象是从socket获得的。
Response类有两个公用方法:setRequest 和 sendStaticResource
setRequest方法用来设置一个Request对象给Response对象。
sendStaticResource 方法用来发送一个静态资源,如一个HTML 页面。首先要通过构造函数创建一个java.io.File对象,参数为文件父路径及子路径。
File file = new File(HttpServer.WEB_ROOT, request.getUri());
然后检测该文件是否存在。如果存在,sendStaticResource将该file对象作为参数构造一个java.io.FileInputStream对象。然后调用FileInputStream 的read 方法和OutputStream的 write方法将byte数组写入OutputStream类型的 output对象。注意这种情况下,静态资源的内容是作为原始数据被发送到浏览器的。
if (file.exists()) {
fis = new FileInputstream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
如果文件不存在,sendStaticResource方法将发送一个错误信息到浏览器。
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());
运行该程序
在浏览器中输入如下地址测试该程序:
http://localhost:8080/index.html
你将看到 index.html页面显示在浏览器中,如图1.1所示:
.
图 1.1: web server 的输出
在控制台,你可以看到如下所示的HTTP请求:
GET /index.html HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,
application/vnd.ms-excel, application/msword, application/vnd.ms-
powerpoint, application/x-shockwave-flash, application/pdf, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive
GET /images/logo.gif HTTP/1.1
Accept: */*
Referer: http://localhost:8080/index.html
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive
总结
阅读完这章,你已经知道了一个简单的web server是如何工作的了。这章的应用程序仅由三个类组成,功能不是很强大,但它是个很好的学习工具。下一章将讨论动态内容的处理。
我也没找到,最近忙,有空我再去找找。
这章将解释Java web server 是如何工作的。Web server 也叫超文本传输协议(HTTP)server,因为它使用HTTP 连接客户端,客户端一般指web 浏览器。基于java的web server使用两个重要的类:java.net.Socket 和 java.net.ServerSocket,连接工作是通过HTTP 消息完成的。因此自然我们从HTTP和这两个类开始讨论这章。在这章我们将继续解释简单web server应用。
超文本传输协议(HTTP)
HTTP是允许web server 和浏览器之间通过Internet发送和接收数据的协议,它是一个request和response的协议。客户端请求一个文件,服务器端响应这个请求。HTTP使用可靠的TCP连接,默认端口为80。HTTP的第一个版本是0.9,后来被HTTP/1.0所取代。HTTP/1.1是目前的版本。在Request for Comments(RFC)2616中有定义,可以从这里下载http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf。
注:这部分关于HTTP1.1的简介是为了帮助你理解web server应用上的消息的发送。如果你感兴趣,可以阅读RFC 2616。
HTTP,是客户端通过建立连接发起一个事务并发送HTTP请求。Web server 适时的连接客户端或者做回调连接给客户端。客户端和server都可以提早结束一个连接。例如,当用web 浏览器时,你可以点击浏览器上的停止按钮来停止下载一个文件的进程,有效地关闭与web server间HTTP 连接。
HTTP request
一个HTTP请求包括三部分:
方法—URI—协议/版本号(Method—Uniform Resource Identifier (URI)—Protocol/Version)
请求头部信息(Request headers)
实体(Entity body)
下面是一个HTTP请求的例子:
POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
lastName=Franks&firstName=Michael
方法—URI—协议/版本号在request的第一行显示
POST /examples/default.jsp HTTP/1.1
POST是request的方法,/examples/default.jsp是URI和HTTP/1.1是协议/版本号部分。
每个HTTP 请求可以使用多个request方法中的一个, HTTP标准中有详细的说明。HTTP1.1支持七种类型的请求:GET,POST,HEAD,PUT,DELETE和TRACE。GET和POST是最常用的Internet应用。
URI详细指明了一个完整的Internet资源。URI通常是指server的相对根路径。因此,URI总是以“/”开头。URL是URI的一个实例(详看http://www.ietf.org/rfc/rfc2396.txt)。
请求头包含了客户端环境和request实体的有用信息。例如,它可能包括浏览器设置的语言,实体的长度等等。每个header被一个CRLF序列分隔的。
在headers 与实体之间,有一个空行(CRLF),这HTTP 请求格式是非常重要的。CRLF 告诉HTTP server 实体从哪里开始。有些Internet编程书籍中,CRLF被认为是HTTP 请求的第四个组成部分。
在上面的HTTP 请求中,实体仅有下面这行:
lastName=Franks&firstName=Michael
在典型的HTTP请求中,实体可能变得很长。
HTTP Response
类似于HTTP request,一个HTTP response 也包括三个部分:
协议—状态码—描述(Protocol—Status code—Description)
响应头(Response headers)
实体(Entity body)
下面是一个HTTP response的例子:
HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
Content-Length: 112
<html>
<head>
<title>HTTP Response Example</title>
</head>
<body>
Welcome to Brainy Software
</body>
</html> Response 头的第一行与request头的第一行类似,第一行告诉你使用的HTTP第一行告诉我们HTTP版本号是1.1,请求成功(200=成功),万事OK。
Response头与request头一样都包含了很多有用的信息。实体部分是response本身的HTML内容。Response头和实体通过CRLF序列分割开。
Socket类
Socket 是网络连接的一个终端。Socket使应用程序从网络读和写成为可能。在两个不同计算机上的应用软件连接后可以通过发送和接收字节流彼此通信。两个不同主机上的应用程序通过一个连接可以彼此发送和接收字节流。从你的应用程序上发送一个消息到另外一个应用程序上,你需要知道另外一个应用的IP地址和Socket端口号。在java中,socket由java.net.Socket类展现。
要创建一个socket,你可以使用socket类的构造函数,有一个构造函数接收主机名和端口号:
public Socket (java.lang.String host, int port)
host是远程计算机的名字或IP地址,port是远程应用程序的端口号。例如,要通过80端口连接yahoo.com.,你可以这样创建Socket 对象:
new Socket ("yahoo.com", 80);
一旦你成功创建了Socket实例,你就可以用它发送和接收字节流了。要发送字节流,必须首先调用Socket类的getOutputStream方法以获得一个java.io.OutputStream对象。要发送文本到一个远程应用程序,通常要构建一个java.io.PrintWriter对象,它是从OutputStream对象返回的。要从连接的其他终端接收字节流,你须要调用Socket类的getInputStream方法,这个方法返回一个java.io.InputStream对象。
下面的代码片段创建了一个Socket,它能与本地HTTP server(127.0.0.1 表示本地主机)通信,发送一个HTTP请求,并且从服务器接收响应。创建一个StringBuffer对象来存储响应,并在控制台打印出来。
Socket socket = new Socket("127.0.0.1", "8080");
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(
socket.getOutputStream(), autoflush);
BufferedReader in = new BufferedReader(
new InputStreamReader( socket.getInputstream() ));
// send an HTTP request to the web server
out.println("GET /index.jsp HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
// read the response
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
while (loop) {
if ( in.ready() ) {
int i=0; while (i!=-1) {
i = in.read();
sb.append((char) i);
}
loop = false;
}
Thread.currentThread().sleep(50);
}
// display the response to the out console
System.out.println(sb.toString());
socket.close();
注意要从web server 获得正确的response,你需要发送一个遵循HTTP协议的HTTP请求。如果你读了前一章节HTTP,你应该会理解上面的代码。
注:如果你使用本书中的com.brainysoftware.pyrmont.util.HttpSniffer类发送HTTP请求并显示响应,你必须连接到Internet。如果你使用防火墙将发出警告,可能无法运行该程序。
ServerSocket 类
Socket类展现的是一个“客户端”socket,例如,只要你想要连接一个远程应用服务,你就要创建一个socket。现在,如果你想要实现一个应用服务,如一个HTTP server 或者一个FTP server,这就要用不同的方法。这是因为你的服务器必须一直待命,因为不知道客户端应用程序会在什么时候连接它。为了使你的应用程序一直待命,需要使用java.net.ServerSocket类。它是server socket 的一个实现。
ServerSocket 和 Socket不同。Server Socket等待来自客户端的连接请求。一旦Server Socket 获得一个连接请求,便创建一个Socket 实例来处理与客户端的通信。
要创建一个 Server Socket,可以使用ServerSocket类的一个构造方法。需要写明IP地址和Server Socket将要监听的端口号。最具代表性的IP地址是127.0.0.1,意味着Server Socket 将监听本地计算机。ServerSocket 监听中的这个IP地址被作为绑定地址。ServerSocket的另一个重要的属性是它的backlog,backlog是在ServerSocket开始拒绝连接请求前的最大队列长度。
ServerSocket类的其中一个构造函数如下:
public ServerSocket(int port, int backLog, InetAddress bindingAddress);
注意这个构造函数,绑定的地址必须是java.net.InetAddress的一个实例。构造一个InetAddress对象的最简单的方法是调用静态方法getByName,传递一个包含主机名的字符串,类似下面代码:
InetAddress.getByName("127.0.0.1");
下面这行代码是构建一个ServerSocket在本地主机8080端口上的一个监听。这个ServerSocket的backlog 是1。
new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
一旦你创建了ServerSocket的实例,你可以通知他等待来自绑定地址的监听端口上的连接请求。要做到这点,你可以调用ServerSocket类的 accept 方法。这个方法只有当有一个连接请求时才返回,返回的值是一个Socket类实例。这个Socket对象可以用来从客户端应用程序发送和接收字节流。前一部分“Socket类”已有解释。实际上,这个接收方法是这章使用的唯一一个方法。
应用程序
该应用程序阻碍ex01.pyrmont 包中,它由三部分组成
HttpServer
Request
Response
该程序的切入点(静态main方法) 可以在HttpServer 类中找到。main方法创建了一个HttpServer的实例,并调用await方法。await方法,如名字所示,等待指定端口上的HTTP请求,处理他们,然后返回给客户端。它将保持等待状态直到接到关闭命令。
该应用程序不能发送所有的静态资源,如指定路径下的HTML文件和图片文件。它可以在控制台上打印出HTTP 请求字节流。但是它不发送任何header,如date 或者Cookies等到浏览器。
现在就让我们来看一下这三个类。
HttpServer类
HttpServer类展示了一个web server,清单1.1。await方法在清单1.2中给出。
清单1.1:HttpServer 类
package ex01.pyrmont;
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 java.io.File;
public class HttpServer {
/** WEB_ROOT is the directory where our HTML and other files reside.
* For this package, WEB_ROOT is the "webroot" directory under the
* working directory.
* The working directory is the location in the file system
* from where the java command was invoked.
*/
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
// shutdown command
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
// the shutdown command received
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
public void await() {
...
}
}
清单1.2: HttpServer 类 的 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);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace ();
continue;
}
}
}
这个web server可以用于WEB_ROOT目录及其子目录下的静态资源,WEB_ROOT初始化如下:
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
上面代码监听路径是webroot ,webroot下其包括一些静态资源,你可以使用它们测试,你也可以在此目录下找到其他Servlet测试下一章的应用程序。要请求一个静态资源,你可以在浏览器地址栏中输入:
http://machineName:port/staticResource
如果你是在不同的主机上发送请求运行程序, machineName是运行此程序的主机名或者IP地址。 如果你是在同一机器上运行,你可以用 localhost 代替machineName,端口是 8080,staticResource是请求的文件的名字,它必须位于 WEB_ROOT下。
例如,如果你使用同一计算机运行此程序,你想要请求 HttpServer 对象发送index.html 文件, 你应该输入如下URL
http://localhost:8080/index.html
要停止该服务,你可以在浏览器地址栏的 host:port后输入字符串发送关闭命令
关闭命令是HttpServer 类中定义的static final变量 SHUTDOWN_COMMAND:
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
因此,要停止server,可以用以下的URL:
http://localhost:8080/SHUTDOWN
现在让我们看看代码清单1.2中await方法。
用await这个名字代替wait,是因为wait 是一个重要的方法。在 java.lang.Object 类中是与线程相关的方法。
await 方法从创建ServerSocket 实例开始,然后进入while循环。
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
// Loop waiting for a request
while (!shutdown) {
...
}
while循环内,accept方法仅当有一个HTTP请求在8080端口上被接收时才返回socket。
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 对象,将 Request 对象设置进去, 并调用sendStaticResource 方法。
// create Response object...
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
最后, await 方法关闭Socket 并调用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);
Request 类
ex01.pyrmont.Request 类展示了一个HTTP 请求。该类的一个实例通过传入一个InputStream对象创建,该InputStream对象从处理与客户端通信的Socket获得。调用InputStream 对象的read方法获得HTTP 请求的原始数据。
Request 类代码清单如1.3所示。该Request 类有两个公用方法, parse and getUri, 这两个方法的清单在1.4 and 1.5中分别给出。
清单1.3: Request类
package ex01.pyrmont;
import java.io.InputStream;
import java.io.IOException;
public class Request {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public void parse() {
...
}
private String parseUri(String requestString) {
...
}
public String getUri() {
return uri;
}
}
清单 1.4:Request 类的 parse 方法
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());
}
清单1.5:Request类的 parseUri 方法
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;
}
parse 方法解析HTTP 请求中的原始数据。这个方法没有做很多事。唯一有用的信息就是HTTP请求的 URI,调用parseUri可以获得URI。parseUri 方法用uri 变量存储URI。调用getUri 方法返回 HTTP 请求的URI。
注:更多关于处理HTTP请求的原始数据的程序将在第三章介绍
想要理解parseUri如何解析,你需要知道HTTP Request 的结构,在前一部分我们讨论了 "超文本传输协议 (HTTP)"。 这一章,我们只研究 HTTP request的第一部分,request行。 request 行以方法的记开始,紧跟着是request URI和协议版本号信息,以CRLF结束。request 行中的元素通过空格分隔。例如,index.html文件使用GET 方法的Request行写法如下:
GET /index.html HTTP/1.1
解析方法读取来自socket的 InputStream的整个字节流, InputStream作为 Request对象的参数,并且以bytes数组的形式存储在buffer中。然后被组成一个StringBuffer对象,Request使用buffer中的byte 数组, 并将该StringBuffer的字符串形式(toString()方法)作为 parseUri 方法的参数。解析方法代码在清单1.4中给出。
parseUri 方法从request行获取。 清单 1.5展示了parseUri 方法。parseUri 方法在request中查找第一个和第二个空格以获取URI 。
Response 类
ex01.pyrmont.Response 类 展示的是一个 HTTP response 将在清单1.6中给出
Listing 1.6: Response 类
package ex01.pyrmont;
import java.io.OutputStream; import java.io.IOException;
import java.io.FileInputStream;
import java.io.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
*/
public class Response {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(HttpServer.WEB_ROOT, request.getUri());
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
else {
// file not found
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());
}
}
catch (Exception e) {
// thrown if cannot instantiate a File object
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
}
首先,我们看这个类的构造函数需要一个java.io.OutputStream 参数
public Response(OutputStream output) {
this.output = output;
}
传递一个OutputStream对象给HttpServer,HttpServer类就会创建一个Response对象。这个OutputStream对象是从socket获得的。
Response类有两个公用方法:setRequest 和 sendStaticResource
setRequest方法用来设置一个Request对象给Response对象。
sendStaticResource 方法用来发送一个静态资源,如一个HTML 页面。首先要通过构造函数创建一个java.io.File对象,参数为文件父路径及子路径。
File file = new File(HttpServer.WEB_ROOT, request.getUri());
然后检测该文件是否存在。如果存在,sendStaticResource将该file对象作为参数构造一个java.io.FileInputStream对象。然后调用FileInputStream 的read 方法和OutputStream的 write方法将byte数组写入OutputStream类型的 output对象。注意这种情况下,静态资源的内容是作为原始数据被发送到浏览器的。
if (file.exists()) {
fis = new FileInputstream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
如果文件不存在,sendStaticResource方法将发送一个错误信息到浏览器。
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());
运行该程序
在浏览器中输入如下地址测试该程序:
http://localhost:8080/index.html
你将看到 index.html页面显示在浏览器中,如图1.1所示:
.
图 1.1: web server 的输出
在控制台,你可以看到如下所示的HTTP请求:
GET /index.html HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,
application/vnd.ms-excel, application/msword, application/vnd.ms-
powerpoint, application/x-shockwave-flash, application/pdf, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive
GET /images/logo.gif HTTP/1.1
Accept: */*
Referer: http://localhost:8080/index.html
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive
总结
阅读完这章,你已经知道了一个简单的web server是如何工作的了。这章的应用程序仅由三个类组成,功能不是很强大,但它是个很好的学习工具。下一章将讨论动态内容的处理。
- How_Tomcate_works(简介_第一章).rar (126.8 KB)
- 下载次数: 13
评论
3 楼
yangqianyu222
2010-07-13
yaobiao753 写道
有ModernServlet的源代码么?我下的怎么没有这个了?
我也没找到,最近忙,有空我再去找找。
2 楼
yaobiao753
2010-07-07
有ModernServlet的源代码么?我下的怎么没有这个了?
1 楼
yaobiao753
2010-07-05
翻译的不错~刚看了英文的前四章。看看你的翻译复习一下,呵呵
相关推荐
《译How Tomcat Works(第二章)》这篇文章主要讲解了Apache Tomcat服务器的工作原理,它是一个开源的Java Servlet容器,广泛用于部署Web应用程序。在这一章中,我们将深入探讨Tomcat如何处理HTTP请求,以及其内部架构...
《译How Tomcat Works(第四章)》这篇文章深入解析了Apache Tomcat服务器的工作原理,主要聚焦于Tomcat的内部机制,对于理解Web应用容器的运行方式具有重要意义。Tomcat是Java Servlet和JavaServer Pages(JSP)...
第一章节主要介绍了Tomcat的起源和设计目标。Tomcat最初是为了满足轻量级、高性能的需求而创建的,它不是一个完整的应用服务器,而是专注于Servlet和JSP的执行。Tomcat的核心设计原则包括简洁、高效和可扩展性,这...
《How Tomcat Works》是一本深入解析Apache Tomcat工作原理的书籍,同时也包含了源码,为读者提供了理论与实践相结合的深入学习体验。Tomcat是一款广泛使用的开源Java Servlet容器,它是Apache软件基金会 Jakarta...
《译How Tomcat Works(第三章)》这篇文章深入解析了Apache Tomcat服务器的工作原理,Tomcat作为开源的Java Servlet容器,是许多Web应用程序的基础。在本章中,我们将聚焦于Tomcat如何处理HTTP请求,以及它如何加载和...
《How Tomcat Works》这本书是理解Apache Tomcat服务器工作原理的重要资源,第三章主要探讨了Tomcat的架构和核心组件。以下是对这部分内容的详细解读: Tomcat作为一款开源的Java Servlet容器,其核心功能是解析...
《How Tomcat Works》这本书详细解释了Tomcat的工作原理,它不仅为新手提供了一个蓝图,帮助他们理解这个复杂的系统,也为有经验的开发者提供了深入学习的机会。 ### Tomcat的基本概念 Tomcat核心分为两个主要模块...
《How Tomcat Works》是一本深入解析Tomcat内部工作原理的书籍,专注于Tomcat 4.1.12和5.0.18版本的源码和设计原则。它不仅适合对Tomcat的运行机制感兴趣的读者,也适合希望加入Tomcat开发团队的开发者,以及对大型...
通过阅读和分析《How Tomcat Works》的第12章,我们可以更深入地了解Tomcat如何处理请求,管理Web应用,并为开发者提供一个强大的平台来构建和运行Java Web应用程序。这有助于我们更好地利用Tomcat的特性,提高应用...
通过对《How Tomcat Works》一书第11章的翻译与分析,我们将揭示StandardWrapper如何工作,以及它对整个Web应用的影响。 首先,我们需要了解Servlet的生命周期。Servlet在Web应用中扮演着动态处理请求的角色,它们...
在《How Tomcat Works》一书中,第10章主要介绍了Tomcat的内部工作流程。Tomcat接收到HTTP请求后,首先会通过网络层处理数据包,然后进入服务层解析请求。这一阶段涉及到解码URL、解析请求头和提取参数等步骤。接着...
《WEB服务器工作机制由浅至深(8):【How Tomcat Works】第14章Server和Service》 在深入探讨Web服务器的工作机制时,Tomcat作为Apache软件基金会的开源Java Servlet容器,扮演着至关重要的角色。本章将聚焦于Tomcat...
在深入探讨Tomcat服务器的工作机制时,我们来到了第16章,这一章主要讨论了“关闭钩子”(Shutdown Hooks)的概念及其在Tomcat中的应用。关闭钩子是Java应用程序中的一个重要特性,它允许我们在程序退出前执行必要的...
本文将深入探讨Tomcat的工作机制,基于"How Tomcat Works"的第五章和第六章进行翻译和分析。 第五章主要讨论了Tomcat的生命周期管理和容器概念。在Tomcat中,每个应用都有一个对应的Context,它管理Servlet的加载、...
本篇文章将深入探讨Tomcat的工作机制,基于《How Tomcat Works》一书的第7至9章进行翻译和分析。 第7章主要讲解了Tomcat的启动过程和服务器的配置。Tomcat启动时,首先读取主配置文件`server.xml`,这个文件定义了...
第1章 一个简单的Web服务器 3 1.1 The Hypertext Transfer Protocol (HTTP) 3 1.2 HTTP Request 3 1.3 HTTP Response 4 1.4 Socket类 4 1.5 ServerSocket类 5 1.6 应用举例 5 第2章 一个简单的servlet容器 7 2.1 ...
Tomcat的源码是开放的,这对于开发者来说是一个宝贵的资源,可以学习到如何设计和实现一个高性能的WEB服务器。同时,工具如JMX(Java Management Extensions)可以帮助监控和管理Tomcat的运行状态,包括Engine、Host...