InternalInputBuffer处理HTTP请求行-Tomcat源码-我们到底能走多远系列(11)
我们到底能走多远系列(11)
扯淡:
最近行情不好吗?跳槽的比较少嘛,哈哈。有些人一直抗拒跳槽,觉得弊端很多:什么业务积累,职务,别人觉得你不可靠啊等等。我就想:这一辈子时间有限,何必为了一颗可以乘凉的树,放弃穿过森林的机会呢?祝在跳槽路上的朋友 顺利!(ps:个人喜欢面试 那种刺激感)
最爽不顾躺着,最美不过夕阳。秋天的夕阳是一年中最华丽的,各位不要错过哦。
主题:
在tomcat中,一个http请求,会被送到Http11Processor类,执行这个类的process(Socket theSocket) 处理的传入的Socket,Socket里面装的就是http消息。
tomcat是如何调用到Http11Processor的process方法的,可以参照:http://blog.csdn.net/woorh/article/details/8017323
Http11Processor在org.apache.coyote.http11包下。
Http11Processor的rocess方法中,用inputBuffer.parseRequestLine();调用了解析http消息的请求行。这里的inputBuffer是tomcat自定义的InternalInputBuffer。
需要了解的是:
1,org.apache.coyote.Request 是tomcat内部使用用于存放关于request消息的数据结构
2,org.apache.tomcat.util.buf.MessageBytes 用于存放消息,在org.apache.coyote.Request中大量用于存放解析后的byte字符
3,org.apache.tomcat.util.buf.ByteChunk 真正用于存放数据的数据结构,存放的是byte[],org.apache.tomcat.util.buf.MessageBytes使用它。
大流程:
http消息通过inputBuffer解析后放到Request中,Request把它放到相应的MessageBytes,最后MessageBytes把它存到ByteChunk里。
以上都可以通过方法调用来完成流程。
主要关注的是解析的源代码,在查看源代码前需要了解http请求行的结构:可以参照:http://www.cnblogs.com/killbug/archive/2012/10/10/2719142.html
阅读前准备:
1,方法中全部的异常时,会调用getString方法,其实就是StringManager的写日志方法,这是tomcat中统一的管理日志的方法。详细的解释在前一篇中已经几时过了:Tomcat StringManager阅读学习
2,
转义字符 | 意义 | ASCII码值(十进制) |
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\\ | 代表一个反斜线字符''\' | 092 |
源码:
public void parseRequestLine() throws IOException { int start = 0; // // Skipping blank lines // 忽略空行 // byte chr = 0; do { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } chr = buf[pos++]; } while ((chr == Constants.CR) || (chr == Constants.LF)); pos--; // Mark the current buffer position start = pos; // // Reading the method name // Method name is always US-ASCII // // space类似于开关一样,当为false时,查内容,为true时,去除空行时间 boolean space = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // Spec says no CR or LF in method name if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) { throw new IllegalArgumentException( sm.getString("iib.invalidmethod")); } // Spec says single SP but it also says be tolerant of HT // 查出第一个空格,tab居然也是允许的 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true;//跳出循环 // 把下标记录下来,这里的method()得到一个Requast的MessageBytes:methodMB request.method().setBytes(buf, start, pos - start); } pos++; } // Spec says single SP but also says be tolerant of multiple and/or HT // 忽略空格后面的空格或者tab,因为是忽略的内容所以不需要什么start while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++;// 忽略的方式就是继续移动下标 } else { space = false; } } // Mark the current buffer position start = pos; // 出现start了,后面肯定是需要记录下标 int end = 0; int questionPos = -1; // // Reading the URI // 上面是源码的注释,URI是什么?你懂的 // boolean eol = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // Spec says single SP but it also says be tolerant of HT // 寻找第二个空格,第一个空格和第二个空格之间就是传说中的URI if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; end = pos; } else if ((buf[pos] == Constants.CR) || (buf[pos] == Constants.LF)) { // HTTP/0.9 style request // 为了兼容HTTP/0.9格式 eol = true; space = true; end = pos; } else if ((buf[pos] == Constants.QUESTION) // 遇到‘?’了 && (questionPos == -1)) { // 把问号的位置先记录下来 questionPos = pos; } pos++; } // 把可能包含问号的URI的起始位和结束位记录下来 request.unparsedURI().setBytes(buf, start, end - start); if (questionPos >= 0) {// 有问号的情况 // 问号位置记录 request.queryString().setBytes(buf, questionPos + 1, end - questionPos - 1); // 把URI记录下来 request.requestURI().setBytes(buf, start, questionPos - start); } else { request.requestURI().setBytes(buf, start, end - start); } // Spec says single SP but also says be tolerant of multiple and/or HT // 这段算是重复代码吧,就是忽略空格用和tab用的 while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++; } else { space = false; } } // Mark the current buffer position start = pos; end = 0; // // Reading the protocol // Protocol is always US-ASCII // // eol标志位是为了标记是否是HTTP/0.9 style request 前面代码已经提到 // 最后一样:protocol(HTTP/ 1.1或1.0) while (!eol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // 查出 /r/n(CRLF) if (buf[pos] == Constants.CR) { end = pos; } else if (buf[pos] == Constants.LF) { if (end == 0) end = pos; eol = true; } pos++; } // 至此把head分成三部分,放到Request定义好的MessageBytes中去了 if ((end - start) > 0) { request.protocol().setBytes(buf, start, end - start); } else { request.protocol().setString(""); } }
总结习点:
1,利用表示位来控制解析式每个while的功能,上面代码用的是space
2,不用截断的方式存储需要的内容,而是记录开始和结束的下标。
让我们继续前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不会成功。
共勉