简单实现 http Server (版本12)

Posted 一朵花花

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单实现 http Server (版本12)相关的知识,希望对你有一定的参考价值。

http Server

版本1

代码实现:

public class HttpServerV1 
    /*
    *  http 底层要基于 tcp 来实现
    *  需要按照 tcp 的基本格式来进行开发
    * */
    private ServerSocket serverSocket = null;

    public HttpServerV1(int port) throws IOException 
        serverSocket = new ServerSocket(port);
    

    public void start() throws IOException 
        System.out.println("服务器启动...");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true)
            // 1.获取连接
            Socket clientSocket = serverSocket.accept();
            // 2.处理连接 (使用短连接方式)
            executorService.execute(new Runnable() 
                @Override
                public void run() 
                    process(clientSocket);
                
            );
        
    

    private void process(Socket clientSocket) 
        // 由于 http 是一个文本协议,仍然使用字符流来处理
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())))
            // 下面的操作要按照 http 协议的格式来进行操作

            // 1.读取请求并解析
            //  a)解析首行  三个部分按照空格切分
            String fistLine = bufferedReader.readLine();
            String[] fistLineTokens = fistLine.split(" ");
            String method = fistLineTokens[0];
            String url = fistLineTokens[1];
            String version = fistLineTokens[2];
            //  b)解析 header  按行读取 按照冒号空格分割键值对
            Map<String,String> headers = new HashMap<>();
            String line = " ";
            while ((line = bufferedReader.readLine()) != null && line.length() != 0)
                String[] headerTokens = line.split(": ");
                headers.put(headerTokens[0],headerTokens[1]);
            
            //  c)解析 body (暂时不考虑)
            // 打印日志
            System.out.printf("%s %s %s\\n",method,url,version);
            for (Map.Entry<String,String> entry : headers.entrySet()) 
                System.out.println(entry.getKey() + ": " + entry.getValue() + "\\n");
            
            System.out.println();

            // 2.根据请求计算响应
            // 不管是什么请求,都返回一个 hello 这样的 html
            String resp = "<h1>hello</h1>";
            // 3.把响应写回客户端
            bufferedWriter.write(version + "200 OK\\n");
            bufferedWriter.write("Content-Type: text/html\\n");
            bufferedWriter.write("Content-Length: " + resp.getBytes().length + "\\n"); //此处的长度是以为字节为单位的
            bufferedWriter.write("\\n");
            bufferedWriter.write(resp); // body
            //刷新缓冲区, 此处的flash 没有,问题也不大,
            // 紧接着 bufferedWriter 对象就要关闭了 close 时,就会自动触发刷新操作
            bufferedWriter.flush();

         catch (IOException e) 
            e.printStackTrace();
         finally 
            try 
                clientSocket.close();
             catch (IOException e) 
                e.printStackTrace();
            
        
    

测试:

public static void main(String[] args) throws IOException 
    HttpServerV1 serverV1 = new HttpServerV1(6060);
    serverV1.start();

使用 fiddler 查看:

浏览器访问咱们自己的服务器时,就会构造一个 http 请求


服务器收到请求后,就返回一个响应:

把代码改一下:让不同的需求,来看到不同的响应

修改部分代码:

String resp = null;
if(url.equals("/OK"))
	bufferedWriter.write(version + "200 OK\\n");
	resp = "<h1>hello</h1>";

else if(url.equals("/notfound"))
	bufferedWriter.write(version + "404 Not Found\\n");
	resp = "<h1>not found</h1>";

// 重定向情况
else if(url.equals("/seeother"))
    bufferedWriter.write(version + " 303 See Other\\n");
    bufferedWriter.write("Location: http://www.sogou.com\\n");
    resp = "";

else
	bufferedWriter.write(version + "200 OK\\n");
	resp = "<h1>default</h1>";

运行程序,连接服务器:


default:


重定向:

版本2

1.整理代码格式,让代码更规范
2.解析 URL 中包含的参数(键值对),能够方便的处理用户传过来的参数
3.演示 Cookie 的工作流程

request 类:

/*
*  专门表示 Http 请求
*  表示一个 http 请求,并解析
* */
public class HttpRequest 
    private String method;
    private String url;
    private String version;
    private Map<String,String> headers = new HashMap<>();
    // 表示 url中的参数
    private Map<String,String> parameters = new HashMap<>();

    // 请求的构造逻辑,仍使用工厂模式来构造
    // 此处的参数就是从 socket 中获取到的 InputStream 对象
    // 这个过程本质上是在 "反序列化"   把一个比特流,转换成一个结构化数据
    public static HttpRequest build(InputStream inputStream) throws IOException 
        HttpRequest request = new HttpRequest();
        // 此处的逻辑,不能把 bufferedReader 写入到 try() 中
        // 一旦写进去之后就意味着 bufferedReader就会被关闭,会影响到 clientSocket 的状态
        // 等到最后整个请求处理完了, 再统一关闭
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

        // 此处的 build 的过程就是解析请求的过程
        //1.解析首行
        String firstLine = bufferedReader.readLine();
        String[] firstLineTokens = firstLine.split(" ");
        request.method = firstLineTokens[0];
        request.url = firstLineTokens[1];
        request.version = firstLineTokens[2];

        //2.解析 url 中的参数
        int pos = request.url.indexOf("?");
        if(pos != -1)
            // 检查url中是否有 ? ,若没有,说明不带参数,就不需要解析了
            // pos 表示 ? 的下标   /index.html?a=10&bb=20
            // parameters 的结果就相当于是 a=10&bb=20
            String parameters = request.url.substring(pos + 1);
            // 切分的最终结果,key-a,value-10 ;  key-b,value-20
            parseKV(parameters,request.parameters);
        
        //3.解析header
        String line = "";
        while ((line = bufferedReader.readLine()) != null && line.length() != 0)
            String[] headerTokens = line.split(": ");
            request.headers.put(headerTokens[0],headerTokens[1]);
        
        //4.解析 body(暂时不考虑)
        return request;
    

    private static void parseKV(String input, Map<String, String> output) 
        // 1.先按照 & 切分成若干组键值对
        String[] kvTokens =  input.split("&");
        // 2.针对切分结果,再分别进行 按照 = 切分,得到键和值
        for (String kv : kvTokens) 
            String[] result = kv.split("=");
            output.put(result[0],result[1]);
        
    

    // 给这个类构造一些 getter方法 (不要搞 setter 方法)
    // 请求对象的内容应该从网络上解析来的,用户不应该修改
    public String getMethod() 
        return method;
    

    public String getUrl() 
        return url;
    

    public String getVersion() 
        return version;
    

    public String getHeader(String key) 
        return headers.get(key);
    

    public String getParameter(String key) 
        return parameters.get(key);
    
    
    @Override
    public String toString() 
        return "HttpRequest" +
                "method='" + method + '\\'' +
                ", url='" + url + '\\'' +
                ", version='" + version + '\\'' +
                ", headers=" + headers +
                ", parameters=" + parameters +
                '';
    

response 类:

/*
* 专门表示 Http 响应
* 表示一个 http 响应,负责构造
* */
public class HttpResponse 
    private String version = "HTTP/1.1";
    private int status;  // 状态码
    private String message; // 状态码描述性信息
    private Map<String,String> headers = new HashMap<>();
    // 使用StringBuilder 方便进行拼接
    private StringBuilder body = new StringBuilder();
    // 写回给客户端时需要用到
    // 代码把响应写回到客户端时,就往 outputStream 中写
    private OutputStream outputStream = null;

    public static HttpResponse build(OutputStream outputStream)
        HttpResponse response = new HttpResponse();
        response.outputStream = outputStream;
        // 除了outputStream外,其他的属性内容,暂时无法确定,要根据代码的具体逻辑,来决定
        return response;
    

    // 提供一些 setter 方法
    public void setVersion(String version) 
        this.version = version;
    

    public void setStatus(int status) 
        this.status = status;
    

    public void setMessage(String message) 
        this.message = message;
    

    public void setHeader(String key,String value) 
        headers.put(key,value);
    

    public void writeBody(String content) 
        body.append(content);
    

    // 以上设置属性操作,都是在内存中
    // 还需要一个专门de方法,把这些属性,按照 Http 协议写到socket中
    public void flush() throws IOException 
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        // 构造首行
        bufferedWriter.write(version + " " + status + " " + message + "\\n");

        headers.put("Content-Length",body.toString().getBytes().length +"");
        for (Map.Entry<String,String> entry : headers.entrySet())
            bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\\n");
        
        bufferedWriter.write("\\n");
        bufferedWriter.write(body.toString());
        bufferedWriter.flush();
    

主类:

public class HttpServerV2 
    private ServerSocket serverSocket = null;

    public HttpServerV2(int port) throws IOException 
        serverSocket = new ServerSocket(port);
    

    public void start() throws IOException 
        System.out.println("服务器启动...");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) 
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() 
                @Override
                public void run() 
                    process(clientSocket);
                
            );
        
    
    public void process(Socket clientSocket) 
        try 
            // 1.读取并解析请求
            HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
            System.out.println("request: " + request);
            HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
            response.setHeader("Content-Type","text/html");
            // 2.根据请求计算响应
            if(request.getUrl().startsWith("/hello")) 
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>hello</h1>");
            
            else
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>default</h1>");
            
            // 3.把响应写回客户端
            response.flush();
         catch (IOException e) 
            e.printStackTrace();
         finally 
            try 
                // 这个操作会同时关闭 getInputStream 和 getOutputStream 对象
                clientSocket.close();
             catch (IOException e) 
                e.printStackTrace();
            
        
    

    public static void main(String[] args) throws IOException 
        HttpServerV2 serverV2 = new HttpServerV2(6060);
        serverV2.start();
    

测试结果:


添加参数计算:

else if(request.getUrl().startsWith("/calc"))
	// 根据参数的内容进行计算
	// 先获取到 a 和 b 两个参数的值
	String aStr = request.getParameter("a");
	String bStr = request.getParameter("b");
	int a = Integer.parseInt(aStr);
	int b = Integer.parseInt(bStr);
	int result = a + b;
	response.setStatus(200);
	response.setMessage(&#

以上是关于简单实现 http Server (版本12)的主要内容,如果未能解决你的问题,请参考以下文章

http server 简单实现

Http Server 版本3 - 实现完整的登录过程(Cookie & Session)

Http Server 版本3 - 实现完整的登录过程(Cookie & Session)

docker 1.12.3版本搭建私有仓库,上传镜像报错:server gave HTTP response to HTTPS client”

Nginx + Koa 开启http/2 server push

简单的Http Server实现