设为首页 收藏本站
查看: 753|回复: 0

[经验分享] coder 爱翻译 How Tomcat Works 第一章

[复制链接]

尚未签到

发表于 2017-2-8 08:49:27 | 显示全部楼层 |阅读模式
第一章一个简单的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、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-339009-1-1.html 上篇帖子: 使用myEclipse 给TOMCAT加内存 下篇帖子: coder 爱翻译 How Tomcat Works 第二章 第一部分
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表