最近在看图解http这本书,本来是打算看tcp/ip卷一的但是看了一部分,感觉和之前看的自顶向下有不少重复的地方,而且书也比较厚,所以打算先看一下图解http这本,这本比较薄而且侧重点也是我目前打算深入了解的http协议。
在学习这本书的过程中我想要自己跟着书编辑一些我想要的http请求和响应的报文,不过之前常用spring全家桶并不是很好去做这个需求。这样我就想要通过netty并根据netty的httpservercodcer去做一个接受http请求并可以检测http请求格式是否符合规范的静态服务器。
netty提供的编解码器就可以很好的实现我想要对我自己编写的http请求是否符合规范的格式的检测功能。
而且我还可以通过netty编写我的响应报文来做响应。
这部分就是很规范的netty的使用流程。

下面就是做一个http请求来发送到我的静态服务器了,这里我选择直接使用Java的nio来做,并且通过system.in/out这两个流在控制台编辑我的http请求并发送这个请求
代码如下

InputStream in = System.in;
        int cpde =0;
        System.out.println("---");
        //缓存文件
        File file = new File("/Users/caohao/IdeaProjects/threadlearn/src/main/java/springlearn/test4/request.txt");
        if (file.exists()==false) {
            boolean newFile = file.createNewFile();
            System.out.println(newFile);
        }
        FileOutputStream outputStream = new FileOutputStream(file);
        while ((cpde=in.read())!=-1){

            if (cpde==36){//按了回车了\n的值=10,$=36这里将$作为结束符号
                outputStream.close();
                break;
            }else {
                outputStream.write(cpde);
            }
        }
        FileInputStream inputStream = new FileInputStream(file);
        FileChannel channel = inputStream.getChannel();
        ByteBuffer buffer = Util.buffer;
        channel.read(buffer);
        file.delete();
        inputStream.close();
        buffer.flip();
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9001));
        System.out.println(socketChannel.finishConnect());
        socketChannel.write(buffer);
        buffer.flip();
        buffer.clear();
        System.out.println(buffer.remaining());

很简单的代码,里面的一部分大数组用的是全局变量,因为写到这里时候想着都做到这里了,干脆做一个页面来发生http请求不也挺香吗。
所以里面的大数组和缓冲区都放在一个接口中来作为全局变量,这样就可以减少栈帧中大量的朝生夕灭大对象,减少了gc的压力(这个观点来自于深入理解jvm这本书)。

下面我贴出来这个web的代码:
util包的代码如下:

package com.caohao.nettyforwebpost.util;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.HashMap;

public class SocketUtil {
    public static void sendHTTPRequest() throws IOException {
        HashMap<String, String> map = Util.MAP;
        SocketChannel channel = SocketChannel.open();
        channel.connect(new InetSocketAddress(map.get("host"), Integer.valueOf(map.get("port"))));
        boolean connect = channel.finishConnect();
        System.out.println(connect);
        channel.write(ByteBuffer.wrap(map.get("requestText").getBytes()));
        map.clear();
        channel.close();
    }
    public static void decodeHTTPText(String text){
        HashMap<String, String> map = Util.MAP;
        //1.取出requesttext
        int index = text.lastIndexOf("requestText=");
        String requesttext = text.substring(index+12);
        map.put("requesttext",requesttext);
        //2.取出port
        int index1 = text.lastIndexOf("port=");
        String port = text.substring(index1+5, index-1);
        map.put("port",port);
        //3.取出host
        int index2 = text.lastIndexOf("host=");
        String host = text.substring(index2+5, index1-1);
        map.put("host",host);
    }
}

util接口:

public interface Util {
        byte[] BYTES = new byte[1024*10];//为了防止在堆中出现过多的短命大对象,所以设置了一个全局的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024 * 10);
        HashMap<String,String> MAP = new HashMap<>();
}

管道的handler注册代码:

ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
        pipeline.addLast(new handler());

自己的handler实现类核心逻辑代码:

@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent){
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)){
                System.out.println("read out of time...");
                ctx.channel().close();
            }
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        if (msg.uri().equals("/hr")){
            ByteBuf content = msg.content();
            byte[] bytes = new  byte[content.readableBytes()];
            content.getBytes(0,bytes);
            String request = new String(bytes);
            System.out.println(request);
            SocketUtil.decodeHTTPText(request);
            SocketUtil.sendHTTPRequest();
        }else {
            FileInputStream inputStream = new FileInputStream("/Users/caohao/IdeaProjects/nettyforwebpost/src/main/resources"+msg.uri());
            ByteBuf buffer = Unpooled.buffer();
            int code=0;
            while ((code=inputStream.read())!=-1){
                buffer.writeByte(code);
            }
            inputStream.close();
            DefaultFullHttpResponse defaultFullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,buffer);
            ctx.channel().writeAndFlush(defaultFullHttpResponse);
        }
    }

html页面代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://192.168.31.118:9001/hr" method="post" enctype="text/plain">
        host:       <input type="text" name="host"><br>
        port:       <input type="text" name="port"><br>
        requestText:<textarea name="requestText" cols="20" rows="10"></textarea><br>
                    <input type="submit"><br>
    </form>
</body>
</html>

以上就是全部的代码了,简单说一下逻辑,就是我们的页面请求会响应我们的html页面,如果是/rh那么就代表了是发送了一个http请求数据,那么就对这个数据进行解析并转发这个响应