版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/PacosonSWJTU/article/details/51058791
【0】README
0.1)本文部分描述转自“深入剖析tomcat”, 旨在学习 一个简单的web server 的基础知识;
0.2)for complete source code, please visit https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter1
【1】HTTP
【1.1】HTTP请求
1)一个HTTP请求包括以下3部分(parts):(干货——一个HTTP请求包括以下3部分(parts))
p1)请求方法——统一资源标识符(URI)——协议/版本;
p2)请求头;
p3)实体;
2)HTTP 请求的示例如下所示:(干货——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
// 这里是空行(CRLF)
lastName=Yun&firstName=Lin
对以上HTTP请求的分析(Analysis):(干货——对HTTP请求的分析)
A1)第一行:请求方法——URI——协议/版本(Post /examples/default.jsp HTTP/1.1)
A2)HTTP1.1 支持的请求方法有:GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE;
A3)URI:指定internet 资源的完整路径;而统一资源定位符(URL) 是 URI 的一种类型;
A4)在请求头和请求实体间有一个空行:该空行只有 CRLF 符;CRLF 告诉HTTP 服务器请求实体正文从哪里开始;正文(lastName=Yun&firstName=Lin)
【1.2】HTTP响应
1)HTTP响应也包括3部分(parts):
part1)协议——状态码——描述;
part2)响应头;
part3)响应实体段;
2)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>hello, world</title>
</head>
<body>
hello, world
</body>
</html>
对上述HTTP响应代码的分析(Analysis):
A1)第一行的200,表示状态码(请求发送成功);
A2)响应头和响应实体正文间由只包含 CRLF 的一个空行分隔;
【1.3】Socket类
1)看个荔枝:(创建一个套接字,用于与本地HTTP Server 进行通信(127.0.0.1 表示一个本地主机),发送HTTP请求接收server 的响应信息);
// 创建一个套接字,用于与本地HTTP Server 进行通信(127.0.0.1 表示一个本地主机),发送HTTP请求接收server 的响应信息
// 采用tomcat 开启8080 端口
public class SocketTest {
public static void main(String[] args) throws Exception {
try(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();
}
}
}
【3】应用程序
0)intro:web服务器应用程序包括3个类: HttpServer, Request, Response;
1)我们先看运行结果(显然发出了两个HTTP 请求,浏览器发出一个获取静态资源的 HTTP请求, 而该静态资源又 发出获取图像资源的 HTTP 请求)(干货——显然发出了两个HTTP 请求):
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java com.tomcat.chapter1.HttpServer
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src
GET /index.html HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
GET /images/psu.jpg HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36
Referer: http://localhost:8080/index.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
GET /SHUTDOWN HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
2)HttpServer, Request, Response 的源码如下所示:
public class HttpServer { // 接受HTTP 请求的web server
/** 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) {
System.out.println(System.getProperty("user.dir"));
HttpServer server = new HttpServer();
server.await();// 会在指定端口上等待HTTP请求。
}
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,创建 HTTP请求对象
Request request = new Request(input);
request.parse(); // 解析HTTP请求字符串
// create Response object,创建HTTP 响应对象
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource(); // 发送静态资源到client
// 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;
}
}
}
}
public class Request { // 封装 HTTP 请求字符串的类
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
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());
}
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;
}
public String getUri() {
return uri;
}
}
public class Response { <span style="font-family: 宋体;">// 封装 HTTP 响应字符串的类</span>
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();
}
}
}
补充)本文总结了一张上述应用程序的调用流程图
对上图的分析(Analysis):
A1)HttpServer:
step1)创建服务器套接字,等待接收 client 发出HTTP 连接请求;
step2)连接成功后,利用套接字创建输入输出流;
step3)创建Request对象, 向Request构造函数传入输入流,并利用request实例对象解析 HTTP 请求;
step4)创建Response对象,向Response构造函数传入输出流,且设置 Response中的request变量引用,调用response对象的sendStaticResource方法发送静态资源到 client;
A2)Request:HTTP请求对象,其parse方法用于读取HTTP请求头;其parseUri方法用于解析client 请求的 uri;
A3)Response:HTTP响应对象,其sendStaticResource方法 读取request解析出的uri对应的资源文件,并发送该文件数据到client端;
3)静态资源html
<html>
<head>
<title>How Tomcat Works</title>
</head>
<body>
<img src="./images/psu.jpg">
<br>
hello, my name is xiao tangtang.
</body>
</html>
4)最后po 出 整体的文件目录架构