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