飞道的博客

Java IO编程由浅入深 - 4 (bio http协议解编码器,实现http服务器)

234人阅读  评论(0)

实现基于http协议的解编码器,并通过浏览器请求,返回数据

目录

  1. Java IO编程由浅入深 - 1 (bio c/s架构实现)

  2. Java IO编程由浅入深 - 2(bio 基于字符串的消息编解码器)

  3. Java IO编程由浅入深 - 3 (bio 基于消息长度的解编码器)

  4. Java IO编程由浅入深 - 4 (bio http协议解编码器,实现http服务器)

  5. Java IO编程由浅入深 - 5 (项目架构重构)

  6. Java IO编程由浅入深 - 6 (bio实现C/S聊天室 1 )

  7. Java IO编程由浅入深 - 7 (bio实现C/S聊天室 2 )

  8. Java IO编程由浅入深 - 8 (bio实现C/S聊天室 3 )

  9. Java IO编程由浅入深 - 9 (bio实现http升级到websocket )

  10. Java IO编程由浅入深 - 10 (bio 基于websocket的心跳检测实现 )

创建HttpRequest类

package com.lhstack.bio.codec.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.nio.charset.Charset;
import java.util.Map;

/**
 * @author lhstack
 */
public class HttpRequest {
   

    private String version;

    private String uri;

    private String method;

    private final Map<String,String> headers;

    private ByteBuf content;

    public HttpRequest(String version, String uri, String method, Map<String, String> headers) {
   
        this.version = version;
        this.uri = uri;
        this.method = method;
        this.headers = headers;
        this.content = Unpooled.EMPTY_BUFFER;
    }

    public String version() {
   
        return version;
    }

    public void setVersion(String version) {
   
        this.version = version;
    }

    public String uri() {
   
        return uri;
    }

    public void setUri(String uri) {
   
        this.uri = uri;
    }

    public String method() {
   
        return method;
    }

    public void setMethod(String method) {
   
        this.method = method;
    }

    public Map<String, String> headers() {
   
        return headers;
    }


    public ByteBuf getContent() {
   
        return content;
    }

    public void setContent(ByteBuf content) {
   
        this.content = content;
    }

    /**
     * clone一份
     * @return
     */
    public HttpRequest copy(){
   
        HttpRequest httpRequest = new HttpRequest(this.version, this.uri, this.method, this.headers);
        httpRequest.setContent(this.content.copy());
        return httpRequest;
    }

    @Override
    public String toString() {
   
        return "HttpRequest{" +
                "version='" + version + '\'' +
                ", uri='" + uri + '\'' +
                ", method='" + method + '\'' +
                ", headers=" + headers +
                ", content=" + this.content.toString(Charset.defaultCharset()) +
                '}';
    }
}

创建HttpResponse类

package com.lhstack.bio.codec.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lhstack
 */
public class HttpResponse {
   
    private String version;

    private int status;

    private ByteBuf content;

    private Map<String,String> headers;

    public HttpResponse(String version, int status) {
   
        this(version,status,Unpooled.EMPTY_BUFFER);
    }

    public HttpResponse(String version, int status, ByteBuf content) {
   
        this(version,status,content,new HashMap<>());
    }

    public HttpResponse(String version, int status, ByteBuf content, Map<String, String> headers) {
   
        this.version = version;
        this.status = status;
        this.content = content;
        this.headers = headers;
        this.init();
    }

    private void init() {
   
        this.headers.put("Server","lhstack");
        if(this.content.readableBytes() > 0){
   
            this.headers.put("Content-Length",String.valueOf(this.content.readableBytes()));
        }
    }

    public String version() {
   
        return version;
    }

    public void setVersion(String version) {
   
        this.version = version;
    }

    public int status() {
   
        return status;
    }

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

    public ByteBuf content() {
   
        return content;
    }

    public void setContent(ByteBuf content) {
   
        this.content = content;
    }

    public Map<String, String> headers() {
   
        return headers;
    }

    @Override
    public String toString() {
   
        return "HttpResponse{" +
                "version='" + version + '\'' +
                ", status=" + status +
                ", content=" + content.toString(StandardCharsets.UTF_8) +
                ", headers=" + headers +
                '}';
    }
}

创建HttpMessageCodec

实现将http请求封装成HttpRequest对象,将服务端响应的数据封装成HttpResponse

package com.lhstack.bio.codec.http;

import com.lhstack.bio.codec.MessageCodec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lhstack
 * http解编码器
 */
public class HttpMessageCodec implements MessageCodec<HttpResponse> {
   

    /**
     * 解析状态
     */
    enum State{
   
        //http读取行状态
        READ_LINE,
        //http请求体状态
        READ_BODY,
        //http读取完成状态
        READ_FINISH
    }

    /**
     * 保存上一次读取的字节
     */
    private byte oldByte = ' ';

    private static final String LR_LF = "\r\n";

    private static final String SPACE = " ";

    private State state = State.READ_LINE;

    private HttpRequest httpRequest;

    private static final String CONTENT_LENGTH = "Content-Length";

    /**
     * 读取的http请求信息
     */
    private ByteBuf httpBody = Unpooled.buffer(128);

    @Override
    public ByteBuf messageEncoder(HttpResponse msg) {
   
        ByteBuf result = Unpooled.buffer(128);
        result.writeBytes(String.format("%s %s %s\r\n",msg.version(),msg.status(),"OK").getBytes(StandardCharsets.UTF_8));
        msg.headers().forEach((k,v) ->{
   
            result.writeBytes(String.format("%s: %s\r\n",k,v).getBytes(StandardCharsets.UTF_8));
        });
        if(msg.headers().containsKey(CONTENT_LENGTH)){
   
            result.writeBytes(new byte[]{
   '\r','\n'})
                    .writeBytes(msg.content());
        }
        result.writeBytes(new byte[]{
   '\r','\n'});
        return result;
    }

    @Override
    public Object messageDecoder(ByteBuf buf) {
   
        if(state == State.READ_LINE){
   
            //循环判断是否可读
            while(buf.isReadable()){
   
                buf.markReaderIndex();
                //获取一个字节
                byte b = buf.readByte();
                if(this.oldByte == '\n' && b == '\r'){
   
                    //如果不可读,则返回到上一个状态
                    if(!buf.isReadable()){
   
                        buf.resetReaderIndex();
                        return null;
                    }
                    //读取完最后一个\n
                    buf.readByte();
                    //开始解析http协议
                    httpParse();
                    break;
                }
                this.oldByte = b;
                this.httpBody.writeByte(b);
            }
        }
        if(this.state == State.READ_BODY){
   
            int length = Integer.parseInt(this.httpRequest.headers().get(CONTENT_LENGTH));
            //如果长度不够,等待下一次数据到达
            if(buf.readableBytes() < length){
   
                return null;
            }
            ByteBuf requestBody = Unpooled.buffer(length);
            buf.readBytes(requestBody);
            this.httpRequest.setContent(requestBody);
            this.state = State.READ_FINISH;
        }
        if(this.state == State.READ_FINISH){
   
            //初始化
            this.state = State.READ_LINE;
            //回收httpBody
            this.httpBody.release();
            this.httpBody = Unpooled.buffer(128);
            this.oldByte = ' ';
            HttpRequest copy = this.httpRequest.copy();
            //回收httpRequest
            this.httpRequest.getContent().release();
            this.httpRequest = null;
            return copy;
        }
        return null;
    }

    private void httpParse() {
   
        byte[] bytes = new byte[this.httpBody.readableBytes()];
        //读取这一次的请求
        this.httpBody.readBytes(bytes);
        //存储解析出来的请求头
        Map<String,String> headers = new HashMap<>();
        //获取每一行数据
        String[] lines = new String(bytes, StandardCharsets.UTF_8).split(LR_LF);

        //GET /index.html HTTP/1.1
        String[] line0 = lines[0].split(SPACE);

        String method = line0[0].strip();

        String uri = line0[1].strip();

        String version = line0[2].strip();

        for (int i = 1; i < lines.length; i++) {
   
            String[] kv = lines[i].split(":",2);
            //获取key
            String key = kv[0].strip();
            if(!headers.containsKey(key) && kv.length == 1){
   
                headers.put(key,"");
            }else if(!headers.containsKey(key) && kv.length == 2){
   
                headers.put(key,kv[1].strip());
            }else {
   
                headers.put(key,headers.get(key) + "," + kv[1].strip());
            }
        }
        httpRequest = new HttpRequest(version,uri,method,headers);
        if(headers.containsKey(CONTENT_LENGTH)){
   
            this.state = State.READ_BODY;
        }else{
   
            this.state = State.READ_FINISH;
        }
    }
}

启动服务端

public static void main(String[] args) throws Exception {
   
        Server server = new Server(8080, 200, HttpMessageCodec::new, () -> (MessageHandler<HttpRequest>) (msg, socket) -> {
   
            System.out.println(msg);
            HttpResponse httpResponse = new HttpResponse("HTTP/1.1", 200, Unpooled.wrappedBuffer("hello world".getBytes(StandardCharsets.UTF_8)));
            httpResponse.headers().put("Content-Type","text/plain;charset=utf-8");
            return httpResponse;
        });
        server.start();
    }

使用浏览器访问,查看服务端控制台,可以看到,解析到了浏览器的请求数据

再查看浏览器,浏览器也将服务端响应的hello world打印到了页面

我们再使用PostMan实现带文件的请求,看服务端是否接受到了文件

可以看到,postman的请求到了服务端,并接收到了服务端响应的hello world
这个时候,我们查看服务端,同时服务端也接收到了客户端发送过来的文件,说明我们这次针对http协议的解析,是成功的

下一期,我将实现一个群聊服务器,实现多个客户端互相聊天


转载:https://blog.csdn.net/qq_42413011/article/details/114646159
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场