设为首页 收藏本站
查看: 1216|回复: 0

[经验分享] Tomcat源码学习(15)-How Tomcat works(转)

[复制链接]

尚未签到

发表于 2015-8-10 08:22:50 | 显示全部楼层 |阅读模式
HttpRequest类实现了javax.servlet.http.HttpServletRequest。跟随它的是一个叫做 HttpRequestFacade的facade类。Figure 3.2显示了HttpRequest类和它的相关类的UML图。
         HttpRequest类的很多方法都留空(你需要等到第4章才会完全实现),但是servlet程序员已经可以从到来的HTTP请求中获得头部,cookies和参数。这三种类型的值被存储在下面几个引用变量中:
protected HashMap headers = new HashMap();
protected ArrayList cookies = new ArrayList();
protected ParameterMap parameters = null;
     注意:ParameterMap类将会在“获取参数”这节中解释。
     因此,一个servlet程序员可以从javax.servlet.http.HttpServletRequest中的下列方法中取得正确的返回 值:getCookies,getDateHeader,getHeader, getHeaderNames, getHeaders, getParameter, getPrameterMap,getParameterNames和getParameterValues。就像你在HttpRequest类中看到的一样,一旦你取得了头部,cookies和填充了正确的值的参数,相关的方法的实现是很简单的。
     不用说,这里主要的挑战是解析HTTP请求和填充HttpRequest类。对于头部和cookies,HttpRequest类提供了addHeader和addCookie方法用于HttpProcessor的parseHeaders方法调用。当需要的时候,会使用 HttpRequest类的parseParameters方法来解析参数。在本节中所有的方法都会被讨论。
     因为HTTP请求的解析是一项相当复杂的任务,所以本节会分为以下几个小节:

  • 读取套接字的输入流
  • 解析请求行
  • 解析头部
  • 解析cookies
    获取参数
解析请求行
     HttpProcessor的process方法调用私有方法parseRequest用来解析请求行,例如一个HTTP请求的第一行。这里是一个请求行的例子:
GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1
     请求行的第二部分是URI加上一个查询字符串。在上面的例子中,URI是这样的:
/myApp/ModernServlet
     另外,在问好后面的任何东西都是查询字符串。因此,查询字符串是这样的:
userName=tarzan&password=pwd
     查询字符串可以包括零个或多个参数。在上面的例子中,有两个参数名/值对,userName/tarzan和password/pwd。在servlet/JSP编程中,参数名jsessionid是用来携带一个会话标识符。会话标识符经常被作为cookie来嵌入,但是程序员可以选择把它嵌入到查询字符串去,例如,当浏览器的cookie被禁用的时候。
     当parseRequest方法被HttpProcessor类的process方法调用的时候,request变量指向一个HttpRequest实例。parseRequest方法解析请求行用来获得几个值并把这些值赋给HttpRequest对象。现在,让我们来关注一下在Listing 3.4中的parseRequest方法。
         Listing 3.4:HttpProcessor类中的parseRequest方法
private void parseRequest(SocketInputStream input, OutputStream output)
throws IOException, ServletException {
     // Parse the incoming request line
     input.readRequestLine(requestLine);
     String method =
         new String(requestLine.method, 0, requestLine.methodEnd);
     String uri = null;
     String protocol = new String(requestLine.protocol, 0,
     requestLine.protocolEnd);
     // Validate the incoming request line
     if (method, length () < 1) {
         throw new ServletException(&quot;Missing HTTP request method&quot;);
     }
     else if (requestLine.uriEnd < 1) {
         throw new ServletException(&quot;Missing HTTP request URI&quot;);
     }
     // Parse any query parameters out of the request URI
     int question = requestLine.indexOf(&quot;?&quot;);
     if (question >= 0) {
         request.setQueryString(new String(requestLine.uri, question + 1,
         requestLine.uriEnd - question - 1));
         uri = new String(requestLine.uri, 0, question);
     }
     else {
         request.setQueryString(null);
         uri = new String(requestLine.uri, 0, requestLine.uriEnd);
     }
     // Checking for an absolute URI (with the HTTP protocol)
     if (!uri.startsWith(&quot;/&quot;)) {
         int pos = uri.indexOf(&quot;://&quot;);
         // Parsing out protocol and host name
         if (pos != -1) {
             pos = uri.indexOf('/', pos + 3);
             if (pos == -1) {
                 uri = &quot;&quot;;
             }
             else {
                 uri = uri.substring(pos);
             }
         }
     }
     // Parse any requested session ID out of the request URI
     String match = &quot;;jsessionid=&quot;;
     int semicolon = uri.indexOf(match);
     if (semicolon >= 0) {
         String rest = uri.substring(semicolon + match,length());
         int semicolon2 = rest.indexOf(';');
         if (semicolon2 >= 0) {
             request.setRequestedSessionId(rest.substring(0, semicolon2));
             rest = rest.substring(semicolon2);
         }
         else {
             request.setRequestedSessionId(rest);
             rest = &quot;&quot;;
         }
         request.setRequestedSessionURL(true);
         uri = uri.substring(0, semicolon) + rest;
     }
     else {
         request.setRequestedSessionId(null);
         request.setRequestedSessionURL(false);
     }
     // Normalize URI (using String operations at the moment)
     String normalizedUri = normalize(uri);
     // Set the corresponding request properties
     ((HttpRequest) request).setMethod(method);
     request.setProtocol(protocol);
     if (normalizedUri != null) {
         ((HttpRequest) request).setRequestURI(normalizedUri);
     }
     else {
         ((HttpRequest) request).setRequestURI(uri);
     }
     if (normalizedUri == null) {
         throw new ServletException(&quot;Invalid URI: &quot; + uri + &quot;'&quot;);
     }
}
     parseRequest方法首先调用SocketInputStream类的readRequestLine方法:
input.readRequestLine(requestLine);
     在这里requestLine是HttpProcessor里边的HttpRequestLine的一个实例:
private HttpRequestLine requestLine = new HttpRequestLine();
     调用它的readRequestLine方法来告诉SocketInputStream去填入HttpRequestLine实例。
     接下去,parseRequest方法获得请求行的方法,URI和协议:
String method =
     new String(requestLine.method, 0, requestLine.methodEnd);
String uri = null;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
     不过,在URI后面可以有查询字符串,假如存在的话,查询字符串会被一个问好分隔开来。因此,parseRequest方法试图首先获取查询字符串。并调用setQueryString方法来填充HttpRequest对象:
// Parse any query parameters out of the request URI
int question = requestLine.indexOf(&quot;?&quot;);
if (question >= 0) { // there is a query string.
     request.setQueryString(new String(requestLine.uri, question + 1,
     requestLine.uriEnd - question - 1));
     uri = new String(requestLine.uri, 0, question);
}
else {
     request.setQueryString (null);
     uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
     不过,大多数情况下,URI指向一个相对资源,URI还可以是一个绝对值,就像下面所示:
http://www.brainysoftware.com/index.html?name=Tarzan
     parseRequest方法同样也检查这种情况:
// Checking for an absolute URI (with the HTTP protocol)
if (!uri.startsWith(&quot;/&quot;)) {
     // not starting with /, this is an absolute URI
     int pos = uri.indexOf(&quot;://&quot;);
     // Parsing out protocol and host name
     if (pos != -1) {
         pos = uri.indexOf('/', pos + 3);
         if (pos == -1) {
             uri = &quot;&quot;;
         }
         else {
             uri = uri.substring(pos);
         }
     }
}
     然后,查询字符串也可以包含一个会话标识符,用jsessionid参数名来指代。因此,parseRequest方法也检查一个会话标识符。假如在查询字符串里边找到jessionid,方法就取得会话标识符,并通过调用setRequestedSessionId方法把值交给HttpRequest实例:
// Parse any requested session ID out of the request URI
String match = &quot;;jsessionid=&quot;;
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
     String rest = uri.substring(semicolon + match.length());
     int semicolon2 = rest.indexOf(';');
     if (semicolon2 >= 0) {
         request.setRequestedSessionId(rest.substring(0, semicolon2));
         rest = rest.substring(semicolon2);
     }
     else {
         request.setRequestedSessionId(rest);
         rest = &quot;&quot;;
     }
     request.setRequestedSessionURL (true);
     uri = uri.substring(0, semicolon) + rest;
}
else {
     request.setRequestedSessionId(null);
     request.setRequestedSessionURL(false);
}
     当jsessionid被找到,也意味着会话标识符是携带在查询字符串里边,而不是在cookie里边。因此,传递true给request的 setRequestSessionURL方法。否则,传递false给setRequestSessionURL方法并传递null给 setRequestedSessionURL方法。
     到这个时候,uri的值已经被去掉了jsessionid。
     接下去,parseRequest方法传递uri给normalize方法,用于纠正“异常”的URI。例如,任何\的出现都会给/替代。假如uri是正确的格式或者异常可以给纠正的话,normalize将会返回相同的或者被纠正后的URI。假如URI不能纠正的话,它将会给认为是非法的并且通常会返回null。在这种情况下(通常返回null),parseRequest将会在方法的最后抛出一个异常。
     最后,parseRequest方法设置了HttpRequest的一些属性:
((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
     ((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
     ((HttpRequest) request).setRequestURI(uri);
}
     还有,假如normalize方法的返回值是null的话,方法将会抛出一个异常:
if (normalizedUri == null) {
     throw new ServletException(&quot;Invalid URI: &quot; + uri + &quot;'&quot;);
}
解析头部
     一个HTTP头部是用类HttpHeader来代表的。这个类将会在第4章详细解释,而现在知道下面的内容就足够了:

  • 你可以通过使用类的无参数构造方法构造一个HttpHeader实例。


  • 一旦你拥有一个HttpHeader实例,你可以把它传递给SocketInputStream的readHeader方法。假如这里有头部需要读取,readHeader方法将会相应的填充HttpHeader对象。假如再也没有头部需要读取了,HttpHeader实例的nameEnd和valueEnd字段将会置零。


  • 为了获取头部的名称和值,使用下面的方法:


  • String name = new String(header.name, 0, header.nameEnd);


  • String value = new String(header.value, 0, header.valueEnd);
     parseHeaders方法包括一个while循环用于持续的从SocketInputStream中读取头部,直到再也没有头部出现为止。循环从构建一个HttpHeader对象开始,并把它传递给类SocketInputStream的readHeader方法:
HttpHeader header = new HttpHeader();
// Read the next header
input.readHeader(header);
     然后,你可以通过检测HttpHeader实例的nameEnd和valueEnd字段来测试是否可以从输入流中读取下一个头部信息:
if (header.nameEnd == 0) {
     if (header.valueEnd == 0) {
         return;
     }
     else {
         throw new ServletException              (sm.getString(&quot;httpProcessor.parseHeaders.colon&quot;));
     }
}
       假如存在下一个头部,那么头部的名称和值可以通过下面方法进行检索:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
     一旦你获取到头部的名称和值,你通过调用HttpRequest对象的addHeader方法来把它加入headers这个HashMap中:
request.addHeader(name, value);
     一些头部也需要某些属性的设置。例如,当servlet调用javax.servlet.ServletRequest的getContentLength方法的时候,content-length头部的值将被返回。而包含cookies的cookie头部将会给添加到cookie集合中。就这样,下面是其中一些过程:
if (name.equals(&quot;cookie&quot;)) {
     ... // process cookies here
}
else if (name.equals(&quot;content-length&quot;)) {
     int n = -1;
     try {
         n = Integer.parseInt (value);
     }
     catch (Exception e) {
         throw new ServletException(sm.getString(
             &quot;httpProcessor.parseHeaders.contentLength&quot;));
     }
     request.setContentLength(n);
}
else if (name.equals(&quot;content-type&quot;)) {
     request.setContentType(value);
}
     Cookie的解析将会在下一节“解析Cookies”中讨论。
解析Cookies
     Cookies是作为一个Http请求头部通过浏览器来发送的。这样一个头部名为&quot;cookie&quot;并且它的值是一些cookie名/值对。这里是一个包括两个cookie:username和password的cookie头部的例子。
Cookie: userName=budi; password=pwd;
     Cookie的解析是通过类org.apache.catalina.util.RequestUtil的parseCookieHeader方法来处理的。这个方法接受cookie头部并返回一个javax.servlet.http.Cookie数组。数组内的元素数量和头部里边的cookie名/值对个数是一样的。parseCookieHeader方法在Listing 3.5中列出。
Listing 3.5: The org.apache.catalina.util.RequestUtil class's parseCookieHeader method
public static Cookie[] parseCookieHeader(String header) {
     if ((header == null) || (header.length 0 < 1) )
         return (new Cookie[0]);
     ArrayList cookies = new ArrayList();
     while (header.length() > 0) {
         int semicolon = header.indexOf(';');
         if (semicolon < 0)
             semicolon = header.length();
         if (semicolon == 0)
             break;
         String token = header.substring(0, semicolon);
         if (semicolon < header.length())
             header = header.substring(semicolon + 1);
         else
             header = &quot;&quot;;
         try {
             int equals = token.indexOf('=');
             if (equals > 0) {
                 String name = token.substring(0, equals).trim();
                 String value = token.substring(equals+1).trim();
                 cookies.add(new Cookie(name, value));
             }
         }
         catch (Throwable e) {
             ;
         }
     }
     return ((Cookie[]) cookies.toArray (new Cookie [cookies.size ()]));
}
     还有,这里是HttpProcessor类的parseHeader方法中用于处理cookie的部分代码:
else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
     Cookie cookies[] = RequestUtil.ParseCookieHeader (value);
     for (int i = 0; i < cookies.length; i++) {
         if (cookies.getName().equals(&quot;jsessionid&quot;)) {
             // Override anything requested in the URL
             if (!request.isRequestedSessionIdFromCookie()) {
                 // Accept only the first session id cookie
                 request.setRequestedSessionId(cookies.getValue());
                 request.setRequestedSessionCookie(true);
                 request.setRequestedSessionURL(false);
             }
         }
         request.addCookie(cookies);
     }
}
获取参数
     你不需要马上解析查询字符串或者HTTP请求内容,直到servlet需要通过调用javax.servlet.http.HttpServletRequest的getParameter,
getParameterMap, getParameterNames或者getParameterValues方法来读取参数。因此,HttpRequest的这四个方法开头调用了parseParameter方法。
     这些参数只需要解析一次就够了,因为假如参数在请求内容里边被找到的话,参数解析将会使得SocketInputStream到达字节流的尾部。类HttpRequest使用一个布尔变量parsed来指示是否已经解析过了。
     参数可以在查询字符串或者请求内容里边找到。假如用户使用GET方法来请求servlet的话,所有的参数将在查询字符串里边出现。假如使用POST方法的话,你也可以在请求内容中找到一些。所有的名/值对将会存储在一个HashMap里边。Servlet程序员可以以Map的形式获得参数(通过调用HttpServletRequest的getParameterMap方法)和参数名/值。There is a catch, though. Servlet程序员不被允许修改参数值。因此,将使用一个特殊的HashMap:org.apache.catalina.util.ParameterMap。
     类ParameterMap继承java.util.HashMap,并使用了一个布尔变量locked。当locked是false的时候,名/值对仅仅可以添加,更新或者移除。否则,异常IllegalStateException会抛出。而随时都可以读取参数值。
类ParameterMap将会在Listing 3.6中列出。它覆盖了方法用于增加,更新和移除值。那些方法仅仅在locked为false的时候可以调用。
Listing 3.6: The org.apache.Catalina.util.ParameterMap class.
package org.apache.catalina.util;
import java.util.HashMap;
import java.util.Map;
public final class ParameterMap extends HashMap {
     public ParameterMap() {
         super ();
     }
     public ParameterMap(int initialCapacity) {
         super(initialCapacity);
     }
     public ParameterMap(int initialCapacity, float loadFactor) {
         super(initialCapacity, loadFactor);
     }
     public ParameterMap(Map map) {
         super(map);
     }
     private boolean locked = false;
     public boolean isLocked() {
         return (this.locked);
     }
     public void setLocked(boolean locked) {
         this.locked = locked;
     }
     private static final StringManager sm =
         StringManager.getManager(&quot;org.apache.catalina.util&quot;);
     public void clear() {
         if (locked)
             throw new IllegalStateException
                 (sm.getString(&quot;parameterMap.locked&quot;));
         super.clear();
     }
     public Object put(Object key, Object value) {
         if (locked)
             throw new IllegalStateException
                 (sm.getString(&quot;parameterMap.locked&quot;));
         return (super.put(key, value));
     }
     public void putAll(Map map) {
         if (locked)
             throw new IllegalStateException
                 (sm.getString(&quot;parameterMap.locked&quot;));
         super.putAll(map);
     }
     public Object remove(Object key) {
         if (locked)
             throw new IllegalStateException
                 (sm.getString(&quot;parameterMap.locked&quot;));
         return (super.remove(key));
     }
}
     现在,让我们来看parseParameters方法是怎么工作的。
     因为参数可以存在于查询字符串或者HTTP请求内容中,所以parseParameters方法会检查查询字符串和请求内容。一旦解析过后,参数将会在对象变量parameters中找到,所以方法的开头会检查parsed布尔变量,假如已经解析过的话,parsed将会返回true。
if (parsed)
return;
     然后,parseParameters方法创建一个名为results的ParameterMap变量,并指向parameters。假如
parameters为null的话,它将创建一个新的ParameterMap。
ParameterMap results = parameters;
if (results == null)
     results = new ParameterMap();
     然后,parseParameters方法打开parameterMap的锁以便写值。
results.setLocked(false);
     下一步,parseParameters方法检查字符编码,并在字符编码为null的时候赋予默认字符编码。
String encoding = getCharacterEncoding();
if (encoding == null)
     encoding = &quot;ISO-8859-1&quot;;
     然后,parseParameters方法尝试解析查询字符串。解析参数是使用org.apache.Catalina.util.RequestUtil的parseParameters方法来处理的。
// Parse any parameters specified in the query string
String queryString = getQueryString();
try {
     RequestUtil.parseParameters(results, queryString, encoding);
}
catch (UnsupportedEncodingException e) {
     ;
}
     接下来,方法尝试查看HTTP请求内容是否包含参数。这种情况发生在当用户使用POST方法发送请求的时候,内容长度大于零,并且内容类型是application/x-www-form-urlencoded的时候。所以,这里是解析请求内容的代码:
// Parse any parameters specified in the input stream
String contentType = getContentType();
if (contentType == null)
     contentType = &quot;&quot;;
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
     contentType = contentType.substring (0, semicolon).trim();
}
else {
     contentType = contentType.trim();
}
if (&quot;POST&quot;.equals(getMethod()) && (getContentLength() > 0)
     && &quot;application/x-www-form-urlencoded&quot;.equals(contentType)) {
     try {
         int max = getContentLength();
         int len = 0;
         byte buf[] = new byte[getContentLength()];
         ServletInputStream is = getInputStream();
         while (len < max) {
             int next = is.read(buf, len, max - len);
             if (next < 0 ) {
                 break;
             }
             len += next;
         }
         is.close();
         if (len < max) {
             throw new RuntimeException(&quot;Content length mismatch&quot;);
         }
         RequestUtil.parseParameters(results, buf, encoding);
     }
     catch (UnsupportedEncodingException ue) {
         ;
     }
     catch (IOException e) {
         throw new RuntimeException(&quot;Content read fail&quot;);
     }
}
     最后,parseParameters方法锁定ParameterMap,设置parsed为true,并把results赋予parameters。
// Store the final results
results.setLocked(true);
parsed = true;
parameters = results;

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-96716-1-1.html 上篇帖子: How to use BTM as the transaction manager in Tomcat 6.x 下篇帖子: Tomcat 5.5.23 文档阅读Tips 3
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表