理解http服务器, 手写建议服务器

151 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

简介

后端写接口服务, 总离不开http服务器的支持, 为了加深对于http的理解, 今天尝试使用Socket写一个相对简易的服务器.

前言

写之前, 我们先熟悉一下http请求的报文, 即从前端/请求工具中发送到服务器时, 我们接受到了什么. 首先写一个熟悉的main()方法, 启动一个ServerSocket, 然后接受一次请求去查看输出了什么.

package com.demo;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Main {

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            Socket accept = serverSocket.accept();
            InputStream inputStream = accept.getInputStream();
            int i = -1;
            while ((i = inputStream.read()) != -1) {
                System.out.print((char) i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

image.png

百度看下解析, 熟悉下其中的元素

image.png

看完之后我们就可以动手写了

请求

新建一个项目, 写下上述代码. 处理思路: 启动服务, 然后监听请求, 处理请求, 获取报文, 解析报文, 返回响应, 直到服务关闭, 然后我们想清楚思路后就可以开始了.

package com.demo;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true) {
            Socket accept = serverSocket.accept();
            handlerConnection(accept);
        }
    }

    /**
     * 处理连接请求
     *
     * @param accept 请求
     * @throws IOException 异常
     */
    private static void handlerConnection(Socket accept) throws IOException {
        // 获取socket的输入流
        InputStream inputStream = accept.getInputStream();
        // 我们可以用StringBuilder把报文存储起来
        StringBuilder message = new StringBuilder();
        byte[] buf = new byte[1];
        while (inputStream.read(buf,0,buf.length) != -1) {
            String str = new String(buf);
            message.append(str);
        }

        // 解析包文
        parseMessage(message);
    }

    /**
     * 解析请求报文
     *
     * @param message 报文
     */
    private static void parseMessage(StringBuilder message) {
        // 先通过 \r\n分行
        String[] split = message.toString().split("\r\n");
        // 第一行是请求方式, 请求url, 请求协议
        String firstRow = split[0];
        // 通过空格分开
        String[] methodPathWithProtocol = firstRow.split(" ");
        String method = methodPathWithProtocol[0];
        String path = methodPathWithProtocol[1];
        String protocol = methodPathWithProtocol[2];
        System.out.printf("请求方法为:%s,请求路径为:%s,请求协议:%s", method, path, protocol);

        Map<String, String> headers = new HashMap<>();
        // 从第一行开始解析请求头
        String line;
        int index = 1;
        while (index < split.length && !(line = split[index++]).equals(" ")) {
            String[] header = line.split(":");
            headers.put(header[0], header[1].trim());
        }
        System.out.printf("请求头:%s", headers);
    }
}

image.png

对方法协议和路径进行解析, 对请求头进行解析

响应

一次完整的http调用, 除了请求外, 还应该有响应, 我们查一下响应体是什么格式

image.png

组成对应的字符串, 然后write()到客户端即可

/**
 * 处理响应
 *
 * @param accept 请求
 */
private static void handlerResponse(Socket accept) throws IOException {
    OutputStream outputStream = accept.getOutputStream();
    PrintStream printStream = new PrintStream(outputStream);
    printStream.println("HTTP/1.1 200 OK");
    printStream.println("Content-Type:text/html;charset:utf-8");
    printStream.println();
    printStream.println("<h1>HELLO WORLD</h1>");
    printStream.flush();
    accept.shutdownOutput();
}

image.png