今天读的比较多,就多写点吧。
一个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出来。
(待续)