how tomcat works[1]:一个简单的Web服务器

一个简单的Web服务器:

Web服务器也称为超文本传输协议(HyperTextTransfer Protocol)服务器.

1.HTTP

​ 允许Web服务器和浏览器通过Internet发送并接受数据,是一种基于“请求-响应”的协议。客户端请求一个文件,服务器对该请求进行响应。使用可靠的TCP连接

1.1HTTP请求头:

​ 一个HTTP请求头包含三部分信息:

​ ①请求方法——统一资源定位符(URI)——协议/版本

​ ②请求头

​ ③实体

​ 例:

GET /index.html HTTP/1.1	/**请求方法——统一资源定位符(URI)——协议/版本 */
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763
Accept-Encoding: gzip, deflate	/** 请求头*/

Host: localhost:8080	/** 实体*/
Connection: Keep-Alive
1.2HTTP响应

​ 与请求类似,响应也是三部分:

​ ①协议——状态码——描述

​ ②响应头

​ ③响应实体段

​ 例:

HTTP/1.1 200 OK	/**协议——状态码——描述 */
Server:Microsoft-IIS/4.0
Date:Mon, 5 Jan 2004 13:13:13 GMT
Content-Length:112	/** 响应头 */
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HelloWorld</title>
</head>
<body>
    <h1>HelloWorld</h1>
</body>
</html>	/** 响应实体段 */

2.Socket类和ServerSocket类

2.1 Socket

​ 套接字是网络连接的端点。可以从网络中读取数据。

Socket socket = new Socket("127.0.0.1", 80);

​ 创建成功后就可以通过socket发送或接受字节流。用到了java.io.OutputStream和java.io.InputStream类。socket用于客户端创建连接。

2.2 ServerSocket

​ ServerSocket可用于建立侦听,侦听客户端用socket发出的连接请求。

ServerSocket serverSocket = new ServerSocket("127.0.0.1", 80);

​ 建立时指名Ip和端口号,等待客户端连接就好。真听到连接之后双方建立通道,就可以传输字节流。

3.简单模拟Web服务器

​ 三个核心类实现Web服务器:HttpServer, Request, Response

HttpServer类是程序的入口。该类定义了两个常量。第一个是静态资源在本地存放的路径;第二个是当程序监听到SHOTDOWN命令时,退出监听。
main()方法中,创建该类的实例去调用该类的await()方法,await()方法建立侦听,然后进入死循环监听客户端的连接,一直到接收到SHOTDOWN命令后退出循环。
在循环中,监听到有客户端连接时,建立通信信道,调用Request类中的parse()方法解析客户端发来的HTTP请求.得到请求的资源定位符URI,该URIResponse类中被调用,从本地获取客户端所请求的资源,通过字节码的形式发送给客户端。**

package com.yc.test;

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;

/***
 * HttpServer类是程序的入口。该类定义了两个常量。
 * 第一个是静态资源在本地存放的路径;
 * 第二个是当程序监听到SHOTDOWN命令时,退出监听。
 * main()方法中,创建该类的实例去调用该类的await()方法,
 * await()方法建立侦听,然后进入死循环监听客户端的连接,
 * 一直到接收到SHOTDOWN命令后退出循环。
 * 在循环中,监听到有客户端连接时,建立通信信道,
 * 调用Request类中的parse()方法解析客户端发来的HTTP请求.得到请求的资源定位符URI,
 * 该URI在Response类中被调用,从本地获取客户端所请求的资源,通过字节码的形式发送给客户端。
 * @author yc
 *
 */
public class HttpServer {
    
    /** WEB_ROOT 这里定义的目录是用来存放一些静态的html 文件或者txt 文件或者jpg 等静态文件*/
    public static final String WEB_ROOT = 
    	System.getProperty("user.dir") + File.separator + "webroot";
    
    /** 关闭命令 */
    public 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() {
        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);
        }
        
        /** 循环等待请求的到来 */
        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();
                /** 判断是否有SHUTDOWN 指令 */
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
        
    }
    
}

Request类中主要有两个方法,parse()方法用来从请求头中读出字符集,parseUri()方法是从请求头中提取出资源定位符URI的值。该类就是解析客户端发过来的 HTTP请求。

package com.yc.test;

import java.io.IOException;
import java.io.InputStream;

/**
 * Request类中主要有两个方法,
 * parse()方法用来从请求头中读出字符集,
 * parseUri()方法是从请求头中提取出资源定位符URI的值。
 * 该类就是解析客户端发过来的HTTP请求。
 * @author yc
 *
 */
public class Request {
    private InputStream input;
    private String uri;
    
    public Request(InputStream input) {
        this.input = input;
    }
    
    /**
     * 从Socket 中读取字符集合
     */
    public void parse() {
        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());
    }
    
    /**
     * 根据http 请求头的格式,读取出请求的uri
     * @param requestString
     * @return
     */
    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;
    }
}

Response类就一个主要的方法sendStaticResource()。该方法会拿到Request类解析出的URI,去本地(服务器)查询客户端所请求的文件,将文件分割按字节码的形式通过信道发送给客户端。

package com.yc.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Response类就一个主要的方法sendStaticResource()。
 * 该方法会拿到Request类解析出的URI,去本地(服务器)查询
 * 客户端所请求的文件,将文件分割按字节码的形式通过信道发送给客户端
 * @author yc
 *
 */
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;
    }
    
    /**
     * 查询 客户端所请求的文件,将文件分割按字节码的形式通过信道发送给客户端
     * @throws IOException
     */
    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 {
                /** 当打开文件发生错误时404 */
            	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) {
            System.out.println(e.toString());
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }
}

4.效果展示

经过尝试,在chrom浏览器不能访问。火狐浏览器编码有问题,html页面不能解析。在Edge浏览器中可以看到效果。目前原因未知。有大神知道请告知!
访问html页面:在这里插入图片描述
访问图片:
在这里插入图片描述
访问文本文件:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值