WebScarab关键源码分析(3)

今天读的比较多,就多写点吧。

一个Http代理要实现的功能不外乎就四句话,保存客户端发过来的http请求,将请求发给服务器端,搜集返回结果,并发给客户端,如果用webscarab的数据结构来描述这个过程的话也就:

 

客户socket的输入流----->> request对象 ------->>>response对象------->>>客户socket输出流

如果把这三个箭头当做3个过程,过程1个过程3在前面的博文中已经详细说明(不外乎就是request的read方法和response的write方法,等等,不应该是过程0和过程2吗?好吧我编程中毒了已经),而第二个过程(你要非要说第一个也行)就是一开始说的URLFetcher的功能。

URLFetcher实现HttpClient接口,该接口就一个方法:

Response fetchResponse(Request request)

也就是把request转换成response。

下面我们仔细研究URLFetcher这个类。

研究一个类最本质的是回答以下三个问题:

1、这个类是做什么的?他代表一种什么样的实体?代表一种什么样的逻辑?具有着一种什么样的能力?提供一种什么样的服务?当然这几句话 本质上都是一样的。

2、我们要如何做才能正确的使用这个类?如何实例化?如何传入数据?调用的方法是否有顺序要求?

3、它是怎么实现它的功能的。

第一个第二问题从黑盒的角度出发,第三个问题从白盒的角度出发。下面就回答这3个问题。

1、URLFetcher代表一种能力,一种根据Resquest对象获得Response对象的能力。

2、直接调用fetchResponse方法即可,但可以有选择性的去设置代理服务器地址、连接超时时间等。

3、fetchResponse方法代码较长,将一段一段的进行描述。

首先,是一些检查初始化以及检查运行中用到的各变量合法性的代码,以及一些Http协议相关验证头部分的代码,我不是很懂http的Authentication头一些知识,所以这些代码我很遗憾的不能给出确切的说明。

 if (_response != null) {
            _response.flushContentStream(); // flush the content stream, just in case it wasn't read
            _response = null;
        }
        if (request == null) {
            _logger.severe("Asked to fetch a null request");
            return null;
        }
        HttpUrl url = request.getURL();
        if (url == null) {
            _logger.severe("Asked to fetch a request with a null URL");
            return null;
        }
        
        // if the previous auth method was not "Basic", force a new connection
        if (_authCreds != null && !_authCreds.startsWith("Basic"))
            _lastRequestTime = 0;
        if (_proxyAuthCreds != null && !_proxyAuthCreds.startsWith("Basic")) 
            _lastRequestTime = 0;
        
        // Get any provided credentials from the request
        _authCreds = request.getHeader("Authorization");
        
        String keyFingerprint = request.getHeader("X-SSLClientCertificate");
        request.deleteHeader("X-SSLClientCertificate");
        if (keyFingerprint == null && _keyFingerprint == null) {
            // no problem
        } else if (keyFingerprint != null && _keyFingerprint != null && keyFingerprint.equals(_keyFingerprint)) {
            // no problem
        } else {
            // force a new connection, and change the fingerprint
            _keyFingerprint = keyFingerprint;
            _lastRequestTime = 0;
        }
        
        String status;
        
        String oldProxyAuthHeader = null;
        if (_proxyAuthCreds == null && _authenticator!= null && useProxy(url))
            _proxyAuthCreds = _authenticator.getProxyCredentials(url.toString().startsWith("https") ? _httpsProxy : _httpProxy, null);
        String proxyAuthHeader = constructAuthenticationHeader(null, _proxyAuthCreds);
        
        String oldAuthHeader = null;
        if (_authCreds == null && _authenticator!= null)
            _authCreds = _authenticator.getCredentials(url, null);
        String authHeader = constructAuthenticationHeader(null, _authCreds);


其次是一个很长的do----while循环

int tries = 0;
        do {
            // make sure that we have a "clean" request each time through
            request.deleteHeader("Authorization");
            request.deleteHeader("Proxy-Authorization");
            ......
            tries ++;
        } while (tries < 3 && ((status.equals("401") && authHeader != null) || (status.equals("407") && proxyAuthHeader != null)));
       


中间省略号省略了很多代码。可以看到当且仅当尝试次数小于3并且响应码为401或407且有相应验证头的话才会重新进入循环,否则循环体只执行一次。

其次是一段发送请求的代码:

  // make sure that we have a "clean" request each time through
            request.deleteHeader("Authorization");
            request.deleteHeader("Proxy-Authorization");
            
            _response = null;
            connect(url);
            if (_response != null) { // there was an error opening the socket
                return _response;
            }
            
            if (authHeader != null) {
                request.setHeader("Authorization", authHeader);
                if (authHeader.startsWith("NTLM") || authHeader.startsWith("Negotiate")) {
                    if (request.getVersion().equals("HTTP/1.0")) {
                        // we have to explicitly tell the server to keep the connection alive for 1.0
                        request.setHeader("Connection", "Keep-Alive");
                    } else {
                        request.deleteHeader("Connection");
                    }
                }
            }
            // depending on whether we are connected directly to the server, or via a proxy
            if (_direct) {
                request.writeDirect(_out);
            } else {
                if (proxyAuthHeader != null) {
                    request.setHeader("Proxy-Authorization", proxyAuthHeader);
                    if (proxyAuthHeader.startsWith("NTLM") || proxyAuthHeader.startsWith("Negotiate")) {
                        if (request.getVersion().equals("HTTP/1.0")) {
                            // we have to explicitly tell the server to keep the connection alive for 1.0
                            request.setHeader("Connection", "Keep-Alive");
                        } else {
                            request.deleteHeader("Connection");
                        }
                    }
                }
                request.write(_out);
            }
            _out.flush();
            _logger.finest("Request : \n" + request.toString());

首先清除各种认证头(不用怕丢失信息,相关认证信息已在函数开头那段代码中保存起来了),然后设置返回的结果为空,接着尝试进行socket链接到目的url,url是哪来的呢?从request里面提取出来的。

如果连接成功后,该类的成员变量_in和_out会设置成该socket的输入流和输出流。

连接上之后再判断一下response对象是否为空,如果不为空则表示该方法被重入了,再其它线程或许调用了这个方法,为了防止重入出错,则函数直接返回。

之后根据_direct变量判断是否将http请求发送到代理服务器,这个变量的值是通过比较url和当前对象代理设置来决定的,代码不再赘述。

如果直接发送到http服务器,则直接写到socket的输出流,否则加上proxy的认证头(如果有的话)再发送到输出流。

至此发送过程结束。

接下来是接收过程。

do {
                _response.read(_in);
                status = _response.getStatus();
            } while (status.equals("100"));
            
            {
                StringBuffer buff = new StringBuffer();
                buff.append(_response.getStatusLine()).append("\n");
                NamedValue[] headers = _response.getHeaders();
                if (headers != null)
                    for (int i=0; i< headers.length; i++)
                        buff.append(headers[i].getName()).append(": ").append(headers[i].getValue()).append("\n");
                _logger.finest("Response:\n" + buff.toString());
            }
            
            if (status.equals("407")) {
                _response.flushContentStream();
                oldProxyAuthHeader = proxyAuthHeader;
                String[] challenges = _response.getHeaders("Proxy-Authenticate");
                if (_proxyAuthCreds == null && _authenticator != null) {
                    _proxyAuthCreds = _authenticator.getProxyCredentials(_httpProxy, challenges);
                }
                proxyAuthHeader = constructAuthenticationHeader(challenges, _proxyAuthCreds);
                if (proxyAuthHeader != null && oldProxyAuthHeader != null && oldProxyAuthHeader.equals(proxyAuthHeader)) {
                    _logger.info("No possible authentication");
                    proxyAuthHeader = null;
                }
            }
            
            if (status.equals("401")) {
                _response.flushContentStream();
                oldAuthHeader = authHeader;
                String[] challenges = _response.getHeaders("WWW-Authenticate");
                if (_authCreds == null && _authenticator != null)
                    _authCreds = _authenticator.getCredentials(url, challenges);
                _logger.finer("Auth creds are " + _authCreds);
                authHeader = constructAuthenticationHeader(challenges, _authCreds);
                _logger.finer("Auth header is " + authHeader);
                if (authHeader != null && oldAuthHeader != null && oldAuthHeader.equals(authHeader)) {
                    _logger.info("No possible authentication");
                    authHeader = null;
                }
            }
            
            // if the request method is HEAD, we get no contents, EVEN though there
            // may be a Content-Length header.
            if (request.getMethod().equals("HEAD")) _response.setNoBody();
            
            _logger.info(request.getURL() +" : " + _response.getStatusLine());
            
            String connection = _response.getHeader("Proxy-Connection");
            if (connection != null && "close".equalsIgnoreCase(connection)) {
                _in = null;
                _out = null;
                // do NOT close the socket itself, since the message body has not yet been read!
            } else {
                connection = _response.getHeader("Connection");
                String version = request.getVersion();
                if (version.equals("HTTP/1.0") && "Keep-alive".equalsIgnoreCase(connection)) {
                    _lastRequestTime = System.currentTimeMillis();
                } else if (version.equals("HTTP/1.1") && (connection == null || !connection.equalsIgnoreCase("Close"))) {
                    _lastRequestTime = System.currentTimeMillis();
                } else {
                    _logger.info("Closing connection!");
                    _in = null;
                    _out = null;
                    // do NOT close the socket itself, since the message body has not yet been read!
                }
            }


首先是一个do-while循环,尝试从输入流里得到服务器返回的信息,并独到response对象中。值得注意的是,如果返回码是100(100代表continue,感觉实际用的不多),则继续读,否则这个循环体只执行一次。

其次新建一个buffer,将第一行状态行(就是http 200 ok)之类的,以及剩下的http响应头读到buffer里,随后输出。这个StringBuffer并没有在之后使用,或许只是起到输出log的作用????

值得注意的是此时的response的状态,根据第一篇日志里所说,read只读取头,并没有读取实际内容的状态,所以此时输入流_in的位置是指向内容位第一个字节,换句话说真正的内容还在操作系统协议栈里,并没有flush出来。


 

 (待续)

 

 

你可能感兴趣的:(WebScarab关键源码分析(3))