HttpClient类是进行TCP连接的实现类,

package sun.net.www.http;

import java.io.*;
import java.net.*;
import java.util.*;
import sun.net.NetworkClient;
import sun.net.ProgressSource;
import sun.net.ProgressMonitor;
import sun.net.www.MessageHeader;
import sun.net.www.HeaderParser;
import sun.net.www.MeteredStream;
import sun.net.www.ParseUtil;
import sun.net.www.protocol.http.HttpURLConnection;
import sun.misc.RegexpPool; import java.security.*;
/**
* @author Herb Jellinek
* @author Dave Brown
*/
public class HttpClient extends NetworkClient {
// whether this httpclient comes from the cache
protected boolean cachedHttpClient = false; private boolean inCache; protected CookieHandler cookieHandler; // Http requests we send
MessageHeader requests; // Http data we send with the headers
PosterOutputStream poster = null; // if we've had one io error
boolean failedOnce = false; /** regexp pool of hosts for which we should connect directly, not Proxy
* these are intialized from a property.
*/
private static RegexpPool nonProxyHostsPool = null; /** The string source of nonProxyHostsPool
*/
private static String nonProxyHostsSource = null; /** Response code for CONTINUE */
private static final int HTTP_CONTINUE = 100; /** Default port number for http daemons. REMIND: make these private */
static final int httpPortNumber = 80; /** return default port number (subclasses may override) */
protected int getDefaultPort () { return httpPortNumber; } static private int getDefaultPort(String proto) {
if ("http".equalsIgnoreCase(proto))
return 80;
if ("https".equalsIgnoreCase(proto))
return 443;
return -1;
} /* The following three data members are left in for binary */
/* backwards-compatibility. Unfortunately, HotJava sets them directly */
/* when it wants to change the settings. The new design has us not */
/* cache these, so this is unnecessary, but eliminating the data members */
/* would break HJB 1.1 under JDK 1.2. */
/* */
/* These data members are not used, and their values are meaningless. */
/* REMIND: Take them out for JDK 2.0! */
/**
* @deprecated
*/
// public static String proxyHost = null;
/**
* @deprecated
*/
// public static int proxyPort = 80; /* instance-specific proxy fields override the static fields if set.
* Used by FTP. These are set to the true proxy host/port if
* usingProxy is true.
*/
// private String instProxy = null;
// private int instProxyPort = -1; /* All proxying (generic as well as instance-specific) may be
* disabled through use of this flag
*/
protected boolean proxyDisabled; // are we using proxy in this instance?
public boolean usingProxy = false;
// target host, port for the URL
protected String host;
protected int port; /* where we cache currently open, persistent connections */
protected static KeepAliveCache kac = new KeepAliveCache(); private static boolean keepAliveProp = true; // retryPostProp is true by default so as to preserve behavior
// from previous releases.
private static boolean retryPostProp = true; volatile boolean keepingAlive = false; /* this is a keep-alive connection */
int keepAliveConnections = -1; /* number of keep-alives left */ /**Idle timeout value, in milliseconds. Zero means infinity,
* iff keepingAlive=true.
* Unfortunately, we can't always believe this one. If I'm connected
* through a Netscape proxy to a server that sent me a keep-alive
* time of 15 sec, the proxy unilaterally terminates my connection
* after 5 sec. So we have to hard code our effective timeout to
* 4 sec for the case where we're using a proxy. *SIGH*
*/
int keepAliveTimeout = 0; /** whether the response is to be cached */
private CacheRequest cacheRequest = null; /** Url being fetched. */
protected URL url; /* if set, the client will be reused and must not be put in cache */
public boolean reuse = false; /**
* A NOP method kept for backwards binary compatibility
* @deprecated -- system properties are no longer cached.
*/
@Deprecated
public static synchronized void resetProperties() {
} int getKeepAliveTimeout() {
return keepAliveTimeout;
} static {
String keepAlive = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("http.keepAlive")); String retryPost = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("sun.net.http.retryPost")); if (keepAlive != null) {
keepAliveProp = Boolean.valueOf(keepAlive).booleanValue();
} else {
keepAliveProp = true;
} if (retryPost != null) {
retryPostProp = Boolean.valueOf(retryPost).booleanValue();
} else
retryPostProp = true; } /**
* @return true iff http keep alive is set (i.e. enabled). Defaults
* to true if the system property http.keepAlive isn't set.
*/
public boolean getHttpKeepAliveSet() {
return keepAliveProp;
} protected HttpClient() {
} private HttpClient(URL url)
throws IOException {
this(url, (String)null, -1, false);
} protected HttpClient(URL url,
boolean proxyDisabled) throws IOException {
this(url, null, -1, proxyDisabled);
} /* This package-only CTOR should only be used for FTP piggy-backed on HTTP
* HTTP URL's that use this won't take advantage of keep-alive.
* Additionally, this constructor may be used as a last resort when the
* first HttpClient gotten through New() failed (probably b/c of a
* Keep-Alive mismatch).
*
* XXX That documentation is wrong ... it's not package-private any more
*/
public HttpClient(URL url, String proxyHost, int proxyPort)
throws IOException {
this(url, proxyHost, proxyPort, false);
}
//该构造方法的最后一行openserver()方法进行了TCP连接
protected HttpClient(URL url, Proxy p, int to) throws IOException {
proxy = (p == null) ? Proxy.NO_PROXY : p;
this.host = url.getHost();
this.url = url;
port = url.getPort();
if (port == -1) {
port = getDefaultPort();
}
setConnectTimeout(to); // get the cookieHandler if there is any
cookieHandler = (CookieHandler)java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
return CookieHandler.getDefault();
}
}); openServer();
} static protected Proxy newHttpProxy(String proxyHost, int proxyPort,
String proto) {
if (proxyHost == null || proto == null)
return Proxy.NO_PROXY;
int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort;
InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport);
return new Proxy(Proxy.Type.HTTP, saddr);
} /*
* This constructor gives "ultimate" flexibility, including the ability
* to bypass implicit proxying. Sometimes we need to be using tunneling
* (transport or network level) instead of proxying (application level),
* for example when we don't want the application level data to become
* visible to third parties.
*
* @param url the URL to which we're connecting
* @param proxy proxy to use for this URL (e.g. forwarding)
* @param proxyPort proxy port to use for this URL
* @param proxyDisabled true to disable default proxying
*/
private HttpClient(URL url, String proxyHost, int proxyPort,
boolean proxyDisabled)
throws IOException {
this(url, proxyDisabled ? Proxy.NO_PROXY :
newHttpProxy(proxyHost, proxyPort, "http"), -1);
} public HttpClient(URL url, String proxyHost, int proxyPort,
boolean proxyDisabled, int to)
throws IOException {
this(url, proxyDisabled ? Proxy.NO_PROXY :
newHttpProxy(proxyHost, proxyPort, "http"), to);
} /* This class has no public constructor for HTTP. This method is used to
* get an HttpClient to the specifed URL. If there's currently an
* active HttpClient to that server/port, you'll get that one.
*/
public static HttpClient New(URL url)
throws IOException {
return HttpClient.New(url, Proxy.NO_PROXY, -1, true);
} public static HttpClient New(URL url, boolean useCache)
throws IOException {
return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache);
} public static HttpClient New(URL url, Proxy p, int to, boolean useCache)
throws IOException {
if (p == null) {
p = Proxy.NO_PROXY;
}
HttpClient ret = null;
/* see if one's already around */
if (useCache) {
ret = (HttpClient) kac.get(url, null);
if (ret != null) {
if ((ret.proxy != null && ret.proxy.equals(p)) ||
(ret.proxy == null && p == null)) {
synchronized (ret) {
ret.cachedHttpClient = true;
assert ret.inCache;
ret.inCache = false;
}
} else {
// We cannot return this connection to the cache as it's
// KeepAliveTimeout will get reset. We simply close the connection.
// This should be fine as it is very rare that a connection
// to the same host will not use the same proxy.
ret.inCache = false;
ret.closeServer();
ret = null;
}
}
}
if (ret == null) {
ret = new HttpClient(url, p, to);
} else {
SecurityManager security = System.getSecurityManager();
if (security != null) {
if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) {
security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(),
url.getPort());
} else {
security.checkConnect(url.getHost(), url.getPort());
}
}
ret.url = url;
}
return ret;
} public static HttpClient New(URL url, Proxy p, int to) throws IOException {
return New(url, p, to, true);
} public static HttpClient New(URL url, String proxyHost, int proxyPort,
boolean useCache)
throws IOException {
return New(url, newHttpProxy(proxyHost, proxyPort, "http"), -1, useCache);
} public static HttpClient New(URL url, String proxyHost, int proxyPort,
boolean useCache, int to)
throws IOException {
return New(url, newHttpProxy(proxyHost, proxyPort, "http"), to, useCache);
} /* return it to the cache as still usable, if:
* 1) It's keeping alive, AND
* 2) It still has some connections left, AND
* 3) It hasn't had a error (PrintStream.checkError())
* 4) It hasn't timed out
*
* If this client is not keepingAlive, it should have been
* removed from the cache in the parseHeaders() method.
*/ public void finished() {
if (reuse) /* will be reused */
return;
keepAliveConnections--;
poster = null;
if (keepAliveConnections > 0 && isKeepingAlive() &&
!(serverOutput.checkError())) {
/* This connection is keepingAlive && still valid.
* Return it to the cache.
*/
putInKeepAliveCache();
} else {
closeServer();
}
} protected synchronized void putInKeepAliveCache() {
if (inCache) {
assert false : "Duplicate put to keep alive cache";
return;
}
inCache = true;
kac.put(url, null, this);
} protected boolean isInKeepAliveCache() {
return inCache;
} /*
* Close an idle connection to this URL (if it exists in the
* cache).
*/
public void closeIdleConnection() {
HttpClient http = (HttpClient) kac.get(url, null);
if (http != null) {
http.closeServer();
}
} /* We're very particular here about what our InputStream to the server
* looks like for reasons that are apparent if you can decipher the
* method parseHTTP(). That's why this method is overidden from the
* superclass.
* server : 要连接的IP port:要连接的端口
*/
public void openServer(String server, int port) throws IOException {
//serverSocket对象是一个Socket对象,是建立连接之后返回的Socket对象,调用父类NetworkClient类的doConnect方法
serverSocket = doConnect(server, port);
try {
serverOutput = new PrintStream(
new BufferedOutputStream(serverSocket.getOutputStream()),
false, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding+" encoding not found");
}
serverSocket.setTcpNoDelay(true);
} /*
* Returns true if the http request should be tunneled through proxy.
* An example where this is the case is Https.
*/
public boolean needsTunneling() {
return false;
} /*
* Returns true if this httpclient is from cache
*/
public boolean isCachedConnection() {
return cachedHttpClient;
} /*
* Finish any work left after the socket connection is
* established. In the normal http case, it's a NO-OP. Subclass
* may need to override this. An example is Https, where for
* direct connection to the origin server, ssl handshake needs to
* be done; for proxy tunneling, the socket needs to be converted
* into an SSL socket before ssl handshake can take place.
*/
public void afterConnect() throws IOException, UnknownHostException {
// NO-OP. Needs to be overwritten by HttpsClient
} /*
* call openServer in a privileged block
*/
private synchronized void privilegedOpenServer(final InetSocketAddress server)
throws IOException
{
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction() {
public Object run() throws IOException {
openServer(server.getHostName(), server.getPort());
return null;
}
});
} catch (java.security.PrivilegedActionException pae) {
throw (IOException) pae.getException();
}
} /*
* call super.openServer
*/
private void superOpenServer(final String proxyHost,
final int proxyPort)
throws IOException, UnknownHostException
{
super.openServer(proxyHost, proxyPort);
} /*
* call super.openServer in a privileged block
*/
private synchronized void privilegedSuperOpenServer(final String proxyHost,
final int proxyPort)
throws IOException
{
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction() {
public Object run() throws IOException
{
superOpenServer(proxyHost, proxyPort);
return null;
}
});
} catch (java.security.PrivilegedActionException pae) {
throw (IOException) pae.getException();
}
} /*
*无参数的openServer方法,实现开启TCP连接工作
*/
protected synchronized void openServer() throws IOException { SecurityManager security = System.getSecurityManager(); if (keepingAlive) { // already opened
if (security != null) {
security.checkConnect(host, port);
} return;
} String urlHost = url.getHost().toLowerCase(); if (url.getProtocol().equals("http") ||
url.getProtocol().equals("https") ) { if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
if (security != null) {
security.checkConnect(host, port);
}
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
if (security != null) {
// redundant?
security.checkConnect(host, port);
}
         //调用有参构造方法,传入要连接的host和port
openServer(host, port);
usingProxy = false;
return;
} } else {
/* we're opening some other kind of url, most likely an
* ftp url.
*/
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
if (security != null) {
security.checkConnect(host, port);
}
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
if (security != null) {
// redundant?
security.checkConnect(host, port);
}
super.openServer(host, port);
usingProxy = false;
return;
}
}
} public String getURLFile() throws IOException { String fileName = url.getFile();
if ((fileName == null) || (fileName.length() == 0))
fileName = "/"; /**
* proxyDisabled is set by subclass HttpsClient!
*/
if (usingProxy && !proxyDisabled) {
// Do not use URLStreamHandler.toExternalForm as the fragment
// should not be part of the RequestURI. It should be an
// absolute URI which does not have a fragment part.
StringBuffer result = new StringBuffer(128);
result.append(url.getProtocol());
result.append(":");
if (url.getAuthority() != null && url.getAuthority().length() > 0) {
result.append("//");
result.append(url.getAuthority());
}
if (url.getPath() != null) {
result.append(url.getPath());
}
if (url.getQuery() != null) {
result.append('?');
result.append(url.getQuery());
} fileName = result.toString();
}
if (fileName.indexOf('\n') == -1)
return fileName;
else
throw new java.net.MalformedURLException("Illegal character in URL");
} /**
* @deprecated
*/
@Deprecated
public void writeRequests(MessageHeader head) {
requests = head;
requests.print(serverOutput);
serverOutput.flush();
} public void writeRequests(MessageHeader head,
PosterOutputStream pos) throws IOException {
requests = head;
requests.print(serverOutput);
poster = pos;
if (poster != null)
poster.writeTo(serverOutput);
serverOutput.flush();
} /** Parse the first line of the HTTP request. It usually looks
something like: "HTTP/1.0 <number> comment\r\n". */ public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
throws IOException {
/* If "HTTP/*" is found in the beginning, return true. Let
* HttpURLConnection parse the mime header itself.
*
* If this isn't valid HTTP, then we don't try to parse a header
* out of the beginning of the response into the responses,
* and instead just queue up the output stream to it's very beginning.
* This seems most reasonable, and is what the NN browser does.
*/ try {
serverInput = serverSocket.getInputStream();
serverInput = new BufferedInputStream(serverInput);
return (parseHTTPHeader(responses, pi, httpuc));
} catch (SocketTimeoutException stex) {
// We don't want to retry the request when the app. sets a timeout
closeServer();
throw stex;
} catch (IOException e) {
closeServer();
cachedHttpClient = false;
if (!failedOnce && requests != null) {
if (httpuc.getRequestMethod().equals("POST") && !retryPostProp) {
// do not retry the request
} else {
// try once more
failedOnce = true;
openServer();
if (needsTunneling()) {
httpuc.doTunneling();
}
afterConnect();
writeRequests(requests, poster);
return parseHTTP(responses, pi, httpuc);
}
}
throw e;
} } public int setTimeout (int timeout) throws SocketException {
int old = serverSocket.getSoTimeout ();
serverSocket.setSoTimeout (timeout);
return old;
} private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
throws IOException {
/* If "HTTP/*" is found in the beginning, return true. Let
* HttpURLConnection parse the mime header itself.
*
* If this isn't valid HTTP, then we don't try to parse a header
* out of the beginning of the response into the responses,
* and instead just queue up the output stream to it's very beginning.
* This seems most reasonable, and is what the NN browser does.
*/ keepAliveConnections = -1;
keepAliveTimeout = 0; boolean ret = false;
byte[] b = new byte[8]; try {
int nread = 0;
serverInput.mark(10);
while (nread < 8) {
int r = serverInput.read(b, nread, 8 - nread);
if (r < 0) {
break;
}
nread += r;
}
String keep=null;
ret = b[0] == 'H' && b[1] == 'T'
&& b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
b[5] == '1' && b[6] == '.';
serverInput.reset();
if (ret) { // is valid HTTP - response started w/ "HTTP/1."
responses.parseHeader(serverInput); // we've finished parsing http headers
// check if there are any applicable cookies to set (in cache)
if (cookieHandler != null) {
URI uri = ParseUtil.toURI(url);
// NOTE: That cast from Map shouldn't be necessary but
// a bug in javac is triggered under certain circumstances
// So we do put the cast in as a workaround until
// it is resolved.
if (uri != null)
cookieHandler.put(uri, (Map<java.lang.String,java.util.List<java.lang.String>>)responses.getHeaders());
} /* decide if we're keeping alive:
* This is a bit tricky. There's a spec, but most current
* servers (10/1/96) that support this differ in dialects.
* If the server/client misunderstand each other, the
* protocol should fall back onto HTTP/1.0, no keep-alive.
*/
if (usingProxy) { // not likely a proxy will return this
keep = responses.findValue("Proxy-Connection");
}
if (keep == null) {
keep = responses.findValue("Connection");
}
if (keep != null && keep.toLowerCase().equals("keep-alive")) {
/* some servers, notably Apache1.1, send something like:
* "Keep-Alive: timeout=15, max=1" which we should respect.
*/
HeaderParser p = new HeaderParser(
responses.findValue("Keep-Alive"));
if (p != null) {
/* default should be larger in case of proxy */
keepAliveConnections = p.findInt("max", usingProxy?50:5);
keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
}
} else if (b[7] != '0') {
/*
* We're talking 1.1 or later. Keep persistent until
* the server says to close.
*/
if (keep != null) {
/*
* The only Connection token we understand is close.
* Paranoia: if there is any Connection header then
* treat as non-persistent.
*/
keepAliveConnections = 1;
} else {
keepAliveConnections = 5;
}
}
} else if (nread != 8) {
if (!failedOnce && requests != null) {
if (httpuc.getRequestMethod().equals("POST") && !retryPostProp) {
// do not retry the request
} else {
failedOnce = true;
closeServer();
cachedHttpClient = false;
openServer();
if (needsTunneling()) {
httpuc.doTunneling();
}
afterConnect();
writeRequests(requests, poster);
return parseHTTP(responses, pi, httpuc);
}
}
throw new SocketException("Unexpected end of file from server");
} else {
// we can't vouche for what this is....
responses.set("Content-type", "unknown/unknown");
}
} catch (IOException e) {
throw e;
} int code = -1;
try {
String resp;
resp = responses.getValue(0);
/* should have no leading/trailing LWS
* expedite the typical case by assuming it has
* form "HTTP/1.x <WS> 2XX <mumble>"
*/
int ind;
ind = resp.indexOf(' ');
while(resp.charAt(ind) == ' ')
ind++;
code = Integer.parseInt(resp.substring(ind, ind + 3));
} catch (Exception e) {} if (code == HTTP_CONTINUE) {
responses.reset();
return parseHTTPHeader(responses, pi, httpuc);
} int cl = -1; /*
* Set things up to parse the entity body of the reply.
* We should be smarter about avoid pointless work when
* the HTTP method and response code indicate there will be
* no entity body to parse.
*/
String te = null;
try {
te = responses.findValue("Transfer-Encoding");
} catch (Exception e) {}
if (te != null && te.equalsIgnoreCase("chunked")) {
serverInput = new ChunkedInputStream(serverInput, this, responses); /*
* If keep alive not specified then close after the stream
* has completed.
*/
if (keepAliveConnections <= 1) {
keepAliveConnections = 1;
keepingAlive = false;
} else {
keepingAlive = true;
}
failedOnce = false;
} else { /*
* If it's a keep alive connection then we will keep
* (alive if :-
* 1. content-length is specified, or
* 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
* 204 or 304 response must not include a message body.
*/
try {
cl = Integer.parseInt(responses.findValue("content-length"));
} catch (Exception e) {} String requestLine = requests.getKey(0); if ((requestLine != null &&
(requestLine.startsWith("HEAD"))) ||
code == HttpURLConnection.HTTP_NOT_MODIFIED ||
code == HttpURLConnection.HTTP_NO_CONTENT) {
cl = 0;
} if (keepAliveConnections > 1 &&
(cl >= 0 ||
code == HttpURLConnection.HTTP_NOT_MODIFIED ||
code == HttpURLConnection.HTTP_NO_CONTENT)) {
keepingAlive = true;
failedOnce = false;
} else if (keepingAlive) {
/* Previously we were keeping alive, and now we're not. Remove
* this from the cache (but only here, once) - otherwise we get
* multiple removes and the cache count gets messed up.
*/
keepingAlive=false;
}
} /* wrap a KeepAliveStream/MeteredStream around it if appropriate */ if (cl > 0) {
// In this case, content length is well known, so it is okay
// to wrap the input stream with KeepAliveStream/MeteredStream. if (pi != null) {
// Progress monitor is enabled
pi.setContentType(responses.findValue("content-type"));
} if (isKeepingAlive()) {
// Wrap KeepAliveStream if keep alive is enabled.
serverInput = new KeepAliveStream(serverInput, pi, cl, this);
failedOnce = false;
}
else {
serverInput = new MeteredStream(serverInput, pi, cl);
}
}
else if (cl == -1) {
// In this case, content length is unknown - the input
// stream would simply be a regular InputStream or
// ChunkedInputStream. if (pi != null) {
// Progress monitoring is enabled. pi.setContentType(responses.findValue("content-type")); // Wrap MeteredStream for tracking indeterministic
// progress, even if the input stream is ChunkedInputStream.
serverInput = new MeteredStream(serverInput, pi, cl);
}
else {
// Progress monitoring is disabled, and there is no
// need to wrap an unknown length input stream. // ** This is an no-op **
}
}
else {
if (pi != null)
pi.finishTracking();
} return ret;
} public synchronized InputStream getInputStream() {
return serverInput;
} public OutputStream getOutputStream() {
return serverOutput;
} public String toString() {
return getClass().getName()+"("+url+")";
} public final boolean isKeepingAlive() {
return getHttpKeepAliveSet() && keepingAlive;
} public void setCacheRequest(CacheRequest cacheRequest) {
this.cacheRequest = cacheRequest;
} CacheRequest getCacheRequest() {
return cacheRequest;
} protected void finalize() throws Throwable {
// This should do nothing. The stream finalizer will
// close the fd.
} public void setDoNotRetry(boolean value) {
// failedOnce is used to determine if a request should be retried.
failedOnce = value;
} /* Use only on connections in error. */
public void closeServer() {
try {
keepingAlive = false;
serverSocket.close();
} catch (Exception e) {}
} /**
* @return the proxy host being used for this client, or null
* if we're not going through a proxy
*/
public String getProxyHostUsed() {
if (!usingProxy) {
return null;
} else {
return ((InetSocketAddress)proxy.address()).getHostName();
}
} /**
* @return the proxy port being used for this client. Meaningless
* if getProxyHostUsed() gives null.
*/
public int getProxyPortUsed() {
if (usingProxy)
return ((InetSocketAddress)proxy.address()).getPort();
return -1;
}
}
/*
* Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net; import java.io.*;
import java.net.Socket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.net.Proxy;
import java.util.Arrays;
import java.security.AccessController;
import java.security.PrivilegedAction; /**
* This is the base class for network clients.
*
* @author Jonathan Payne
*/
public class NetworkClient {
protected Proxy proxy = Proxy.NO_PROXY;
/** Socket for communicating with server. */
protected Socket serverSocket = null; /** Stream for printing to the server. */
public PrintStream serverOutput; /** Buffered stream for reading replies from server. */
public InputStream serverInput; protected static int defaultSoTimeout;
protected static int defaultConnectTimeout; protected int readTimeout = -1;
protected int connectTimeout = -1;
/* Name of encoding to use for output */
protected static String encoding; static {
final int vals[] = {0, 0};
final String encs[] = { null }; AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 0).intValue();
vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 0).intValue();
encs[0] = System.getProperty("file.encoding", "ISO8859_1");
return null;
}
});
if (vals[0] == 0)
defaultSoTimeout = -1;
else
defaultSoTimeout = vals[0]; if (vals[1] == 0)
defaultConnectTimeout = -1;
else
defaultConnectTimeout = vals[1]; encoding = encs[0];
try {
if (!isASCIISuperset (encoding)) {
encoding = "ISO8859_1";
}
} catch (Exception e) {
encoding = "ISO8859_1";
}
} /**
* Test the named character encoding to verify that it converts ASCII
* characters correctly. We have to use an ASCII based encoding, or else
* the NetworkClients will not work correctly in EBCDIC based systems.
* However, we cannot just use ASCII or ISO8859_1 universally, because in
* Asian locales, non-ASCII characters may be embedded in otherwise
* ASCII based protocols (eg. HTTP). The specifications (RFC2616, 2398)
* are a little ambiguous in this matter. For instance, RFC2398 [part 2.1]
* says that the HTTP request URI should be escaped using a defined
* mechanism, but there is no way to specify in the escaped string what
* the original character set is. It is not correct to assume that
* UTF-8 is always used (as in URLs in HTML 4.0). For this reason,
* until the specifications are updated to deal with this issue more
* comprehensively, and more importantly, HTTP servers are known to
* support these mechanisms, we will maintain the current behavior
* where it is possible to send non-ASCII characters in their original
* unescaped form.
*/
private static boolean isASCIISuperset (String encoding) throws Exception {
String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
"abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,"; // Expected byte sequence for string above
byte[] chkB = { 48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,71,72,
73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,97,98,99,
100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,
115,116,117,118,119,120,121,122,45,95,46,33,126,42,39,40,41,59,
47,63,58,64,38,61,43,36,44}; byte[] b = chkS.getBytes (encoding);
return Arrays.equals (b, chkB);
} /** Open a connection to the server. */
public void openServer(String server, int port)
throws IOException, UnknownHostException {
if (serverSocket != null)
closeServer();
serverSocket = doConnect (server, port);
try {
serverOutput = new PrintStream(new BufferedOutputStream(
serverSocket.getOutputStream()),
true, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding +"encoding not found");
}
serverInput = new BufferedInputStream(serverSocket.getInputStream());
} /**
* Return a socket connected to the server, with any
* appropriate options pre-established
*/
protected Socket doConnect (String server, int port)
throws IOException, UnknownHostException {
Socket s;
if (proxy != null) {
if (proxy.type() == Proxy.Type.SOCKS) {
s = (Socket) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return new Socket(proxy);
}});
} else if (proxy.type() == Proxy.Type.DIRECT) {
s = createSocket();
} else {
// Still connecting through a proxy
// server & port will be the proxy address and port
s = new Socket(Proxy.NO_PROXY);
}
} else
s = createSocket();
// Instance specific timeouts do have priority, that means
// connectTimeout & readTimeout (-1 means not set)
// Then global default timeouts
// Then no timeout.
if (connectTimeout >= 0) {
s.connect(new InetSocketAddress(server, port), connectTimeout);
} else {
if (defaultConnectTimeout > 0) {
s.connect(new InetSocketAddress(server, port), defaultConnectTimeout);
} else {
s.connect(new InetSocketAddress(server, port));
}
}
if (readTimeout >= 0)
s.setSoTimeout(readTimeout);
else if (defaultSoTimeout > 0) {
s.setSoTimeout(defaultSoTimeout);
}
return s;
} /**
* The following method, createSocket, is provided to allow the
* https client to override it so that it may use its socket factory
* to create the socket.
*/
protected Socket createSocket() throws IOException {
return new java.net.Socket();
} protected InetAddress getLocalAddress() throws IOException {
if (serverSocket == null)
throw new IOException("not connected");
return serverSocket.getLocalAddress();
} /** Close an open connection to the server. */
public void closeServer() throws IOException {
if (! serverIsOpen()) {
return;
}
serverSocket.close();
serverSocket = null;
serverInput = null;
serverOutput = null;
} /** Return server connection status */
public boolean serverIsOpen() {
return serverSocket != null;
} /** Create connection with host <i>host</i> on port <i>port</i> */
public NetworkClient(String host, int port) throws IOException {
openServer(host, port);
} public NetworkClient() {} public void setConnectTimeout(int timeout) {
connectTimeout = timeout;
} public int getConnectTimeout() {
return connectTimeout;
} public void setReadTimeout(int timeout) {
if (serverSocket != null && timeout >= 0) {
try {
serverSocket.setSoTimeout(timeout);
} catch(IOException e) {
// We tried...
}
}
readTimeout = timeout;
} public int getReadTimeout() {
return readTimeout;
}
}

 

----------------------------------------------------------------------------------------------------------------------------------------------

下面是sun.net.NetworkClient类中的doConnect(String server,int port)方法,具体执行了创建TCP连接的工作

/**
public void openServer(String server, int port) throws IOException {
serverSocket = doConnect(server, port);
try {
serverOutput = new PrintStream(
new BufferedOutputStream(serverSocket.getOutputStream()),
false, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding+" encoding not found");
}
serverSocket.setTcpNoDelay(true);
}

  

* Return a socket connected to the server, with any appropriate options * pre-established */ protected Socket doConnect(String server, int port) throws IOException, UnknownHostException { Socket s; if (proxy != null) { if (proxy.type() == Proxy.Type.SOCKS) { s = (Socket) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new Socket(proxy); } }); } else if (proxy.type() == Proxy.Type.DIRECT) { s = createSocket(); } else { // Still connecting through a proxy // server & port will be the proxy address and port s = new Socket(Proxy.NO_PROXY); } } else s = createSocket(); // Instance specific timeouts do have priority, that means // connectTimeout & readTimeout (-1 means not set) // Then global default timeouts // Then no timeout. if (connectTimeout >= 0) { s.connect(new InetSocketAddress(server, port), connectTimeout); } else { if (defaultConnectTimeout > 0) { s.connect(new InetSocketAddress(server, port), defaultConnectTimeout); } else { s.connect(new InetSocketAddress(server, port)); } } if (readTimeout >= 0) s.setSoTimeout(readTimeout); else if (defaultSoTimeout > 0) { s.setSoTimeout(defaultSoTimeout); } return s; }

(1)final URL url = new URL(str);

final HttpURLConnection conn = (HttpURLConnection)url.openConnection();

.........

.........//对Connection进行一系列的参数设置

final OutputStream outputStream = conn.getOutputStream()

outputStream.write(body.getBytes(“UTF-8”));

sun.net.www.protocol.http.HttpURLConnection类当中的getOutputStream()方法

 /*
* Allowable input/output sequences:
* [interpreted as POST/PUT]
* - get output, [write output,] get input, [read input]
* - get output, [write output]
* [interpreted as GET]
* - get input, [read input]
* Disallowed:
* - get input, [read input,] get output, [write output]
*/
public synchronized OutputStream getOutputStream() throws IOException
{ try
{
if (!doOutput)
{
throw new ProtocolException("cannot write to a URLConnection" + " if doOutput=false - call setDoOutput(true)");
} if (method.equals("GET"))
{
method = "POST"; // Backward compatibility
}
if (!"POST".equals(method) && !"PUT".equals(method) && "http".equals(url.getProtocol()))
{
throw new ProtocolException("HTTP method " + method + " doesn't support output");
} // if there's already an input stream open, throw an exception
if (inputStream != null)
{
throw new ProtocolException("Cannot write output after reading input.");
} if (!checkReuseConnection())
connect(); /*
* REMIND: This exists to fix the HttpsURLConnection subclass.
* Hotjava needs to run on JDK1.1FCS. Do proper fix in subclass for
* 1.2 and remove this.
*/ if (streaming() && strOutputStream == null)
{
writeRequests();
}
ps = (PrintStream) http.getOutputStream();
if (streaming())
{
if (strOutputStream == null)
{
if (fixedContentLength != -1)
{
strOutputStream = new StreamingOutputStream(ps, fixedContentLength);
}
else if (chunkLength != -1)
{
strOutputStream = new StreamingOutputStream(new ChunkedOutputStream(ps, chunkLength), -1);
}
}
return strOutputStream;
}
else
{
if (poster == null)
{
poster = new PosterOutputStream();
}
return poster;
}
}
catch(RuntimeException e)
{
disconnectInternal();
throw e;
}
catch(IOException e)
{
disconnectInternal();
throw e;
}
}

(2) 调用本类的connect()方法

// overridden in HTTPS subclass    
public void connect() throws IOException {
plainConnect();
}

(3) 继续调用本类的plainConnect()方法

connected变量是java.net.URLConnection类当中的变量

/**
* If <code>false</code>, this connection object has not created a
* communications link to the specified URL. If <code>true</code>,
* the communications link has been established.
*/
protected boolean connected = false;

------

关于Cache,应该是个优化的重点

cacheHandler类是java.net.ResponseCache接口的实现类

protected void plainConnect()  throws IOException {
if (connected) {
return;
}
// try to see if request can be served from local cache
if (cacheHandler != null && getUseCaches()) {
try {
URI uri = ParseUtil.toURI(url);
if (uri != null) {
cachedResponse = cacheHandler.get(uri, getRequestMethod(), requests.getHeaders(EXCLUDE_HEADERS));
if ("https".equalsIgnoreCase(uri.getScheme())
&& !(cachedResponse instanceof SecureCacheResponse)) {
cachedResponse = null;
}
if (cachedResponse != null) {
cachedHeaders = mapToMessageHeader(cachedResponse.getHeaders());
cachedInputStream = cachedResponse.getBody();
}
}
} catch (IOException ioex) {
// ignore and commence normal connection
}
if (cachedHeaders != null && cachedInputStream != null) {
connected = true;
return;
} else {
cachedResponse = null;
}
}
try {
/* Try to open connections using the following scheme,
* return on the first one that's successful:
* 1) if (instProxy != null)
* connect to instProxy; raise exception if failed
* 2) else use system default ProxySelector
* 3) is 2) fails, make direct connection
*/ if (instProxy == null) { // no instance Proxy is set
/**
* Do we have to use a proxy?
*/
ProxySelector sel = (ProxySelector)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
return ProxySelector.getDefault();
}
});
Proxy p = null;
if (sel != null) {
URI uri = sun.net.www.ParseUtil.toURI(url);
Iterator<Proxy> it = sel.select(uri).iterator();
while (it.hasNext()) {
p = it.next();
try {
if (!failedOnce) {
http = getNewHttpClient(url, p, connectTimeout);
http.setReadTimeout(readTimeout);
} else {
// make sure to construct new connection if first
// attempt failed
http = getNewHttpClient(url, p, connectTimeout, false);
http.setReadTimeout(readTimeout);
}
break;
} catch (IOException ioex) {
if (p != Proxy.NO_PROXY) {
sel.connectFailed(uri, p.address(), ioex);
if (!it.hasNext()) {
// fallback to direct connection
http = getNewHttpClient(url, null, connectTimeout, false);
http.setReadTimeout(readTimeout);
break;
}
} else {
throw ioex;
}
continue;
}
}
} else {
// No proxy selector, create http client with no proxy
if (!failedOnce) {
http = getNewHttpClient(url, null, connectTimeout);
http.setReadTimeout(readTimeout);
} else {
// make sure to construct new connection if first
// attempt failed
http = getNewHttpClient(url, null, connectTimeout, false);
http.setReadTimeout(readTimeout);
}
}
} else {
if (!failedOnce) {
http = getNewHttpClient(url, instProxy, connectTimeout);
http.setReadTimeout(readTimeout);
} else {
// make sure to construct new connection if first
// attempt failed
http = getNewHttpClient(url, instProxy, connectTimeout, false);
http.setReadTimeout(readTimeout);
}
} ps = (PrintStream)http.getOutputStream();
} catch (IOException e) {
throw e;
}
// constructor to HTTP client calls openserver
connected = true;
}

  

经过各种纠结判断创建了新的HttpClient对象

在HttpClient对象的构造方法当中

protected HttpClient(URL url, Proxy p, int to) throws IOException {
proxy = (p == null) ? Proxy.NO_PROXY : p;
this.host = url.getHost();
this.url = url;
port = url.getPort();
if (port == -1) {
port = getDefaultPort();
}
setConnectTimeout(to); // get the cookieHandler if there is any
cookieHandler = (CookieHandler)java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
return CookieHandler.getDefault();
}
}); openServer();
}

出现了openServer()方法,openServer()方法

protected synchronized void openServer() throws IOException {

        SecurityManager security = System.getSecurityManager();

        if (keepingAlive) { // already opened
if (security != null) {
security.checkConnect(host, port);
} return;
} String urlHost = url.getHost().toLowerCase(); if (url.getProtocol().equals("http") ||
url.getProtocol().equals("https") ) { if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
if (security != null) {
security.checkConnect(host, port);
}
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
if (security != null) {
// redundant?
security.checkConnect(host, port);
}
openServer(host, port);
usingProxy = false;
return;
} } else {
/* we're opening some other kind of url, most likely an
* ftp url.
*/
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
if (security != null) {
security.checkConnect(host, port);
}
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
if (security != null) {
// redundant?
security.checkConnect(host, port);
}
super.openServer(host, port);
usingProxy = false;
return;
}
}
}

最终openServer()方法调用了有参数的openServer方法,该方法中调用了doConnect()方法

public void openServer(String server, int port) throws IOException {
serverSocket = doConnect(server, port);
try {
serverOutput = new PrintStream(
new BufferedOutputStream(serverSocket.getOutputStream()),
false, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding+" encoding not found");
}
serverSocket.setTcpNoDelay(true);
}

最终创建了TCP连接。

socket对象获得了getOutputStream输出流被装饰了两次分别称为了有缓存的Buffered 和Print

由于HttpClient类是NetworkClient类的子类,调用的doConnect方法是父类中方法,最终调用的是connect(SocketAddress xxx)方法来建立连接

   /**
* Return a socket connected to the server, with any
* appropriate options pre-established
*/
protected Socket doConnect (String server, int port)
throws IOException, UnknownHostException {
Socket s;
if (proxy != null) {
if (proxy.type() == Proxy.Type.SOCKS) {
s = (Socket) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return new Socket(proxy);
}});
} else if (proxy.type() == Proxy.Type.DIRECT) {
s = createSocket();
} else {
// Still connecting through a proxy
// server & port will be the proxy address and port
s = new Socket(Proxy.NO_PROXY);
}
} else
s = createSocket();
// Instance specific timeouts do have priority, that means
// connectTimeout & readTimeout (-1 means not set)
// Then global default timeouts
// Then no timeout.
if (connectTimeout >= 0) {
s.connect(new InetSocketAddress(server, port), connectTimeout);
} else {
if (defaultConnectTimeout > 0) {
s.connect(new InetSocketAddress(server, port), defaultConnectTimeout);
} else {
s.connect(new InetSocketAddress(server, port));
}
}
if (readTimeout >= 0)
s.setSoTimeout(readTimeout);
else if (defaultSoTimeout > 0) {
s.setSoTimeout(defaultSoTimeout);
}
return s;
}

  

JDK下sun.net.www.protocol.http.HttpURLConnection类-----Http客户端实现类的实现分析的更多相关文章

  1. .jre下的lib和jdk下的lib的区别

    jre是JDK的一个子集.提供一个运行环境.JDK的lib目录是给JDK用的,例如JDK下有一些工具,可能要用该目录中的文件.例如,编译器等.JRE的lib目录是为JVM,运行时候用的.包括所有的标准 ...

  2. Protocol Buffer学习教程之编译器与类文件(三)

    Protocol Buffer学习教程之编译器与类文件(三) 1. 概述 在前面两篇中,介绍了Protobuf的基本概念.应用场景.与protobuf的语法等.在此篇中将介绍如何自己编译protobu ...

  3. python 零散记录(七)(下) 新式类 旧式类 多继承 mro 类属性 对象属性

    python新式类 旧式类: python2.2之前的类称为旧式类,之后的为新式类.在各自版本中默认声明的类就是各自的新式类或旧式类,但在2.2中声明新式类要手动标明: 这是旧式类为了声明为新式类的方 ...

  4. idea下使用autowire注解注入对象,结果初始化不到类

    如果idea下使用autowire注解注入对象,结果初始化不到类,明明使用快捷键alt+insert是可以找到该注入的对象的. 而我们在使用的时候,缺报错了??? 注意,当我们在注入对象的时候,我们留 ...

  5. c#中@标志的作用 C#通过序列化实现深表复制 细说并发编程-TPL 大数据量下DataTable To List效率对比 【转载】C#工具类:实现文件操作File的工具类 异步多线程 Async .net 多线程 Thread ThreadPool Task .Net 反射学习

    c#中@标志的作用   参考微软官方文档-特殊字符@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/toke ...

  6. CSS 伪类(下)结构性伪类\UI伪类\动态伪类和其他伪类 valid check enable child required link visit

      伪类选择器汇总伪类选择器有4种, 结构性伪类\UI伪类\动态伪类和其他伪类. 具体如下 结构性伪类选择器结构性伪类选择器它能够根据元素在文档中的位置选择元素, 这类元素都有个前缀":&q ...

  7. idea lib下有jar包但是仍然报错 找不到类

    现象: idea lib下有jar包但是仍然报错 找不到类 但是有个奇怪现象 同样的配置下项目在eclipse中可以正常编译 启动. package com.puhui.car.aspect; imp ...

  8. Object类 任何类都是object类的子类 用object对象接收数组 object类的向上向下转型

    任何类都是object类的子类 用object对象接收数组 object类的向上向下转型

  9. python 私有和保护成员变量如何实现?—— "单下划线 " 开始的成员变量叫做保护变量,意思是只有类实例和子类实例能访问到这些变量;" 双下划线 " 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据

    默认情况下,Python中的成员函数和成员变量都是公开的(public),在python中没有类似public,private等关键词来修饰成员函数和成员变量.在python中定义私有变量只需要在变量 ...

随机推荐

  1. Sae 上传文件到Storage

    首先说一下几个地方: 1.上传使用ss.upload("domin域名","源地址","目标地址,也就是storage的地址");假设要上传 ...

  2. HDU 1242 rescue and 优先级队列的条目

    Problem Description Angel was caught by the MOLIGPY! He was put in prison by Moligpy. The prison is ...

  3. (转)Java Ant build.xml详解

    1,什么是ant ant是构建工具2,什么是构建概念到处可查到,形象来说,你要把代码从某个地方拿来,编译,再拷贝到某个地方去等等操作,当然不仅与此,但是主要用来干这个3,ant的好处跨平台   --因 ...

  4. SDWebImage 原理及使用

    这个类库提供一个UIImageView类别以支持加载来自网络的远程图片.具有缓存管理.异步下载.同一个URL下载次数控制和优化等特征. SDWebImage 加载图片的流程 入口 setImageWi ...

  5. java数组 数组工具类Arrays

    一.数组 1.java有严格的数据类型限制,一个数组只能声明一个数据类型,存放同一种数据类型. 2.虽然只能存放一种数据类型,假设A , 如果数据类型B 继承A,依然能存放进入数组. 3.数组的初始化 ...

  6. get方式请求会出现中文乱码。post方式不会。

    get方式请求会出现中文乱码.post方式不会.   如果是要解决get方式中文乱码问题,就需要做一个拦截器,或者在web.xml做一个get请求的配置 来自为知笔记(Wiz)

  7. dedeCMS修改文章更新发布时间问题

    今天在dedeCMS系统中,修改或文章时发现,只要提交以后,文章发布时间便是当前时间.但有时候修改文章以后并不想把文章发布时间也更新成修改时间.我希望的是,修改文章不对时间做更改保持文章原有发布时间, ...

  8. css - a:hover变色问题

    今天在帮我们学校做网站的时候,由于在css这里不是很擅长,过程中发现一个问题,a:hover的时候,字体的颜色不变.后来才发现将a和div的嵌套的问题, 我的css代码为: .left_box .lb ...

  9. js给当前日期加一天

    <script type="text/javascript"> function addDay(datetime, days) { var old_time = new ...

  10. maven中tomcat7-maven-plugin插件的使用

    1.(挺清晰,但是我在项目上尝试没有成功) http://blog.csdn.net/yhhazr/article/details/7866501 2.(算是有一些详细的运行命令吧,例如自动打包命令或 ...