Tomcat源码分析使用NIO接收HTTP请求(三)----解析请求行
我们在上一节中已经接收到了请求,在本节中我们将来解析请求行也就是`GET / HTTP/1.1`这一行。
了解Tomcat解析请求行
Tomcat在Http11Processor的service方法中会调用Http11InputBuffer的parseRequestLine方法来解析Tomcat的请求行,所以我们本节的重点将是实现parseRequestLine方法。
实现parseRequestLine方法
以下代码都是基于上一节代码修改或添加的。
第一步: 修改parseRequestLine方法,在parseRequestLine方法读取到数据后,会先执行do-while 循环以用来删除空格。然后根据空格标志来获取Get,获取Get后又以同样的方式获取/,此时可以运行程序 来看一下打印输出,会发现有一个明显的问题(见注释)。
boolean parseRequestLine() throws IOException { byte chr = 0; if (parsingRequestLinePhase < 2) { // 读取数据 wrapper.read(byteBuffer); System.out.println("postion="+byteBuffer.position()+";limit=" + byteBuffer.limit()); if (byteBuffer.position() > 0) { // 打印接收到的请求 System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8)); byteBuffer.flip(); // 这个do while循环用来清除消息开始的空行 do{ chr = byteBuffer.get(); byte[] b = new byte[]{chr}; }while((chr == (byte) '\r') || (chr == (byte) '\n')); // 因为在do while中多读取了一个position所以在这里需要给设置回来 byteBuffer.position(byteBuffer.position() - 1); // 接下来在继续解析 拿到Get // 当遇到空格或制表符时被设置成true boolean space = false; // 获取GET while(!space) { chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, 0, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); // 因为获取/与GET逻辑类似,所以直接复制上一段代码来解析/.但是在打印输出时会发现GET被重新输出 space = false; // 获取 while(!space) { chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } b = byteBuffer.array(); c = Arrays.copyOfRange(b, 0, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); } } return true; }
第二步: 解决第一步当中的问题。在Http11InputBuffer定义一个全局 变量private int parsingRequestLineStart = 0;然后修改代码如下
boolean parseRequestLine() throws IOException { byte chr = 0; if (parsingRequestLinePhase < 2) { wrapper.read(byteBuffer); System.out.println("postion="+byteBuffer.position()+";limit=" + byteBuffer.limit()); if (byteBuffer.position() > 0) { System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8)); byteBuffer.flip(); do{ chr = byteBuffer.get(); }while((chr == (byte) '\r') || (chr == (byte) '\n')); byteBuffer.position(byteBuffer.position() - 1); // 新增加的 parsingRequestLineStart = byteBuffer.position(); // =====================-*-__-*-===================================// boolean space = false; // 获取GET while(!space) { chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); parsingRequestLineStart = byteBuffer.position(); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); // =====================-*-__-*-===================================// space = false; // 获取 / while(!space) { chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } b = byteBuffer.array(); c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); parsingRequestLineStart = byteBuffer.position(); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); // =====================-*-__-*-===================================// space = false; // 获取 HTTP/1.1 while(!space) { chr = byteBuffer.get(); // 这里和上一个有点区别,判断换行 if ((chr == (byte) '\r') || (chr == (byte) '\n')) { space = true; } } b = byteBuffer.array(); c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); } } return true; }
第三步: 第二步中存在的一个问题是我们只尝试读取了一次数据,有可能数据没有接收全,导致后续解析出错。所以我们需要一种尝试重新读取数据的方法。在Http11InputBuffer中新建一个fill方法,并重写parseRequestLine方法。运行结果和上一步基本一样就不在贴图了。有一点需要注意的是我们是根据position和limit的关系来判断是否需要重新读取数据的,所以我们要重写init方法对position和limit进行设置。
public void init(SocketWrapperBase<?> socketWrapper) { wrapper = socketWrapper; byteBuffer.position(0).limit(0); }
private boolean fill() throws IOException { int nRead = -1; byteBuffer.mark(); try { if (byteBuffer.position() < byteBuffer.limit()) { byteBuffer.position(byteBuffer.limit()); } byteBuffer.limit(byteBuffer.capacity()); nRead = wrapper.read(byteBuffer); } finally { byteBuffer.limit(byteBuffer.position()).reset(); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() + ";nRead=" + nRead); } return nRead > 0; }
boolean parseRequestLine() throws IOException { byte chr = 0; do{ if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); }while((chr == (byte) '\r') || (chr == (byte) '\n')); byteBuffer.position(byteBuffer.position() - 1); parsingRequestLineStart = byteBuffer.position(); // =====================-*-__-*-===================================// boolean space = false; // 获取GET while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } byteBuffer.position(byteBuffer.position() - 1); byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLineStart = byteBuffer.position(); // =====================-*-__-*-===================================// space = false; // 获取 空格 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (!(chr == (byte) ' ' || chr == (byte) '\t')) { space = true; byteBuffer.position(byteBuffer.position() - 1); } } b = byteBuffer.array(); c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion11="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLineStart = byteBuffer.position(); // =====================-*-__-*-===================================// space = false; // 获取 / while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } byteBuffer.position(byteBuffer.position() - 1); b = byteBuffer.array(); c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLineStart = byteBuffer.position(); // =====================-*-__-*-===================================// space = false; // 获取 空格 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (!(chr == (byte) ' ' || chr == (byte) '\t')) { space = true; byteBuffer.position(byteBuffer.position() - 1); } } b = byteBuffer.array(); c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); parsingRequestLineStart = byteBuffer.position(); System.out.println("postion11="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); // =====================-*-__-*-===================================// space = false; // 获取 HTTP/1.1 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if ((chr == (byte) '\r') || (chr == (byte) '\n')) { space = true; } } b = byteBuffer.array(); c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); return true; }
第四步: 使用 parsingRequestLinePhase来标志正确解析了请求行,修改parseRequestLine方法如下:
boolean parseRequestLine() throws IOException { byte chr = 0; if (parsingRequestLinePhase < 2) { do{ if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); }while((chr == (byte) '\r') || (chr == (byte) '\n')); byteBuffer.position(byteBuffer.position() - 1); parsingRequestLineStart = byteBuffer.position(); parsingRequestLinePhase = 2; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 2) { boolean space = false; // 获取GET while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } byteBuffer.position(byteBuffer.position() - 1); byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLineStart = byteBuffer.position(); parsingRequestLinePhase = 3; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 3) { boolean space = false; // 获取 空格 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (!(chr == (byte) ' ' || chr == (byte) '\t')) { space = true; byteBuffer.position(byteBuffer.position() - 1); } } byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion11="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLineStart = byteBuffer.position(); parsingRequestLinePhase = 4; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 4) { boolean space = false; // 获取 / while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; } } byteBuffer.position(byteBuffer.position() - 1); byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLineStart = byteBuffer.position(); parsingRequestLinePhase = 5; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 5) { boolean space = false; // 获取 空格 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (!(chr == (byte) ' ' || chr == (byte) '\t')) { space = true; byteBuffer.position(byteBuffer.position() - 1); } } byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); parsingRequestLineStart = byteBuffer.position(); System.out.println("postion11="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLinePhase = 6; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 6) { boolean space = false; // 获取 HTTP/1.1 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if ((chr == (byte) '\r') || (chr == (byte) '\n')) { space = true; } } byte[] b = byteBuffer.array(); byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position()); System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() +";content="+ new String(c)); parsingRequestLinePhase = 7; } if (parsingRequestLinePhase == 7) { parsingRequestLinePhase = 0; parsingRequestLineStart = 0; return true; } throw new IOException("解析错误"); }
第五步: 保存解析后的数据。在Tomcat源码中,当其解析请求后会将解析后的信息保存在Request对象中,这个Request对象是Tomcat内部对像,之后会将其转换为ServletRequest 对像。Tomcat会将接收到的数转换为一个数组,并将这个数组和数据索引封装成一个名为ByteChunk的对象,所以我们要新键一个ByteChunk类,如下:
public final class ByteChunk { // 缓存数据 private byte[] buff; protected int start; protected int end; public void setBytes(byte[] b, int off, int len) { buff = b; start = off; end = start + len; byte[] c = Arrays.copyOfRange(b, start, end); System.out.println("ByteChunk类setBytes();start=" + off + ";end=" + end +";content="+ new String(c)); } }
第六步: ByteChunk是用来保存字节数据的,在Tomcat源码中还有一个CharChunk是用来保存字符数据的,二者被封装在一个名为MessageBytes的类中,现在我们还用不到CharChunk所以先不管它。在新键一个MessageBytes类,如下
public final class MessageBytes implements Cloneable, Serializable { private final ByteChunk byteC=new ByteChunk(); public static MessageBytes newInstance() { return factory.newInstance(); } public void setBytes(byte[] b, int off, int len) { byteC.setBytes( b, off, len ); } private static final MessageBytesFactory factory = new MessageBytesFactory(); private static class MessageBytesFactory { protected MessageBytesFactory() { } public MessageBytes newInstance() { return new MessageBytes(); } } }
public class Request { // 保存请求方法 GET POST之类的 private final MessageBytes methodMB = MessageBytes.newInstance(); // -~-__-~- private final MessageBytes uriMB = MessageBytes.newInstance(); // 使用的协议 private final MessageBytes protoMB = MessageBytes.newInstance(); public MessageBytes method() { return methodMB; } public MessageBytes requestURI() { return uriMB; } public MessageBytes protocol() { return protoMB; } }
第八步: 重写parseRequestLine方法
boolean parseRequestLine() throws IOException { byte chr = 0; if (parsingRequestLinePhase < 2) { do{ if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); }while((chr == (byte) '\r') || (chr == (byte) '\n')); byteBuffer.position(byteBuffer.position() - 1); parsingRequestLineStart = byteBuffer.position(); parsingRequestLinePhase = 2; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 2) { boolean space = false; // 获取GET while(!space) { int pos = byteBuffer.position(); if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; // 保存数据 request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart); } } parsingRequestLinePhase = 3; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 3) { boolean space = false; // 获取 空格 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (!(chr == (byte) ' ' || chr == (byte) '\t')) { space = true; byteBuffer.position(byteBuffer.position() - 1); } } parsingRequestLineStart = byteBuffer.position(); parsingRequestLinePhase = 4; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 4) { boolean space = false; // 获取 / while(!space) { int pos = byteBuffer.position(); if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (chr == (byte) ' ' || chr == (byte) '\t') { space = true; request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart); } } parsingRequestLinePhase = 5; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 5) { boolean space = false; // 获取 空格 while(!space) { if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); if (!(chr == (byte) ' ' || chr == (byte) '\t')) { space = true; byteBuffer.position(byteBuffer.position() - 1); } } parsingRequestLineStart = byteBuffer.position(); parsingRequestLinePhase = 6; } // =====================-*-__-*-===================================// if (parsingRequestLinePhase == 6) { boolean space = false; // 获取 HTTP/1.1 while(!space) { int pos = byteBuffer.position(); if (byteBuffer.position() >= byteBuffer.limit()) { if (!fill()) { return false; } } chr = byteBuffer.get(); // 这里和上一个有点区别,判断换行 if ((chr == (byte) '\r') || (chr == (byte) '\n')) { space = true; request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart); } } parsingRequestLinePhase = 7; } if (parsingRequestLinePhase == 7) { parsingRequestLinePhase = 0; parsingRequestLineStart = 0; return true; } throw new IOException("解析错误"); }
结束 !!!