最近在看图解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请求数据,那么就对这个数据进行解析并转发这个响应