How Tomcat works_Chapter01_A simple Web Server
2009-08-04 20:03
459 查看
本章主要解释Java Web服务器是如何工作的。由于Web服务器通常使用HTTP与客户端(同时为Web浏览器)进行通信,所以Web服务器也经常被称为HTTP服务器。基于Java的Web服务器通常使用两个重要的Java类:java.net.socket和java.net.serversocket,并且服务器与客户端之间的通信通过HTTP信息完成,因此本章的内容从讨论HTTP及这两个类开始。最后,将介绍一个简单的Web服务器应用。
一、 超文本传输协议(Hypertext Transfer Protocol)
HTTP是一种允许Web服务器和Web浏览器在Internet上相互发送和接受数据的协议。这是一种请求响应协议。客户端向服务器请求一个文件然后服务器将对客户端进行响应。HTTP使用可靠的TCP连接——在默认情况下使用TCP的80端口。HTTP的第一个版本是HTTP/0.9,后来这个版本被HTTP/1.0替代。当前所使用的版本HTTP/1.1在RFC2616中定义,该文档的下载地址为:http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf在HTTP中,总是由客户端来发起一个事务,即由客户端建立一个连接并发送一个HTTP请求。服务器没有资格主动联系客户端或与客户端建立一个反馈连接。无论是客户端还是服务器端都可以提前终止连接。例如,当我们在使用Web浏览器时,我们可以点击浏览器上的关闭按钮来阻止文件的下载,同时也有效的关闭了与Web服务器之间的连接.
1 HTTP请求
一个HTTP请求通常由三部分组成:
(1) 方法-URI-协议/版本
(2) 请求头(Request Header)
(3) 实体主体(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 |
POST /examples/default.jsp HTTP/1.1 |
每个HTTP请求使用HTTP标准规定的众多方法中的一种。HTTP1.1支持起七种请求类型:GET,POST,HEAD,OPTIONS,PUT,DELETE和TRACE。在Internet应用中方法GET和POST是最常使用的。
URI完整规定了Internet上的资源。一个URI通常被解析为针对服务器根目录的相对路径,因此该部分通常以“/”开始。URL实际上是URI的一种类型(具体说明可以参见http://www.ietf.org/rfc/rfc2396.txt)。协议版本代表所使用HTTP的版本。
请求头中包含了客户端环境及请求实体主体的重要信息。例如,它包含了浏览器所设定的语言,实体主体的长度等等。每个请求头由回车换行进行分割。
在请求头和实体主体之间有一个空行,这个空行对HTTP请求的格式非常重要。这个回车换行告诉服务器主体实体从哪里开始。在某些Internet编程书中,空格换行被认为是HTTP请求的第四部分。
在前面的HTTP请求中,实体主体是下面的部分:
lastName=Franks&firstName=Michael |
2 HTTP响应
和HTTP请求类似,HTTP响应同样由三部分组成:
(1) 协议-状态码-描述
(2) 响应头
(3) 实体主体
下面是一个HTTP响应的例子:
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> |
响应头中包含的有用信息也和请求头中的类似。响应中的实体主体是响应的HTTP内容。响应头和实体主体由回车换行分割。
3 Socket类
Socket是网络连接中的一个端点。Socket允许一个应用程序在网络上进行读写操作。驻留在两台不同电脑上的两个软件应用可以通过在一个连接上发送和接收字节流进行通信。为了从你的应用中发送一个消息到另外一个应用,你需要知道另一个应用所使用Socket的IP地址和端口号。在Java中,socket由类java.net.Socket表示。
为了创建一个socket,你需要使用Socket类中众多构造方法中的一个。其中接受主机名和端口号的一个构造方法如下:
public Socket (java.lang.String host, int port) |
new Socket ("yahoo.com", 80); |
下面的代码片段创建了一个与本地HTTP服务器(127.0.0.1代表本地主机)通信的socket,它发送一个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(); |
4 ServerSocket类
Socket类代表了“客户端”的socket,即任何时候你想连接到一个远程服务器应用所构建的socket。现在如果你想实现一个服务器应用,例如HTTP服务器或者FTP服务器,你需要一种不同的方法。这是因为服务器必须随时待命,它并不知道客户端应用何时会尝试连接它。为了让你的应用能够随时待命,你需要使用java.net.ServerSocket类。这是服务器Socket的一种实现。
ServerSocket不同于Socket。ServerSocket的作用是随时等待来自客户端的连接请求。一旦服务器socket接收到一个连接请求,它会创建一个socket对象来处理和客户端的通信。
为了创建一个服务器socket,你需要使用ServerSocket类中所提供的四个构造函数中的一个。你需要规定服务器socket所监听的IP地址及端口号。通常情况下,IP地址可以是127.0.0.1,这意味着服务器socket将监听本地机器。服务器socket监听的IP地址叫做绑定地址。服务器socket另一个重要的属性是backlog,它是入连接请求的最大队列长度,当超过这个数字时,服务器socket将开始拒绝入连接请求。
ServerSocket类中的一个构造方法的签名如下:
public ServerSocket(int port, int backLog, InetAddress bindingAddress); |
InetAddress.getName(“127.0.0.1”); |
new ServerSocket(8080, 1, InetAddress.getName(“127.0.0.1”)); |
二、 简单实例
该Web服务器应用是包ex01.pyrmont中的一部分,由三部分组成:
n HttpServer
n Request
n Response
这个应用程序的入口(静态main方法)可以在HttpServer类中找到。main方法创建了一个HttpServer实例并调用了其await方法。这个await方法,正如它名字所暗示的,在一个指定的端口等待HTTP请求,处理它们,同时给客户端发送响应。当接收到shutdown指令时,await方法将停止等待。
该应用程序只能发送静态资源,例如驻留在某个目录下的HTML文件和image文件等。它同样能将入HTTP请求的字节流显示在控制台中。然而,它不能发送任何请求头到浏览器,例如时间或Cookies等。
我们将在下面的部分中依次解释该应用中的三个类。
1 HttpServer类
HttpServer类代表一个Web服务器,其代码参见列表1.1。注意,为了节省列表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. */ // System.getProperty("user.dir")用于获得工作空间地址 public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; //服务器关闭命令 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; //是否接收到关闭命令 private boolean shutdown = false; public static void main(String[] args) { HttpServer server = new HttpServer(); server.await(); } public void await() { … } } |
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; |
为了请求一个静态资源,你可以在浏览器的地址栏中输入如下的URL:
http://machinename:port/staticResource |
例如,如果你使用相同的电脑来测试应用,并且你想让HttpServer对象发送index.html文件,你可以使用如下的URL地址:
http://localhost:8080/index.html |
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; |
http://localhost:8080/SHUTDOWN |
列表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); } // 循环等待请求,当收到SHUTDOWN命令时结束循环 while (!shutdown) { Socket socket = null; InputStream input = null; OutputStream output = null; try { socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // 创建请求对象并解析 Request request = new Request(input); request.parse(); // 创建响应对象 Response response = new Response(output); response.setRequest(request); response.sendStaticResource(); // 关闭socket socket.close(); //检查URL是否为关闭服务器的命令 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); } catch (Exception e) { e.printStackTrace(); continue; } } } |
await方法从创建一个ServerSocket实例开始,紧接着进入while循环。
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); … while (!shutdown) { … } |
socket = serverSocket.accept(); |
input = socket.getInputStream(); output = socket.getOutputStream(); |
// 创建请求对象并解析 Request request = new Request(input); request.parse(); |
// 创建响应对象 Response response = new Response(output); response.setRequest(request); response.sendStaticResource(); |
// 关闭socket socket.close(); //检查URL是否为关闭服务器的命令 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); |
ex01.pyrmont.Request类代表一个HTTP请求。该类的实例通过传入InputStream对象(从处理与客户端通信的socket对象中获得)构造得到。你可以调用InputStream对象中的某个read方法来获得HTTP请求中的原始数据。
Request类的实现在列表1.3中显示。该类中有两个公共方法parse和getUri,它们将分别在列表1.4和1.5中给出。
列表1.3:
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; } } |
为了理解parse和parseUri方法的工作原理,你需要了解HTTP请求的结构,这部分内容在前面的章节“HTTP”中已经讨论过。在本章中,我们只对HTTP请求的第一部分感兴趣,即请求行。请求行以方法开始,随后紧跟着请求URI和协议版本,最后以回车换行结束。请求行中的各个元素通过空格来分隔。例如,某个使用GET方法请求index.html的请求行如下所示:
GET /index.html HTTP/1.1 |
列表1.4:Request类中的parse方法
public void parse() { // 从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) { //URI是第index+1个字符到第index2-1个字符 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; } |
Ex01.pyrmont.Response类代表了一个HTTP响应,并在列表1.6中给出:
列表1.6:Response类
package ex01.pyrmont.Response; 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(); } } } |
public Response(OutputStream output) { this.output = output; } |
Response类有两个公共方法:setRequest和sendStaticResource。方法setRequest用于将Request对象传入Response对象中。
方法sendStaticResource用来发送一个静态资源,例如HTML文件。它首先调用java.io.File类的构造函数,同时传入代表父路径及子路径的参数。
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); } } |
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()); |
为了运行程序,在工作空间目录下输入如下:
java ex01.pyrmont.Response |
http://localhost:8080/index.html |
在控制台中,你可以看到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.mspowerpoint, 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服务器是如何工作的。本章中附带的例子包含了三个部分,但是这三个部分功能都是不完整的。然而这是一个很好的学习工具。下一章我们将讨论动态内容的处理。
本文出自邱栋的博客,转载请注明出处!
相关文章推荐
- How Tomcat Works - A Simple Web Server
- 《How Tomcat Works》读书笔记(一)A Simple Web Server
- 知识库 tomcat-A Simple Web Server static
- [How Tomcat Works]第1章 一个简单的Web服务器
- 攻城狮在路上(肆)How tomcat works(一) 简单的web服务器
- how tomcat works 1 simple web server
- how tomcat works 读书笔记(一)----------一个简单的web服务器
- 《How Tomcat Works》读书笔记(二)A Simple Servlet Conta...
- 【How Tomcat Works】第一章——一个简易的java web服务实现(上)
- how tomcat works 读书笔记(一)----------一个简单的web服务器
- Tomcat-3 A Simple Web Server
- How tomcat works——14 Server和 Service
- 《How To Tomcat Works》-第一章:一个简单的Web服务器
- how tomcat works 2 A simple container
- How Tomcat Works - A Simple Servlet Container
- how tomcat works 简单的server
- 探索《How Tomcat Works》心得 七 Session
- Simple Web API Server in Golang (2)
- how tomcat works(第五章)
- how tomcat works(第八章)