How Tomcat Works 1: A Simple Http Server
A Simple Web Server
Web Server也称作HTTP server,用于客户端和服务器端HTTP通信。Java-base的webserver 主要使用java.net.Socket and java.net.ServerSocket.
1 HTTP协议介绍(Hypertest Transfer Protocol)
Client request一个文件,服务器对request response. HTTP使用可靠的TCP连接(默认PORT 80),第一版HTTP/1.0, 当前版本HTTP/1.1。定义Request for Comments: http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf.
2 HTTP Requests
一个HTTP Request由三个component构成:
(1)Method - Uniform Resource Identifier(URI) - Protocol/Version
(2)Request headers
(3)Entity body
例子:
POST /examples/index.jsp HTTP/1.1 ------method-URI-protocol version
Accept: text/plain; text/html ----- request headers
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
-------Must using blank line 分隔
lastName=Chan&firstName=Jack -------entity body
3 HTTP Response
一个HTTP Response包括三个部分:
(1)Protocol-Status code -Description
(2)Response headers
(3)Entity body
例子:
HTTP/1.1 200 OK ------Protocol-status code-description
Server: Microsoft-IIS/4.0 ----- response headers
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
-------Must using blank line 分隔
<html> -------entity body
<head>
<title>HTTP response Example</title>
</head>
<body>
Welcome to Brainy software
</body>
</html>
4 The Socket Class(套接字类)
如上所属,你需要知道IP地址和另个应用的port, socket让你能在网络的不同application间传送数据。
Socket有多个构造函数,其中之一: public Socket(String host, int port) , host代表远程机器或者IP地址,port代表远程应用端口号。
(1) 创建Socket: new Socket("127.0.0.1", "8080");
(2) 发送byte streams: 使用Socket.getOutputStream()方法生成OutputStream对象
(3) 发送text到远程app:使用OuptStream对象创建PrintWriter对象
(4) 接受byte streams: 使用 Socket.getInputStream()方法返回InputStream对象
5 HTTP Server实例:
(1) 模拟客户端:
需要和本地服务器(127.0.0.1:8080)通信:发送一个HTTP request, 从server接收response. 显示结果运用stringbuffer存储并打印到控制台:
实现原理: 使用Socket.getOutputStream()获取OutputStream对象->用PrintWriter将request info 通过socket写入远程server. 本地通过Socket.getInputStream()读取server端写入Socket的内容.=->打印in内容到控制台
package com.cisco.tomcat.httpserver;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class ClientSocket {
private static final String HOST = "127.0.0.1";
private static final int PORT = 8080;
public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {
requestLocalServer();
}
@SuppressWarnings("static-access")
public static void requestLocalServer() throws UnknownHostException, IOException, InterruptedException {
Socket socket = new Socket(HOST, PORT);
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(os, autoflush); // 用于通过socket写request内容给remote app -》模拟request message
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 用于读取socket从远端读取的字节流,模拟response message
// 使用output发送request给server端
out.println("GET /index.html HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection: Close");
// 使用input读取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);
}
// 显示相应值到 控制台
System.out.println(sb.toString());
socket.close();
}
}
(2)模拟服务端:
监听本地8080端口. ServerSocket有多个构造函数,其一:public ServerSocket(int port, int backLog, InetAddress bindingAddress) --- backLog 队列同时最大处理request数:new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
(1)实验需要三个模块,HTTP Server, Request, Response三个类
(2)HTTP Server: 主要通过ServerSocket.accept去建立socket通信,使用Request对象解析请求信息,使用Response获取并发送静态资源
package com.cisco.tomcat.httpserver;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpServer{
/**
* WEB_ROOT代表当前工作的根目录
* HTML 和其他文件都在这个目录下
*/
public static final String WEB_ROOT = System.getProperty("user.dir")+ File.separator + "webroot";
// ShutDown命令
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
private static final String HOST = "127.0.0.1";
private static final int PORT = 8080;
// 是否接收到 Shutdown命令
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await(); // 等待client端的请求
}
public void await() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(PORT, 1, InetAddress.getByName(HOST));
}catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// 循环等待请求
while(!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// 创建Request对象,并解析
Request request = new Request(input);
request.parse();
// 创建Response对象, 发送request的静态资源给client
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// 关闭socket
socket.close();
// 查看是否有远程 关闭命令
shutdown = SHUTDOWN_COMMAND.equals(request.getUri());
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
}
(3)Request对象:解析请求信息,获取URI
package com.cisco.tomcat.httpserver;
import java.io.IOException;
import java.io.InputStream;
public class Request {
private String uri;
private InputStream input;
private static final int BUFFER_SIZE = 2048;
public Request(InputStream input) {
this.input = input;
}
public void parse() {
StringBuffer request = new StringBuffer(BUFFER_SIZE);
int i;
byte[] buffer = new byte[BUFFER_SIZE];
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 this.uri;
}
}
(4)Response对象:使用URI去搜寻Server资源,同时组装Response信息
package com.cisco.tomcat.httpserver;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* HTTP response =
* Staus-Line(HTTP-Version SP Status-Code SP Reason-Phrase)
* *((general-header | response-header | entity-header))
* [message - body]
*
*/
public class Response {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
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 {
String errorMessage = "HTTP/1.1 404 File Not Found\n"
+ "Content-Type: text/html\n"
+ "Content-Length: 23\n"
+"\n"
+"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
}catch (Exception e) {
System.out.println(e.toString());
}finally {
if(fis != null) {
fis.close();
}
}
}
public void setRequest(Request request) {
this.request = request;
}
}