Http


Http 和 Https都是应用层协议

URL

在这里插入图片描述

url中对应的path不同的时候,获取到的页面也是不同的
url中的服务器的IP确定一个服务器
url中服务器的端口来确定这个主机上的哪个进程
url中的path来确定这个进程中所管理的哪个资源/文件

在搜狗搜索引擎中搜索"蛋糕",可以得到如下URL

https://www.sogou.com/web?
query=%E8%9B%8B%E7%B3%95
&_asf=www.sogou.com
&_ast=&w=01015002
&p=40040108
&ie=utf8
&from=index-nologin
&s_from=index
&oq=
&ri=0
&sourceid=sugg
&suguuid=
&sut=0
&sst0=1597306823875
&lkt=0%2C0%2C0
&sugsuv=001009F56F1247DE5E814028B9A38220
&sugtime=1597306823875

查询字符串中使用 & 把这些内容区分为若干个键值对
每个键值对的键和值之间用 = 分割
此处的键值对的内容外人是不知道的,这是负责此处的程序员自己定义的。
不同服务器上使用的查询字符串中的键值对的内容也是不同的。

这是从百度搜索引擎中去搜索"蛋糕"字样的到的url

https://www.baidu.com/s?
wd=%E8%9B%8B%E7%B3%95
&rsv_spt=1
&rsv_iqid=0xb4446a1300043e82
&issp=1
&f=8
&rsv_bp=1
&rsv_idx=2
&ie=utf-8
&tn=62095104_33_oem_dg
&rsv_enter=1
&rsv_dl=tb
&rsv_sug3=7
&rsv_sug1=5
&rsv_sug7=100
&rsv_sug2=0
&rsv_btype=i
&inputT=1542
&rsv_sug4=1542

#ch1 定位到一个html中的具体位置,(用来实现"回到顶部"功能)

查询词:用户在搜索框中输入的内容
query=%E8%9B%8B%E7%B3%95

urlencode:把url中包含的中文和一些特殊符号进行了转义,转译为
“% + 16进制数字” 的形式。

为什么要转义?
url中包含了一些特殊用途的符号,例如:"/","&","%"…,这些特殊符号有可能会导致浏览器解析出错。

urlencode 的逆向操作 urldencode。

HTTP原理

HTTP协议主要需要了解协议报文格式。
需要借助专门的"抓包"工具(fiddler),查看HTTP具体的协议内容

fiddler

fillder界面主要有三部分

左侧:抓到的包的列表
右上侧:这个包的请求内容
右下侧:这个包的响应内容

HTTP请求与响应

HTTP请求格式

在这里插入图片描述

1、首行

在这里插入图片描述

方法 (GET)
url
版本号

方法、URL和版本号之间使用空格分隔开

2、协议头(header)

若干个键值对
键和值之间使用 ": " 分隔开
此处的键值对可以是用户自定义的,但是大部分都是HTTP中已有的,具有特定含义的内容

3、空行

header的结束标记

4、正文(body)

可能是空(GET)
也可能是非空(POST)

HTTP 响应格式

1、首行

HTTP/1.1 200 OK

版本号 HTTP/1.1
状态码 200
状态码描述信息 OK

2、协议头(header)

若干个键值对
键和值之间使用 ": " 分隔开

3、空行

header的结束标记

4、正文(body)

响应的正文最常见的就是HTML,表示了一个网页具体是长什么样子的。

HTTP的方法

在这里插入图片描述

关于GET和POST

1.GET用于从服务器获取资源,POST用于给服务器提交数据
现在很少严格遵守,两者都可以用来从服务器获取资源和给服务器提交数据。
2.GET传输的数据量限制小(URL长度有限),POST传输的数据量大
很久之前,URL的长度有限,但是现在的URL可能会很长
3.POST比GET更安全
POST只是把密码什么的放在body中,密码不出现在URL(地址栏)中

GET和POST的区别

GET一般把数据放在url中
POST一般把数据放在body中

HTTP状态码

在这里插入图片描述
重定向:访问一个一面的时候自动跳转到另一个页面。

HTTP常见header

Content-Type: 数据类型(text/html等)
Content-Length: Body的长度(字节)

Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
在这里插入图片描述

User-Agent: 声明用户的操作系统和浏览器版本信息;
在这里插入图片描述

referer: 当前页面是从哪个页面跳转过来的;
在这里插入图片描述

location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
在这里插入图片描述

Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

Cookie 就相当于是一个字符串。

HTTP的特点:无状态(两次HTTP请求之间无关联),从业务上要建立起这样的关联,就需要使用Cookie

Cookie就相当于是保存在浏览器中的一个字符串,这个字符串是通过服务器返回的响应的Set-Cookie字段中过来的,后续再访问该服务器,就会自动带上Cookie字段。

以登录为例,一次登陆中通常涉及两个请求
在这里插入图片描述
第一次交互:
请求
在这里插入图片描述
响应
在这里插入图片描述
第二次交互:
在这里插入图片描述
Cookie关联的意义:
借助Cookie就可以把多个请求关联在一起(Cookie中的内容一致,就说明这几个请求是相关联的)

HTTP服务器

HTTP服务器V1

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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) {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
            //严格按照HTTP协议来操作
            //1.读取请求并解析
            //  a) 解析首行
            String firstLine = br.readLine();
            String[] firstLineTokens = firstLine.split(" ");
            String method = firstLineTokens[0];
            String url = firstLineTokens[1];
            String version = firstLineTokens[2];
            //  b) 解析header
            Map<String, String> headers = new HashMap<>(16);
            String line = "";
            //readLine 读取的一行内容,是会自动去掉换行符的。对于空行来说,去掉了换行符,就变成空字符串
            while ((line = br.readLine()) != null && line.length() != 0) {
                 //不能使用 : 来切分,像referer字段,里面的内容可能包含:
                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());
            }
            System.out.println();
            //2.根据请求计算响应
            String response = "";
            if (url.equals("/ok")) {
                bw.write(version + " 200 OK\n");
                response = "<h1>hello</h1>";
            } else if (url.equals("/notfound")) {
                bw.write(version + " 404 Not Found\n");
                response = "<h1>not fount</h1>";
            } else if (url.equals("/seeother")) {
               bw.write(version + " 303 See Other\n");
               bw.write("Location: http://www.baidu.com\n");
               response = "";
            } else {
                bw.write(version + "200 ok\n");
                response = "<h1>default</h1>";
            }

            //3.把响应写回到客户端

            bw.write("Content-Type: text/html\n");

            //此处的长度不能携程response.length(),得到的是字符的数量
            bw.write("Content-Length: " + response.getBytes().length + "\n");
            bw.write("\n");
            bw.write(response);
            bw.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(9090);
        serverV1.start();
    }
}

V1版本比较简陋,只是在TCP的基础上,根据HTTP协议进行解析,然后再根据HTTP协议计算响应。

HTTP 服务器V2

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


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());
            //2.根据请求计算响应
            response.setHeader("Content-Type", "text/html");
            if (request.getUrl().startsWith("/hello")) {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>hello</h1>");
            } else if (request.getUrl().startsWith("/calc")) {
                //根据参数内容进行计算
                String aStr = request.getParameter("a");
                String bStr = request.getParameter("b");
                int a = Integer.parseInt(aStr);
                int b = Integer.parseInt(bStr);
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1> ret = " + (a+b) + "</h1>");
            } else if (request.getUrl().startsWith("/cookieUser")) {
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Set-Cookie", "user=zh");
                response.writeBody("<h1>set cookieUser</h1>");
            } else if (request.getUrl().startsWith("/cookieTime")) {
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Set-Cookie", "time=" +
                        (System.currentTimeMillis() / 1000));
                response.writeBody("<h1>set cookieTime</h1>");
            } else {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>default</h1>");
            }
            //3.把响应写回到客户端
            response.flush();
        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
        } finally {
            try {
                //这个操作会同时关闭 getInputStream 和 getOutputStream对象
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        HttpServerV2 httpServerV2 = new HttpServerV2(9090);
        httpServerV2.start();
    }
}
import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class HttpRequest {
    private String method;

    private String url;
    private String version;
    private Map<String, String> headers = new HashMap<>(16);
    private Map<String, String> parameters = new HashMap<>(16);


    /**
     * 请求的构造逻辑,也用工厂模式来构造
     * build 的过程就是解析请求的过程
     *  1.解析首行
     *  2.解析url中的参数
     *  3.解析 header
     *  4.解析body
     *
     *  这个过程本质上就是在”反序列化“
     *  反序列化:把字节序列恢复为对象的过程
     * @param inputStream 从socket中获取到的InputStream
     * @return
     */
    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        //此处的逻辑中,不能把br写到try中;一旦写进去就意味着br会被关闭,
        //会影响到 clientSocket的状态  等到整个请求处理完了,再统一关闭
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        //1.解析首行
        String firstLine = br.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中是否有? 如果没有,就说明不带参数,也就不需要解析
            //此处的 parameters 是希望包含整个 参数 部分的内容
            // /index.html?a=10&b=20
            //parameters 的结果就是相当于是 a=10&b=20
            String parameters = request.url.substring(pos + 1);
            parseKV(parameters, request.parameters);

            //3.解析 header
            String line = "";
            while ((line = br.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[] ret = kv.split("=");
            output.put(ret[0], ret[1]);
        }
    }

    //给这个类构造一些getter方法  不需要setter方法,因为请求对象内容是从网络
    //上解析来的,用户不应该修改


    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getVersion() {
        return version;
    }

    public String getHeaders(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 +
                '}';
    }
}

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class HttpResponse {
    private String version = "HTTP/1.1";
    /**
     * 状态码
     */
    private int status;
    /**
     * 状态码描述信息
     */
    private String message;

    private Map<String, String> headers = new HashMap<>(16);
    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;
    }

    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 val) {
        headers.put(key, val);
    }

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

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

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

        bw.write(body.toString());
        bw.flush();
    }
}

HTTP服务器V3

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServerV3 {
    static class User {
        // 保存用户的相关信息
        public String userName;
        public int age;
        public String school;
    }

    private ServerSocket serverSocket = null;
    // session 会话. 指的就是同一个用户的一组访问服务器的操作, 归类到一起, 就是一个会话.
    // 记者来采访你, 记者问的问题就是一个请求, 你回答的内容, 就是一个响应. 一次采访过程中
    // 涉及到很多问题和回答(请求和响应), 这一组问题和回答, 就可以称为是一个 "会话" (整个采访的过程)
    // sessions 中就包含很多会话. (每个键值对就是一个会话)
    private HashMap<String, User> sessions = new HashMap<>();

    public HttpServerV3(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());
            HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
            // 2. 根据请求计算响应
            // 此处按照不同的 HTTP 方法, 拆分成多个不同的逻辑
            if ("GET".equalsIgnoreCase(request.getMethod())) {
                doGet(request, response);
            } else if ("POST".equalsIgnoreCase(request.getMethod())) {
                doPost(request, response);
            } else {
                // 其他方法, 返回一个 405 这样的状态码
                response.setStatus(405);
                response.setMessage("Method Not Allowed");
            }
            // 3. 把响应写回到客户端
            response.flush();
        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void doGet(HttpRequest request, HttpResponse response) throws IOException {
        // 1. 能够支持返回一个 html 文件.
        if (request.getUrl().startsWith("/index.html")) {
            String sessionId = request.getCookie("sessionId");
            User user = sessions.get(sessionId);
            if (sessionId == null || user == null) {
                // 说明当前用户尚未登陆, 就返回一个登陆页面即可.

                // 这种情况下, 就让代码读取一个 index.html 这样的文件.
                // 要想读文件, 需要先知道文件路径. 而现在只知道一个 文件名 index.html
                // 此时这个 html 文件所属的路径, 可以自己来约定(约定某个 d:/...) 专门放 html .
                // 把文件内容写入到响应的 body 中
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                InputStream inputStream = HttpServerV3.class.getClassLoader().getResourceAsStream("index.html");
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                // 按行读取内容, 把数据写入到 response 中
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    response.writeBody(line + "\n");
                }
                bufferedReader.close();
            } else {
                // 用户已经登陆, 无需再登陆了.
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                response.writeBody("<html>");
                response.writeBody("<div>" + "您已经登陆了! 无需再次登陆! 用户名: " + user.userName + "</div>");
                response.writeBody("<div>" + user.age + "</div>");
                response.writeBody("<div>" + user.school + "</div>");
                response.writeBody("</html>");
            }
        }
    }

    private void doPost(HttpRequest request, HttpResponse response) {
        // 2. 实现 /login 的处理
        if (request.getUrl().startsWith("/login")) {
            // 读取用户提交的用户名和密码
            String userName = request.getParameter("username");
            String password = request.getParameter("password");
//            System.out.println("userName: " + userName);
//            System.out.println("password: " + password);
            // 登陆逻辑就需要验证用户名密码是否正确.
            // 此处为了简单, 咱们把用户名和密码在代码中写死了.
            // 更科学的处理方式, 应该是从数据库中读取用户名对应的密码, 校验密码是否一致.
            if ("zh".equals(userName) && "123".equals(password)) {
                // 登陆成功
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                // 原来登陆成功, 是给浏览器写了一个 cookie, cookie 中保存的是用户的用户名.
                // response.setHeader("Set-Cookie", "userName=" + userName);

                // 现有的对于登陆成功的处理. 给这次登陆的用户分配了一个 session
                // (在 hash 中新增了一个键值对), key 是随机生成的. value 就是用户的身份信息
                // 身份信息保存在服务器中, 此时也就不再有泄露的问题了
                // 给浏览器返回的 Cookie 中只需要包含 sessionId 即可
                String sessionId = UUID.randomUUID().toString();
                User user = new User();
                user.userName = "zh";
                user.age = 20;
                user.school = "陕科大";
                sessions.put(sessionId, user);
                response.setHeader("Set-Cookie", "sessionId=" + sessionId);

                response.writeBody("<html>");
                response.writeBody("<div>欢迎您! " + userName + "</div>");
                response.writeBody("</html>");
            } else {
                // 登陆失败
                response.setStatus(403);
                response.setMessage("Forbidden");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                response.writeBody("<html>");
                response.writeBody("<div>登陆失败</div>");
                response.writeBody("</html>");
            }
        }
    }

    public static void main(String[] args) throws IOException {
        HttpServerV3 serverV3 = new HttpServerV3(9090);
        serverV3.start();
    }
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

public class HttpRequest {
    private String method;
    private String url;
    private String version;
    private Map<String, String> headers = new HashMap<>();
    // url 中的参数和 body 中的参数都放到这个 parameters hash 表中.
    private Map<String, String> parameters = new HashMap<>();
    private Map<String, String> cookies = new HashMap<>();
    private String body;

    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        // 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) {
            String queryString = request.url.substring(pos + 1);
            parseKV(queryString, 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. 解析 cookie
        String cookie = request.headers.get("Cookie");
        if (cookie != null) {
            // 把 cookie 进行解析
            parseCookie(cookie, request.cookies);
        }
        // 5. 解析 body
        if ("POST".equalsIgnoreCase(request.method)
                || "PUT".equalsIgnoreCase(request.method)) {
            // 这两个方法需要处理 body, 其他方法暂时不考虑
            // 需要把 body 读取出来.
            // 需要先知道 body 的长度. Content-Length 就是干这个的.
            // 此处的长度单位是 "字节"
            int contentLength = Integer.parseInt(request.headers.get("Content-Length"));
            // 注意体会此处的含义~~
            // 例如 contentLength 为 100 , body 中有 100 个字节.
            // 下面创建的缓冲区长度是 100 个 char (相当于是 200 个字节)
            // 缓冲区不怕长. 就怕不够用. 这样创建的缓冲区才能保证长度管够~~
            char[] buffer = new char[contentLength];
            int len = bufferedReader.read(buffer);
            request.body = new String(buffer, 0, len);
            // body 中的格式形如: username=tanglaoshi&password=123
            parseKV(request.body, request.parameters);
        }
        return request;
    }

    private static void parseCookie(String cookie, Map<String, String> cookies) {
        // 1. 按照 分号空格 拆分成多个键值对
        String[] kvTokens = cookie.split("; ");
        // 2. 按照 = 拆分每个键和值
        for (String kv : kvTokens) {
            String[] result = kv.split("=");
            cookies.put(result[0], result[1]);
        }
    }

    private static void parseKV(String queryString, Map<String, String> parameters) {
        // 1. 按照 & 拆分成多个键值对
        String[] kvTokens = queryString.split("&");
        // 2. 按照 = 拆分每个键和值
        for (String kv : kvTokens) {
            String[] result = kv.split("=");
            parameters.put(result[0], result[1]);
        }
    }

    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getVersion() {
        return version;
    }

    public String getBody() {
        return body;
    }

    public String getParameter(String key) {
        return parameters.get(key);
    }

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

    public String getCookie(String key) {
        return cookies.get(key);
    }
}

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class HttpResponse {
    private String version = "HTTP/1.1";
    private int status;
    private String message;
    private Map<String, String> headers = new HashMap<>();
    private StringBuilder body = new StringBuilder();
    private OutputStream outputStream = null;

    public static HttpResponse build(OutputStream outputStream) {
        HttpResponse response = new HttpResponse();
        response.outputStream = outputStream;
        return response;
    }

    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);
    }

    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();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值