`
dicmo
  • 浏览: 68552 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

coder 爱翻译 How Tomcat Works 第一章

阅读更多
第一章 一个简单的web server
这章分析java web servers怎么工作。一个web 服务器也被称作一个超文本传输协议(HTTP)服务器,因为它使用HTTP来与客户端通信,客户端通常是web浏览器。一个基于java的web服务器使用2个重要的类:java.net.Socket和java.net.ServerSocket.它们之间的通信是通过HTTP消息完成的。很自然我们这章由讨论HTTP和这两个类为开始。最后,继续分析这个简单web服务器应用结束本章内容。
The Hypertext Transfer Protocol (HTTP)
HTTP是种允许web服务器和浏览器之间通过Internet互相发送和接收数据的协议。是一种基于请求/相应的协议。客户端向服务器发出请求来请求一个文件,服务器相应地对这个请求作出回应。HTTP使用可靠地TCP连接—其默认为TCP80端口号。第一个HTTP版本号是HTTP/0.9,然后被HTTP/1.0重新修改。代替HTTP/1.0版本的是现在的HTTP/1.1版本,1.1版本被定义为Request for Comments(RFC)2616 。

提示:这包含HTTP 1.1的部分只是简单的帮助你理解web服务器应用传递消息。如果想了解跟多,可以阅读 RFC 2016。

HTTP总是客户端通过建立连接和发送一个HTTP请求来初始化一个事务。Web服务器是不能够去连接一个客户端或者发出一个(callback connection)回调连接给客户端。客户端和服务器都可以提前结束一个连接。例如,当使用一个web浏览器你可以按浏览器上的stop按钮来停止下载一个文件,有效地关闭与web服务器的HTTP连接。

HTTP Requests

一个HTTP请求包含3个组成部分:
 方法-统一资源定位符(uri)-协议/版本Method-Uniform ResourceIdentifer-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
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
//这行是CRLF
lastName=Franks&firstName=Michael

POST是请求方法,/example/defaul.jsp 是请求的URI HTTP/1.1是协议的和版本部分。
每一个HTTP请求可以使用许多请求方法的一种作为HTTP标准。在HTTP1.1支持7中类型的请求:GET.POST,HEAD,OPTIONS,PUT,DELETE和TRACE。
GET和POST是在Internet应用中使用最普遍的。

URI完整地指定了Intetnet上的资源。一个URI通常被解析为服务器相关的根目录。这样,它总是可以使用一个前斜杠‘/’开始。一个URL(Uniform Resource Locator)本质上是URI的一种类型。
协议版本表示被使用的HTTP的协议版本号。

请求头部包含关于客户端环境和实体正文的有用的信息。例如,它包含浏览器设置的语言,正文的长度等等。每一个头部都被一个回车换行符(CRLF)序列分开。

在头部和实体正文有一个空行(CRLF),这个空行对HTTP求个格式尤为重要。CRLF告诉HTTP服务器正文从哪里开始。在一些Internet编程书籍中介绍这个换行符(CRLF)是HTTP请求的第四组成部分。

在前面的HTTP请求中,实体正文只是简单的一行内容:

lastName=Franks&firstName=Michael

在一个电影的HTTP请求中,实体内容可以很容易地变得更长

HTTP Responses
与HTTP请求类似,一个HTTP 响应也包含3个组成部分:
 Protocol Status code Description 协议 状态号 描述
 Response headers 响应头部
 Entity body 实体正文

下面是一个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
//(CRLF)
<html>
<head>
<title>HTTP Response Example</title>
</head>
<body> Welcome to Brainy Software </body>
</html>     

响应头的第一行与请求头的第一行很相似。第一行告诉我们协议是使用的HTTP1.1版本,请求成功(200表示请求成功),所有的请求都完成(ok)。

响应头报班的有用信息和请求头相似。在响应的实体正文是响应自己的HTML内容。头部和正文实体同样被一个(CRLF)序列隔开。

The Socket Class

一个socket是一个网络连接的终端。一个socket运行一个应用在网络上能够读和写。两个软件应用安装在2台不同的计算机上能够通过一个连接发送和接收字节流来相互通信。从你的应用发送一条消息到另一个应用,你需要了解另一个应用的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对象。发送文本到远程应用,你通常想要创建一个从OutputStream对象返回的java.io.PrintWriter对象。从连接的另一端接收字节流,你可以调用Socket类的 getInputStream方法来返回一个java.io.InputStream对象。

下面的代码创建了一个socket,它可以连接本地 HTTP服务器 (127.0.0.1被分配给本地主机),发送一个HTTP请求和接收来自服务器的响应。它创建了一个StringBuffer对象可以接收响应并把它打印在控制台(console)上。

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();

Socket类代表的是一个客户端socket,例如:一个你想要在任何时候连接到一个远程服务器应用的socket。现在,如果你想要事项一个服务器应用,就像HTTP服务器或FTP服务器,你需要一种不同的途径来实现。这是因为你的服务器必须在任何时刻都处于工作(监听)中,而且它不知道什么时候会有客户端应用来连接它。为了是你的应用能够处于时刻地工作(监听状态)中,你需要使用java.net.ServerSocket类。这是一个服务器socket的实现。

ServerSocket是与Socket有区别的。服务器socket的作用是等待客户端的连接请求。一旦服务器socket得到一个连接请求,它就会创建一个Socket实例来处理与客户端之间的通信。

你需要使用ServerSocket类提供的4个构造函数的其中一个来创建一个服务器Socket。你需要明确IP地址和服务器socket将要监听的端口号。典型地,IP地址为127.0.0.1,意味着服务器socket将要监听本地机器。IP地址是服务器socket正在监听的作为绑定地址。作为服务器的另一重要特性是它的backlog。在服务器socket开始拒绝接收客户端到来的请求的最大连接请求队列的长度。

ServerSocket类的一个构造函数:

public ServerSocket(int port, int backLog, InetAddress bindingAddress);

注意这个构造函数,它的绑定地址必须是一个java.net.InetAddress的实例。一个简单的方法来创建一个InetAddress对象是通过调用它的静态方法 getByName,通过一个包含主机名的字符串来构建,例如:

InetAddress.getByName("127.0.0.1");

下面一行代码是构建一个在8080端口监听的ServerSocket。它的backlog为1。

new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

一旦你拥有一个ServerSocket实例,你就可以通知它在绑定的地址和服务器socket正在监听的端口号等待客户端的链接请求。即调用ServerSocket类的accept方法。这个方法将只在当有一个连接请求和它的返回值是一个Socket类的实例的时返回。这个Socket对象就可以用来发送和接收客户端的字节流。

The Application
我的web服务器应用包括下面3个类:
 HttpServer
 Request
 Response

应用的入口(静态主方法 main )可以再HttpServlet类中找到。主方法创建一个HttpServer的实例然后调用它的等待方法。顾名思义,就是在指定的端口等待HTTP请求、处理请求和发送响应给客户端。一直到它接收到shutdown命名后才停止等待。

这个应用仅仅能发送像HTML文件和图像文件类似的位于某个目录下的静态资源。它也把收到HTTP请求字节流显示在控制台(console)上。不管怎样,它不能发送任何像日期或cookies这样的头信息到浏览器。

下面让我们来看看这3个类

The HttpServer Class


Listing 1.1: The HttpServer class

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()
{ ... } }

Listing 1.2: The HttpServer class's await method
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服务器可以找到在public static final WEB_ROOT和它下面所有的子目录下的静态资源。

WEB_ROOT的初始化工作:
Public static final String WEB_ROOT =
System System.getProperty("user.dir") +File.separator + "webroot";

代码中有一个叫做webroot的一个目录。这个目录里面包含一些静态资源你可以使用它们来测试这个应用。

获取静态资源。你可以在浏览器URL中输入:

http://machineName:port/staticResource

如果你发送一个请求道另一台运行这个应用的计算机上,machineName是主机名或IP地址。如果是本机运行,你可以使用localhost在当做machineName使用。Port端口号是8080,staticResource是你请求的文件名,它必须位于WEB_ROOT目录下。

例如:如果你你使用本机来测试应用程序,你想要HttpServer对象发送index.html文件给你,你使用下面的地址:
http://localhost:8080/index.html

停止服务器,你从浏览器端URL地址上发送一个事先预定义好的字符串shutdown命令。这个命令位于host:port部分的后面。
例如:

http://localhost:8080/SHUTDOWN

await的方法是用来代替wait方法的,因为在java.lang.Object中有一个处理线程的wait方法。

await方法在ServerSocket创建好一个实例的时候就进入一个while循环(死循环)。

serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
...
// Loop waiting for a request
while (!shutdown) {
...
}

这个while循环在ServerSocket的accept方法返回只有接收到在8080端口的HTTP请求后结束。

socket = serverSocket.accept();

一旦接收到一个请求,await方法就会通过accept方法,从Socket实例获得java.io.InputStream和java.io.OututStream对象。

input = socket.getInputStream();
output = socket.getOutputStream();

然后await方法会创建一个Request对象调用它的parse方法来解析HTTP请求未处理的数据。

// create Request object and parse Request request = new Request(input);
request.parse ();

然后,await方法创建一个Response对象,把Requset对象传给Response。随之调用sendStaticResource方法。

// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();


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



// Close the socket
socket.close ();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);

The Request

Listing 1.3: The Request class

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;
}
}
Listing 1.4: The Request class's parse method
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());
}
Listing 1.5: the Request class's parseUri method
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通过调用private的parseUri方法。ParseUri方法在uri变量里保存了URI。Public的getUri方法被调用用来返回HTTP请求的URI。

The Response Class

Listing 1.6: The Response class
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;
}

HttpServer类的await方法,这个await方法传递了一个从socket里获得的OutputStream对象来创建Resqonse对象。
Response类有2个public方法: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的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());

Running the Application

从工作目录下运行这个程序,输入下面:

java ex01.pyrmont.HttpServer

测试程序,打开浏览器输入URL:

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.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

第一章 完
1
1
分享到:
评论

相关推荐

    tomcat原理解析书(how-tomcat-works)中文版

    适合读者 1.jsp/servlet 开发人员,想了解 tomcat 内部机制的 coder; 2.想加入 tomcat 开发团队的 coder; 3.web 开发人员,但对软件开发很有兴趣的 coder; 4.想要对 tomcat 进行定制的 coder。

    Bad Programming Practices 101 Become a Better Coder by Learning How (Not) epub

    Bad Programming Practices 101 Become a Better Coder by Learning How (Not) to Program 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 查看此书详细信息请在美国亚马逊官网搜索此书

    phpcoder.rar

    - **插件扩展**:PHPCoder支持第三方插件,可以通过社区下载安装增强功能。 - **代码格式化**:使用“编辑”&gt;“格式化代码”对代码进行整理,保持良好的编码风格。 - **搜索与替换**:利用查找和替换功能,可以在...

    MediaCoder答题器

    6. 插件扩展:MediaCoder有一个开放的插件架构,用户可以通过安装第三方插件来增加新的编码解码器、滤镜和其他功能,扩展其应用范围。 7. 用户友好:MediaCoder的界面简洁直观,即便是对编码不熟悉的用户也能快速...

    mediacoder专业版

    mediacoder 5685专业版,无普通版的限制

    matlab Embedded Coder Getting Started Guide.pdf

    Embedded Coder用于产生嵌入式处理器、目标快速原型板和大规模生产中使用的微处理器的可读的、紧凑的、快速的C和C++代码。Embedded Coder支持附加的MATLAB Coder™和Simulink Coder™配置选项,以及对生成代码的功能...

    Mediacoder基础教程

    Mediacoder是一款强大的多媒体编码工具,专为音频和视频转换而设计,支持多种格式,如MP4、AVI、MKV等。本教程将深入讲解如何利用Mediacoder进行视频压制,优化视频质量,同时合理权衡码率与视频大小的关系。 **1. ...

    simulink hdl coder 用户手册pdf

    #### 一、Simulink HDL Coder 简介 Simulink HDL Coder 是 MathWorks 提供的一款强大的工具,它允许用户将 Simulink 模型转换为硬件描述语言(HDL)代码,进而用于 FPGA 和 ASIC 的设计实现。此工具旨在帮助工程师...

    The Clean Coder

    Martin, "The Clean Coder: A Code of Conduct for Professional Programmers" Prentice Hall | 2011 | ISBN: 0137081073 | 256 pages | PDF | 6 MB Programmers who endure and succeed amidst swirling ...

    HDL-Coder详细教程

    本文介绍了使用HDL Coder生成HDL代码的基本流程和技术要点,包括生成代码前的准备工作、不同环境下的代码生成方法以及一个实践案例。通过对这些知识点的学习,读者可以更好地理解和掌握HDL Coder的应用技巧,从而...

    CoolCoder 类生成工具

    综上所述,CoolCoder类生成工具是现代开发环境下的一款实用工具,它通过自动化的方式帮助开发者快速生成实体类和映射文件,减轻了开发负担,提高了开发质量。对于那些使用NHibernate框架的项目,它更是不可或缺的...

    MediaCoder.5755专业破解版

    MediaCoder行业版一款针对VOD及KTV视频点播行业开发的,用于转换和处理带有多音轨内容的视频节目的软件。它具备业界领先的视频编码引擎,在高性能转码的同时保持高画质,并通过丰富的视频滤镜增强画面视觉效果。作为...

    MediaCoder

    MediaCoder是一款功能强大的多媒体转换工具,它支持广泛的音频和视频编码格式,使用户能够轻松地在不同设备之间转换媒体文件。这款软件适用于个人用户、专业音频和视频制作人员,以及那些希望在各种设备上享受多媒体...

    php coder编辑器

    PHPCoder用于快速开发和调试PHP应用程序,它很容易扩展和定制,完全能够符合开发者的个性要求.PHPCoder是一个非常实用的,功能强大的编程环境,而且它是免费的!

    MatlabCoder使用-Matlab Coder的基本使用.pdf

    Matlab Coder是Mathworks公司推出的一款用于将Matlab代码转换成高效C代码的工具。从2004年开始,Matlab陆续在Simulink中添加了Embeded Matlab Function模块,2007年在Real-Time Workshop中引入了emlc函数(现在的...

    matlab coder基本函数教程

    MATLAB Coder 是一款能够将 MATLAB 代码转换成独立的 C 或 C++ 代码的强大工具。这一过程对于那些希望在非 MATLAB 环境下部署 MATLAB 代码的应用开发者来说至关重要。通过将 MATLAB 代码转换为 C/C++ 代码,不仅可以...

    MediaCoder64位专业破解版

    MediaCoder-Premium-x64 MediaCoder是最早开始使用GPU进行视频编码加速的影音转码软件之一。通过将原本完全依赖CPU的计算工作转移到GPU上进行,H.264和H.265编码所需的时间被大幅缩短。

    Embedded Coder.rar

    texasinstrumentsc2000.mlpkginstall 支持TI的C2000系列工具包,要求MATLAB R2017a及其以上版本。 安装方法:打开matlab,调整路径到mlpkginstall文件所在目录;在current folder窗口里双击mlpkginstall文件即可开始...

    AI自动生成SQL语句的开源代码 sqlcoder-main.zip

    开源的AI自动生成SQL语句源代码,这款SQLCoder-70B-Alpha在文本到SQL的转换能力上超越了包括GPT-4在内的所有通用模型,它能更准确地理解你的需求,并生成相应的SQL查询。SQLCoder2和SQLCoder-7B模型已经向公众开放,...

Global site tag (gtag.js) - Google Analytics