HttpUrlConnection底层实现和关于java host绑定ip即时生效的设置及分析

Posted on

HttpUrlConnection底层实现和关于java host绑定ip即时生效的设置及分析

程序员之路
  1. 最近有个需求需要对于获取URL页面进行host绑定并且立即生效,在java里面实现可以用代理服务器来实现:因为在测试环境下可能需要通过绑定来访问测试环境的应用
  2. 实现代码如下:
  3. public static String getResponseText(String queryUrl,String host,String ip) { //queryUrl,完整的url,host和ip需要绑定的host和ip
  4. InputStream is = null;
  5. BufferedReader br = null;
  6. StringBuffer res = new StringBuffer();
  7. try {
  8. HttpURLConnection httpUrlConn = null;
  9. URL url = new URL(queryUrl);
  10. if(ip!=null){
  11. String str[] = ip.split("\.");
  12. byte[] b =new byte[str.length];
  13. for(int i=0,len=str.length;i<len;i++){
  14. b[i] = (byte)(Integer.parseInt(str[i],10));
  15. }
  16. Proxy proxy = new Proxy(Proxy.Type.HTTP,
  17. new InetSocketAddress(InetAddress.getByAddress(b), 80)); //b是绑定的ip,生成proxy代理对象,因为http底层是socket实现,
  18. httpUrlConn = (HttpURLConnection) url
  19. .openConnection(proxy);
  20. }else{
  21. httpUrlConn = (HttpURLConnection) url
  22. .openConnection();
  23. }
  24. httpUrlConn.setRequestMethod("GET");
  25. httpUrlConn.setDoOutput(true);
  26. httpUrlConn.setConnectTimeout(2000);
  27. httpUrlConn.setReadTimeout(2000);
  28. httpUrlConn.setDefaultUseCaches(false);
  29. httpUrlConn.setUseCaches(false);
  30. is = httpUrlConn.getInputStream();
  31. 那么底层对于proxy对象到底是怎么处理,底层的socket实现到底怎么样,带着这个疑惑看了下jdk的rt.jar对于这块的处理
  32. httpUrlConn = (HttpURLConnection) url.openConnection(proxy)
  33. java.net.URL类里面的openConnection方法:
  34. public URLConnection openConnection(Proxy proxy){
  35. …
  36. return handler.openConnection(this, proxy); Handler是sun.net.www.protocol.http.Handler.java类,继承java.net. URLStreamHandler.java类,用来处理http连接请求响应的。
  37. }
  38. Handler的方法:
  39. protected java.net.URLConnection openConnection(URL u, Proxy p)
  40. throws IOException {
  41. return new HttpURLConnection(u, p, this);
  42. }
  43. 只是简单的生成sun.net.www.protocl.http.HttpURLConnection对象,并进行初始化
  44. protected HttpURLConnection(URL u, Proxy p, Handler handler) {
  45. super(u);
  46. requests = new MessageHeader(); 请求头信息生成类
  47. responses = new MessageHeader(); 响应头信息解析类
  48. this.handler = handler;
  49. instProxy = p; 代理服务器对象
  50. cookieHandler = (CookieHandler)java.security.AccessController.doPrivileged(
  51. new java.security.PrivilegedAction() {
  52. public Object run() {
  53. return CookieHandler.getDefault();
  54. }
  55. });
  56. cacheHandler = (ResponseCache)java.security.AccessController.doPrivileged(
  57. new java.security.PrivilegedAction() {
  58. public Object run() {
  59. return ResponseCache.getDefault();
  60. }
  61. });
  62. }
  63. 最终在httpUrlConn.getInputStream();才进行socket连接,发送http请求,解析http响应信息。具体过程如下:
  64. sun.net.www.protocl.http.HttpURLConnection.java的getInputStream方法:
  65. public synchronized InputStream getInputStream() throws IOException {
  66. ...socket连接
  67. connect();
  68. ...
  69. ps = (PrintStream)http.getOutputStream(); 获得输出流,打开连接之后已经生成。
  70. if (!streaming()) {
  71. writeRequests(); 输出http请求头信息
  72. }
  73. ...
  74. http.parseHTTP(responses, pi, this); 解析响应信息
  75. if(logger.isLoggable(Level.FINEST)) {
  76. logger.fine(responses.toString());
  77. }
  78. inputStream = http.getInputStream(); 获得输入流
  79. }
  80. 其中connect()调用方法链:
  81. plainConnect(){
  82. ...
  83. Proxy p = null;
  84. if (sel != null) {
  85. URI uri = sun.net.www.ParseUtil.toURI(url);
  86. Iterator it = sel.select(uri).iterator();
  87. while (it.hasNext()) {
  88. p = it.next();
  89. try {
  90. if (!failedOnce) {
  91. http = getNewHttpClient(url, p, connectTimeout);
  92. ...
  93. }
  94. getNewHttpClient(){
  95. ...
  96. return HttpClient.New(url, p, connectTimeout, useCache);
  97. ...
  98. }
  99. 下面跟进去最终建立socket连接的代码:
  100. sun.net.www.http.HttpClient.java的openServer()方法建立socket连接:
  101. protected synchronized void openServer() throws IOException {
  102. ...
  103. if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
  104. sun.net.www.URLConnection.setProxiedHost(host);
  105. if (security != null) {
  106. security.checkConnect(host, port);
  107. }
  108. privilegedOpenServer((InetSocketAddress) proxy.address());最终socket连接的是设置的代理服务器的地址,
  109. ...
  110. }
  111. private synchronized void privilegedOpenServer(final InetSocketAddress server)
  112. throws IOException
  113. {
  114. try {
  115. java.security.AccessController.doPrivileged(
  116. new java.security.PrivilegedExceptionAction() {
  117. public Object run() throws IOException {
  118. openServer(server.getHostName(), server.getPort()); 注意openserver函数 这里的server的getHostName是设置的代理服务器,(ip或者hostname,如果是host绑定设置的代理服务器的ip,那么这里getHostName出来的就是ip地址,可以去查看InetSocketAddress类的getHostName方法)
  119. return null;
  120. }
  121. });
  122. } catch (java.security.PrivilegedActionException pae) {
  123. throw (IOException) pae.getException();
  124. }
  125. }
  126. public void openServer(String server, int port) throws IOException {
  127. serverSocket = doConnect(server, port); 生成的Socket连接对象
  128. try {
  129. serverOutput = new PrintStream(
  130. new BufferedOutputStream(serverSocket.getOutputStream()),
  131. false, encoding); 生成输出流,
  132. } catch (UnsupportedEncodingException e) {
  133. throw new InternalError(encoding+" encoding not found");
  134. }
  135. serverSocket.setTcpNoDelay(true);
  136. }
  137. protected Socket doConnect (String server, int port)
  138. throws IOException, UnknownHostException {
  139. Socket s;
  140. if (proxy != null) {
  141. if (proxy.type() == Proxy.Type.SOCKS) {
  142. s = (Socket) AccessController.doPrivileged(
  143. new PrivilegedAction() {
  144. public Object run() {
  145. return new Socket(proxy);
  146. }});
  147. } else
  148. s = new Socket(Proxy.NO_PROXY);
  149. } else
  150. s = new Socket();
  151. // Instance specific timeouts do have priority, that means
  152. // connectTimeout & readTimeout (-1 means not set)
  153. // Then global default timeouts
  154. // Then no timeout.
  155. if (connectTimeout >= 0) {
  156. s.connect(new InetSocketAddress(server, port), connectTimeout);
  157. } else {
  158. if (defaultConnectTimeout > 0) {
  159. s.connect(new InetSocketAddress(server, port), defaultConnectTimeout);//连接到代理服务器,看下面Socket类的connect方法代码
  160. } else {
  161. s.connect(new InetSocketAddress(server, port));
  162. }
  163. }
  164. if (readTimeout >= 0)
  165. s.setSoTimeout(readTimeout);
  166. else if (defaultSoTimeout > 0) {
  167. s.setSoTimeout(defaultSoTimeout);
  168. }
  169. return s;
  170. }
  171. 上面的new InetSocketAddress(server, port)这里会涉及到java DNS cache的处理,
  172. public InetSocketAddress(String hostname, int port) {
  173. if (port < 0 || port > 0xFFFF) {
  174. throw new IllegalArgumentException("port out of range:" + port);
  175. }
  176. if (hostname == null) {
  177. throw new IllegalArgumentException("hostname can't be null");
  178. }
  179. try {
  180. addr = InetAddress.getByName(hostname); //这里会有java DNS缓存的处理,先从缓存取hostname绑定的ip地址,如果取不到再通过OS的DNS cache机制去取,取不到再从DNS服务器上取。
  181. } catch(UnknownHostException e) {
  182. this.hostname = hostname;
  183. addr = null;
  184. }
  185. this.port = port;
  186. }
  187. 当然最终的Socket.java的connect方法
  188. java.net.socket
  189. public void connect(SocketAddress endpoint, int timeout) throws IOException {
  190. if (endpoint == null)
  191. if (timeout < 0)
  192. throw new IllegalArgumentException("connect: timeout can't be negative");
  193. if (isClosed())
  194. throw new SocketException("Socket is closed");
  195. if (!oldImpl && isConnected())
  196. throw new SocketException("already connected");
  197. if (!(endpoint instanceof InetSocketAddress))
  198. throw new IllegalArgumentException("Unsupported address type");
  199. InetSocketAddress epoint = (InetSocketAddress) endpoint;
  200. SecurityManager security = System.getSecurityManager();
  201. if (security != null) {
  202. if (epoint.isUnresolved())
  203. security.checkConnect(epoint.getHostName(),
  204. epoint.getPort());
  205. else
  206. security.checkConnect(epoint.getAddress().getHostAddress(),
  207. epoint.getPort());
  208. }
  209. if (!created)
  210. createImpl(true);
  211. if (!oldImpl)
  212. impl.connect(epoint, timeout);
  213. else if (timeout == 0) {
  214. if (epoint.isUnresolved()) //如果没有设置SocketAddress的ip地址,则用域名去访问
  215. impl.connect(epoint.getAddress().getHostName(),
  216. epoint.getPort());
  217. else
  218. impl.connect(epoint.getAddress(), epoint.getPort()); 最终socket连接的是设置的SocketAddress的ip地址,
  219. } else
  220. throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");
  221. connected = true;
  222. //*
  223. /* If the socket was not bound before the connect, it is now because
  224. /* the kernel will have picked an ephemeral port & a local address
  225. /*/
  226. bound = true;
  227. }
  228. 我们再看下通过socket来发送HTTP请求的处理代码,也就是sun.net.www.protocl.http.HttpURLConnection.java的getInputStream方法中调用的writeRequests()方法:
  229. private void writeRequests() throws IOException { 这段代码就是封装http请求的头请求信息,通过socket发送出去
  230. //* print all message headers in the MessageHeader
  231. /* onto the wire - all the ones we've set and any
  232. /* others that have been set
  233. /*/
  234. // send any pre-emptive authentication
  235. if (http.usingProxy) {
  236. setPreemptiveProxyAuthentication(requests);
  237. }
  238. if (!setRequests) {
  239. //* We're very particular about the order in which we
  240. /* set the request headers here. The order should not
  241. /* matter, but some careless CGI programs have been
  242. /* written to expect a very particular order of the
  243. /* standard headers. To name names, the order in which
  244. / Navigator3.0 sends them. In particular, we make /sure/*
  245. /* to send Content-type: <> and Content-length:<> second
  246. /* to last and last, respectively, in the case of a POST
  247. /* request.
  248. /*/
  249. if (!failedOnce)
  250. requests.prepend(method + " " + http.getURLFile()+" " +
  251. httpVersion, null);
  252. if (!getUseCaches()) {
  253. requests.setIfNotSet ("Cache-Control", "no-cache");
  254. requests.setIfNotSet ("Pragma", "no-cache");
  255. }
  256. requests.setIfNotSet("User-Agent", userAgent);
  257. int port = url.getPort();
  258. String host = url.getHost();
  259. if (port != -1 && port != url.getDefaultPort()) {
  260. host += ":" + String.valueOf(port);
  261. }
  262. requests.setIfNotSet("Host", host);
  263. requests.setIfNotSet("Accept", acceptString);
  264. //*
  265. /* For HTTP/1.1 the default behavior is to keep connections alive.
  266. /* However, we may be talking to a 1.0 server so we should set
  267. /* keep-alive just in case, except if we have encountered an error
  268. /* or if keep alive is disabled via a system property
  269. /*/
  270. // Try keep-alive only on first attempt
  271. if (!failedOnce && http.getHttpKeepAliveSet()) {
  272. if (http.usingProxy) {
  273. requests.setIfNotSet("Proxy-Connection", "keep-alive");
  274. } else {
  275. requests.setIfNotSet("Connection", "keep-alive");
  276. }
  277. } else {
  278. //*
  279. /* RFC 2616 HTTP/1.1 section 14.10 says:
  280. /* HTTP/1.1 applications that do not support persistent
  281. /* connections MUST include the "close" connection option
  282. /* in every message
  283. /*/
  284. requests.setIfNotSet("Connection", "close");
  285. }
  286. // Set modified since if necessary
  287. long modTime = getIfModifiedSince();
  288. if (modTime != 0 ) {
  289. Date date = new Date(modTime);
  290. //use the preferred date format according to RFC 2068(HTTP1.1),
  291. // RFC 822 and RFC 1123
  292. SimpleDateFormat fo =
  293. new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
  294. fo.setTimeZone(TimeZone.getTimeZone("GMT"));
  295. requests.setIfNotSet("If-Modified-Since", fo.format(date));
  296. }
  297. // check for preemptive authorization
  298. AuthenticationInfo sauth = AuthenticationInfo.getServerAuth(url);
  299. if (sauth != null && sauth.supportsPreemptiveAuthorization() ) {
  300. // Sets "Authorization"
  301. requests.setIfNotSet(sauth.getHeaderName(), sauth.getHeaderValue(url,method));
  302. currentServerCredentials = sauth;
  303. }
  304. if (!method.equals("PUT") && (poster != null || streaming())) {
  305. requests.setIfNotSet ("Content-type",
  306. "application/x-www-form-urlencoded");
  307. }
  308. if (streaming()) {
  309. if (chunkLength != -1) {
  310. requests.set ("Transfer-Encoding", "chunked");
  311. } else {
  312. requests.set ("Content-Length", String.valueOf(fixedContentLength));
  313. }
  314. } else if (poster != null) {
  315. // add Content-Length & POST/PUT data //
  316. synchronized (poster) {
  317. // close it, so no more data can be added //
  318. poster.close();
  319. requests.set("Content-Length",
  320. String.valueOf(poster.size()));
  321. }
  322. }
  323. // get applicable cookies based on the uri and request headers
  324. // add them to the existing request headers
  325. setCookieHeader();
  326. …
  327. }
  328. 再来看看把socket响应信息解析为http的响应信息的代码:
  329. sun.net.www.http.HttpClient.java的parseHTTP方法:
  330. private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
  331. throws IOException {
  332. // If "HTTP//" is found in the beginning, return true. Let
  333. /* HttpURLConnection parse the mime header itself.
  334. /*
  335. /* If this isn't valid HTTP, then we don't try to parse a header
  336. /* out of the beginning of the response into the responses,
  337. /* and instead just queue up the output stream to it's very beginning.
  338. /* This seems most reasonable, and is what the NN browser does.
  339. /*/
  340. keepAliveConnections = -1;
  341. keepAliveTimeout = 0;
  342. boolean ret = false;
  343. byte[] b = new byte[8];
  344. try {
  345. int nread = 0;
  346. serverInput.mark(10);
  347. while (nread < 8) {
  348. int r = serverInput.read(b, nread, 8 - nread);
  349. if (r < 0) {
  350. break;
  351. }
  352. nread += r;
  353. }
  354. String keep=null;
  355. ret = b[0] == 'H' && b[1] == 'T'
  356. && b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
  357. b[5] == '1' && b[6] == '.';
  358. serverInput.reset();
  359. if (ret) { // is valid HTTP - response started w/ "HTTP/1."
  360. responses.parseHeader(serverInput);
  361. // we've finished parsing http headers
  362. // check if there are any applicable cookies to set (in cache)
  363. if (cookieHandler != null) {
  364. URI uri = ParseUtil.toURI(url);
  365. // NOTE: That cast from Map shouldn't be necessary but
  366. // a bug in javac is triggered under certain circumstances
  367. // So we do put the cast in as a workaround until
  368. // it is resolved.
  369. if (uri != null)
  370. cookieHandler.put(uri, (Map>)responses.getHeaders());
  371. }
  372. //* decide if we're keeping alive:
  373. /* This is a bit tricky. There's a spec, but most current
  374. /* servers (10/1/96) that support this differ in dialects.
  375. /* If the server/client misunderstand each other, the
  376. /* protocol should fall back onto HTTP/1.0, no keep-alive.
  377. /*/
  378. if (usingProxy) { // not likely a proxy will return this
  379. keep = responses.findValue("Proxy-Connection");
  380. }
  381. if (keep == null) {
  382. keep = responses.findValue("Connection");
  383. }
  384. if (keep != null && keep.toLowerCase().equals("keep-alive")) {
  385. //* some servers, notably Apache1.1, send something like:
  386. /* "Keep-Alive: timeout=15, max=1" which we should respect.
  387. /*/
  388. HeaderParser p = new HeaderParser(
  389. responses.findValue("Keep-Alive"));
  390. if (p != null) {
  391. // default should be larger in case of proxy //
  392. keepAliveConnections = p.findInt("max", usingProxy?50:5);
  393. keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
  394. }
  395. } else if (b[7] != '0') {
  396. //*
  397. /* We're talking 1.1 or later. Keep persistent until
  398. /* the server says to close.
  399. /*/
  400. if (keep != null) {
  401. //*
  402. /* The only Connection token we understand is close.
  403. /* Paranoia: if there is any Connection header then
  404. /* treat as non-persistent.
  405. /*/
  406. keepAliveConnections = 1;
  407. } else {
  408. keepAliveConnections = 5;
  409. }
  410. }
  411. ……
  412. }
  413. 对于java.net包的http,ftp等各种协议的底层实现,可以参考rt.jar下面的几个包的代码:
  414. sun.net.www.protocl下的几个包。
  415. 在http client中也可以设置代理:
  416. HostConfiguration conf = new HostConfiguration();
  417. conf.setHost(host);
  418. conf.setProxy(ip, 80);
  419. statusCode = httpclient.executeMethod(conf,getMethod);
  420. httpclient自己也是基于socket封装的http处理的库。底层代理的实现是一样的。
  421. 另外一种不设置代理,通过反射修改InetAddress的cache也是ok的。但是这种方法非常不推荐,不要使用,因为对于proxy代理服务器概念了解不清楚,最开始还使用这种方法,
  422. public static void jdkDnsNoCache(final String host, final String ip)
  423. throws SecurityException, NoSuchFieldException,
  424. IllegalArgumentException, IllegalAccessException {
  425. if (StringUtils.isBlank(host)) {
  426. return;
  427. }
  428. final Class clazz = java.net.InetAddress.class;
  429. final Field cacheField = clazz.getDeclaredField("addressCache");
  430. cacheField.setAccessible(true);
  431. final Object o = cacheField.get(clazz);
  432. Class clazz2 = o.getClass();
  433. final Field cacheMapField = clazz2.getDeclaredField("cache");
  434. cacheMapField.setAccessible(true);
  435. final Map cacheMap = (Map) cacheMapField.get(o);
  436. AccessController.doPrivileged(new PrivilegedAction() {
  437. public Object run() {
  438. try {
  439. synchronized (o) {// 同步是必须的,因为o可能会有多个线程同时访问修改。
  440. // cacheMap.clear();//这步比较关键,用于清除原来的缓存
  441. // cacheMap.remove(host);
  442. if (!StringUtils.isBlank(ip)) {
  443. InetAddress inet = InetAddress.getByAddress(host,IPUtil.int2byte(ip));
  444. InetAddress addressstart = InetAddress.getByName(host);
  445. Object cacheEntry = cacheMap.get(host);
  446. cacheMap.put(host,newCacheEntry(inet,cacheEntry));
  447. // cacheMap.put(host,newCacheEntry(newInetAddress(host, ip)));
  448. }else{
  449. cacheMap.remove(host);
  450. }
  451. // System.out.println(getStaticProperty(
  452. // "java.net.InetAddress", "addressCacheInit"));
  453. // System.out.println(invokeStaticMethod("java.net.InetAddress","getCachedAddress",new
  454. // Object[]{host}));
  455. }
  456. } catch (Throwable te) {
  457. throw new RuntimeException(te);
  458. }
  459. return null;
  460. }
  461. });
  462. final Map cacheMapafter = (Map) cacheMapField.get(o);
  463. System.out.println(cacheMapafter);
  464. }
  465. 关于java中对于DNS的缓存设置可以参考:
  466. 1.在${java_home}/jre/lib/secuiry/java.secuiry文件,修改下面为
  467. networkaddress.cache.negative.ttl=0 DNS解析不成功的缓存时间
  468. networkaddress.cache.ttl=0 DNS解析成功的缓存的时间
  469. 2.jvm启动时增加下面两个启动环境变量
  470. -Dsun.net.inetaddr.ttl=0
  471. -Dsun.net.inetaddr.negative.ttl=0
  472. 如果在java程序中使用,可以这么设置设置:
  473. java.security.Security.setProperty("networkaddress.cache.ttl" , "0");
  474. java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "0");
  475. 还有几篇文档链接可以查看:
  476. http://www.rgagnon.com/javadetails/java-0445.html
  477. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6247501
  478. linux下关于OS DNS设置的几个文件是
  479. /etc/resolve.conf
  480. /etc/nscd.conf
  481. /etc/nsswitch.conf
  482. http://www.linuxfly.org/post/543/
  483. http://linux.die.net/man/5/nscd.conf
  484. http://www.linuxhomenetworking.com/wiki/index.php/Quick_HOWTO_:_Ch18_:_Configuring_DNS
  485. http://linux.die.net/man/5/nscd.conf
    最近有个需求需要对于获取URL页面进行host绑定并且立即生效,在java里面实现可以用代理服务器来实现:因为在测试环境下可能需要通过绑定来访问测试环境的应用 实现代码如下: public static String getResponseText(String queryUrl,String host,String ip) { //queryUrl,完整的url,host和ip需要绑定的host和ip InputStream is = null; BufferedReader br = null; StringBuffer res = new StringBuffer(); try { HttpURLConnection httpUrlConn = null; URL url = new URL(queryUrl); if(ip!=null){ String str[] = ip.split("\."); byte[] b =new byte[str.length]; for(int i=0,len=str.length;i it = sel.select(uri).iterator(); while (it.hasNext()) { p = it.next(); try { if (!failedOnce) { http = getNewHttpClient(url, p, connectTimeout); ... } getNewHttpClient(){ ... return HttpClient.New(url, p, connectTimeout, useCache); ... } 下面跟进去最终建立socket连接的代码: sun.net.www.http.HttpClient.java的openServer()方法建立socket连接: protected synchronized void openServer() throws IOException { ... 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());最终socket连接的是设置的代理服务器的地址, ... } 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()); 注意openserver函数 这里的server的getHostName是设置的代理服务器,(ip或者hostname,如果是host绑定设置的代理服务器的ip,那么这里getHostName出来的就是ip地址,可以去查看InetSocketAddress类的getHostName方法) return null; } }); } catch (java.security.PrivilegedActionException pae) { throw (IOException) pae.getException(); } } public void openServer(String server, int port) throws IOException { serverSocket = doConnect(server, port); 生成的Socket连接对象 try { serverOutput = new PrintStream( new BufferedOutputStream(serverSocket.getOutputStream()), false, encoding); 生成输出流, } catch (UnsupportedEncodingException e) { throw new InternalError(encoding+" encoding not found"); } serverSocket.setTcpNoDelay(true); } 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 s = new Socket(Proxy.NOPROXY); } else s = new Socket(); // 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);//连接到代理服务器,看下面Socket类的connect方法代码 } else { s.connect(new InetSocketAddress(server, port)); } } if (readTimeout >= 0) s.setSoTimeout(readTimeout); else if (defaultSoTimeout > 0) { s.setSoTimeout(defaultSoTimeout); } return s; } 上面的new InetSocketAddress(server, port)这里会涉及到java DNS cache的处理, public InetSocketAddress(String hostname, int port) { if (port < 0 || port > 0xFFFF) { throw new IllegalArgumentException("port out of range:" + port); } if (hostname == null) { throw new IllegalArgumentException("hostname can't be null"); } try { addr = InetAddress.getByName(hostname); //这里会有java DNS缓存的处理,先从缓存取hostname绑定的ip地址,如果取不到再通过OS的DNS cache机制去取,取不到再从DNS服务器上取。 } catch(UnknownHostException e) { this.hostname = hostname; addr = null; } this.port = port; } 当然最终的Socket.java的connect方法 java.net.socket public void connect(SocketAddress endpoint, int timeout) throws IOException { if (endpoint == null) if (timeout < 0) throw new IllegalArgumentException("connect: timeout can't be negative"); if (isClosed()) throw new SocketException("Socket is closed"); if (!oldImpl && isConnected()) throw new SocketException("already connected"); if (!(endpoint instanceof InetSocketAddress)) throw new IllegalArgumentException("Unsupported address type"); InetSocketAddress epoint = (InetSocketAddress) endpoint; SecurityManager security = System.getSecurityManager(); if (security != null) { if (epoint.isUnresolved()) security.checkConnect(epoint.getHostName(), epoint.getPort()); else security.checkConnect(epoint.getAddress().getHostAddress(), epoint.getPort()); } if (!created) createImpl(true); if (!oldImpl) impl.connect(epoint, timeout); else if (timeout == 0) { if (epoint.isUnresolved()) //如果没有设置SocketAddress的ip地址,则用域名去访问 impl.connect(epoint.getAddress().getHostName(), epoint.getPort()); else impl.connect(epoint.getAddress(), epoint.getPort()); 最终socket连接的是设置的SocketAddress的ip地址, } else throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)"); connected = true; // / If the socket was not bound before the connect, it is now because / the kernel will have picked an ephemeral port & a local address // bound = true; } 我们再看下通过socket来发送HTTP请求的处理代码,也就是sun.net.www.protocl.http.HttpURLConnection.java的getInputStream方法中调用的writeRequests()方法: private void writeRequests() throws IOException { 这段代码就是封装http请求的头请求信息,通过socket发送出去 // print all message headers in the MessageHeader / onto the wire - all the ones we've set and any / others that have been set // // send any pre-emptive authentication if (http.usingProxy) { setPreemptiveProxyAuthentication(requests); } if (!setRequests) { // We're very particular about the order in which we / set the request headers here. The order should not / matter, but some careless CGI programs have been / written to expect a very particular order of the / standard headers. To name names, the order in which / Navigator3.0 sends them. In particular, we make /sure/ / to send Content-type: <> and Content-length:<> second / to last and last, respectively, in the case of a POST / request. // if (!failedOnce) requests.prepend(method + " " + http.getURLFile()+" " + httpVersion, null); if (!getUseCaches()) { requests.setIfNotSet ("Cache-Control", "no-cache"); requests.setIfNotSet ("Pragma", "no-cache"); } requests.setIfNotSet("User-Agent", userAgent); int port = url.getPort(); String host = url.getHost(); if (port != -1 && port != url.getDefaultPort()) { host += ":" + String.valueOf(port); } requests.setIfNotSet("Host", host); requests.setIfNotSet("Accept", acceptString); // / For HTTP/1.1 the default behavior is to keep connections alive. / However, we may be talking to a 1.0 server so we should set / keep-alive just in case, except if we have encountered an error / or if keep alive is disabled via a system property // // Try keep-alive only on first attempt if (!failedOnce && http.getHttpKeepAliveSet()) { if (http.usingProxy) { requests.setIfNotSet("Proxy-Connection", "keep-alive"); } else { requests.setIfNotSet("Connection", "keep-alive"); } } else { // / RFC 2616 HTTP/1.1 section 14.10 says: / HTTP/1.1 applications that do not support persistent / connections MUST include the "close" connection option / in every message // requests.setIfNotSet("Connection", "close"); } // Set modified since if necessary long modTime = getIfModifiedSince(); if (modTime != 0 ) { Date date = new Date(modTime); //use the preferred date format according to RFC 2068(HTTP1.1), // RFC 822 and RFC 1123 SimpleDateFormat fo = new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); fo.setTimeZone(TimeZone.getTimeZone("GMT")); requests.setIfNotSet("If-Modified-Since", fo.format(date)); } // check for preemptive authorization AuthenticationInfo sauth = AuthenticationInfo.getServerAuth(url); if (sauth != null && sauth.supportsPreemptiveAuthorization() ) { // Sets "Authorization" requests.setIfNotSet(sauth.getHeaderName(), sauth.getHeaderValue(url,method)); currentServerCredentials = sauth; } if (!method.equals("PUT") && (poster != null || streaming())) { requests.setIfNotSet ("Content-type", "application/x-www-form-urlencoded"); } if (streaming()) { if (chunkLength != -1) { requests.set ("Transfer-Encoding", "chunked"); } else { requests.set ("Content-Length", String.valueOf(fixedContentLength)); } } else if (poster != null) { // add Content-Length & POST/PUT data // synchronized (poster) { // close it, so no more data can be added // poster.close(); requests.set("Content-Length", String.valueOf(poster.size())); } } // get applicable cookies based on the uri and request headers // add them to the existing request headers setCookieHeader(); … } 再来看看把socket响应信息解析为http的响应信息的代码: sun.net.www.http.HttpClient.java的parseHTTP方法: 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>)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; } } …… } 对于java.net包的http,ftp等各种协议的底层实现,可以参考rt.jar下面的几个包的代码: sun.net.www.protocl下的几个包。 在http client中也可以设置代理: HostConfiguration conf = new HostConfiguration(); conf.setHost(host); conf.setProxy(ip, 80); statusCode = httpclient.executeMethod(conf,getMethod); httpclient自己也是基于socket封装的http处理的库。底层代理的实现是一样的。 另外一种不设置代理,通过反射修改InetAddress的cache也是ok的。但是这种方法非常不推荐,不要使用,因为对于proxy代理服务器概念了解不清楚,最开始还使用这种方法, public static void jdkDnsNoCache(final String host, final String ip) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { if (StringUtils.isBlank(host)) { return; } final Class clazz = java.net.InetAddress.class; final Field cacheField = clazz.getDeclaredField("addressCache"); cacheField.setAccessible(true); final Object o = cacheField.get(clazz); Class clazz2 = o.getClass(); final Field cacheMapField = clazz2.getDeclaredField("cache"); cacheMapField.setAccessible(true); final Map cacheMap = (Map) cacheMapField.get(o); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { synchronized (o) {// 同步是必须的,因为o可能会有多个线程同时访问修改。 // cacheMap.clear();//这步比较关键,用于清除原来的缓存 // cacheMap.remove(host); if (!StringUtils.isBlank(ip)) { InetAddress inet = InetAddress.getByAddress(host,IPUtil.int2byte(ip)); InetAddress addressstart = InetAddress.getByName(host); Object cacheEntry = cacheMap.get(host); cacheMap.put(host,newCacheEntry(inet,cacheEntry)); // cacheMap.put(host,newCacheEntry(newInetAddress(host, ip))); }else{ cacheMap.remove(host); } // System.out.println(getStaticProperty( // "java.net.InetAddress", "addressCacheInit")); // System.out.println(invokeStaticMethod("java.net.InetAddress","getCachedAddress",new // Object[]{host})); } } catch (Throwable te) { throw new RuntimeException(te); } return null; } }); final Map cacheMapafter = (Map) cacheMapField.get(o); System.out.println(cacheMapafter); } 关于java中对于DNS的缓存设置可以参考: 1.在${java_home}/jre/lib/secuiry/java.secuiry文件,修改下面为 networkaddress.cache.negative.ttl=0 DNS解析不成功的缓存时间 networkaddress.cache.ttl=0 DNS解析成功的缓存的时间 2.jvm启动时增加下面两个启动环境变量 -Dsun.net.inetaddr.ttl=0 -Dsun.net.inetaddr.negative.ttl=0 如果在java程序中使用,可以这么设置设置: java.security.Security.setProperty("networkaddress.cache.ttl" , "0"); java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "0"); 还有几篇文档链接可以查看: http://www.rgagnon.com/javadetails/java-0445.html http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6247501 linux下关于OS DNS设置的几个文件是 /etc/resolve.conf /etc/nscd.conf /etc/nsswitch.conf http://www.linuxfly.org/post/543/ http://linux.die.net/man/5/nscd.conf http://www.linuxhomenetworking.com/wiki/index.php/Quick_HOWTO:Ch18:_Configuring_DNS http://linux.die.net/man/5/nscd.conf
希望本站内容对您有点用处,有什么疑问或建议请在后面留言评论
转载请注明作者(RobinChia)和出处 It so life ,请勿用于任何商业用途