1. 域名解析的入口点
getaddrinfo
或getAllByName
方法:在底层,Java 使用getaddrinfo
(在 Unix-like 系统中)或类似的系统调用来解析域名。在 Java 的InetAddress
类中,getAllByName
方法是解析域名的入口点之一。它会调用getAddressesFromNameService
方法来获取域名对应的 IP 地址列表。
2. getAddressesFromNameService
方法
- 调用
nameService.lookupAllHostAddr
:此方法尝试通过注册的NameService
(如PlatformNameService
)来解析域名。PlatformNameService
是一个内部类,它实现了NameService
接口,并调用impl.lookupAllHostAddr
方法来执行实际的域名解析。 - 处理异常:如果解析失败,会抛出
UnknownHostException
。对于 "localhost" 这样的特殊情况,会直接返回回环地址。 - 处理请求地址:如果指定了请求地址 (
reqAddr
),则会检查解析结果中是否包含该地址,并进行相应的处理(如旋转数组).
3. lookupAllHostAddr
方法
- 本地缓存和系统缓存:在某些实现中,可能会先检查本地缓存或系统缓存,以快速获取域名对应的 IP 地址。
- 调用底层系统调用:最终,会调用底层的系统调用(如
getaddrinfo
)来从 DNS 服务器获取域名对应的 IP 地址列表。这个过程可能涉及与本地 DNS 服务器的通信,以及 DNS 服务器之间的查询转发.
4. plainConnect
和 plainConnect0
方法
- 权限检查:在
plainConnect
方法中,会先检查是否已经连接。如果未连接,则会进行权限检查,确保有权限连接到指定的 URL。 - 使用缓存:在
plainConnect0
方法中,会尝试从本地缓存中获取响应。如果缓存命中且满足条件(如不是 HTTPS 请求),则会直接使用缓存的响应,而不需要进行实际的网络连接。 - 代理选择:如果没有缓存命中,则会根据配置选择合适的代理(如实例代理或系统默认代理)。如果没有代理或代理连接失败,则会尝试直接连接。
- 创建 HTTP 客户端:通过
getNewHttpClient
方法创建一个新的 HTTP 客户端,并设置连接超时和读取超时等参数。 - 打开服务器连接:调用
openServer
方法来打开与远程服务器的连接。如果使用代理,则会通过代理服务器进行连接。
5. openServer
方法
- 安全检查:如果存在安全管理器,则会进行连接权限检查。
- 处理代理:如果使用代理,则会根据代理类型(如 HTTP 代理或 SOCKS 代理)进行相应的处理。对于 HTTP 代理,会调用
privilegedOpenServer
方法来通过代理服务器建立连接。 - 直接连接:如果没有代理或代理连接失败,则会直接连接到远程服务器。调用
super.openServer
方法来执行实际的连接操作.
总结
Java 客户端请求流程中解析域名的过程涉及多个层次的处理,从 InetAddress
类的高层方法到底层的系统调用。通过缓存、代理选择和权限检查等机制,Java 能够灵活地处理各种网络环境下的域名解析和连接请求。这个过程确保了客户端能够高效、安全地与远程服务器进行通信。
##实验代码
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpUrlConnectionDemo {
public static void main(String[] args) {
try {
// 创建URL对象
URL url = new URL("http://www.baidu.com");
// 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法为GET
connection.setRequestMethod("GET");
// 设置超时时间
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// 获取响应码
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
// 如果响应成功,则读取响应内容
if (responseCode == HttpURLConnection.HTTP_OK) { // 200表示成功
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
// 关闭流
in.close();
// 输出响应内容
System.out.println("Response Content: " + content.toString());
} else {
System.out.println("GET request not worked");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
##java源码
protected void plainConnect() throws IOException {
synchronized (this) {
if (connected) {
return;
}
}
SocketPermission p = URLtoSocketPermission(this.url);
if (p != null) {
try {
AccessController.doPrivilegedWithCombiner(
new PrivilegedExceptionAction<>() {
public Void run() throws IOException {
plainConnect0();
return null;
}
}, null, p
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
} else {
// run without additional permission
plainConnect0();
}
}
protected void plainConnect0() throws IOException {
// 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(), getUserSetHeaders().getHeaders());
if ("https".equalsIgnoreCase(uri.getScheme())
&& !(cachedResponse instanceof SecureCacheResponse)) {
cachedResponse = null;
}
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
logger.finest("Cache Request for " + uri + " / " + getRequestMethod());
logger.finest("From cache: " + (cachedResponse != null ? cachedResponse.toString() : "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) else make a direct connection if ProxySelector is not present
*/
if (instProxy == null) { // no instance Proxy is set
/**
* Do we have to use a proxy?
*/
ProxySelector sel =
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<>() {
public ProxySelector run() {
return ProxySelector.getDefault();
}
});
if (sel != null) {
URI uri = sun.net.www.ParseUtil.toURI(url);
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
logger.finest("ProxySelector Request for " + uri);
}
Iterator<Proxy> it = sel.select(uri).iterator();
Proxy p;
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);
}
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
if (p != null) {
logger.finest("Proxy used: " + p.toString());
}
}
break;
} catch (IOException ioex) {
if (p != Proxy.NO_PROXY) {
sel.connectFailed(uri, p.address(), ioex);
if (!it.hasNext()) {
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
logger.finest("Retrying with proxy: " + p.toString());
}
http = getNewHttpClient(url, p, 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;
}
protected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout)
throws IOException {
return HttpClient.New(url, p, connectTimeout, this);
}
protected synchronized void openServer() throws IOException {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkConnect(host, port);
}
if (keepingAlive) { // already opened
return;
}
if (url.getProtocol().equals("http") ||
url.getProtocol().equals("https") ) {
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
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);
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
super.openServer(host, port);
usingProxy = false;
return;
}
}
}
public void openServer(String server, int port) throws IOException {
serverSocket = doConnect(server, port);
try {
OutputStream out = serverSocket.getOutputStream();
if (capture != null) {
out = new HttpCaptureOutputStream(out, capture);
}
serverOutput = new PrintStream(
new BufferedOutputStream(out),
false, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding+" encoding not found", e);
}
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 = AccessController.doPrivileged(
new PrivilegedAction<>() {
public Socket 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;
}
public static InetAddress[] getAllByName(String host)
throws UnknownHostException {
return getAllByName(host, null);
}
private static InetAddress[] getAllByName(String host, InetAddress reqAddr)
throws UnknownHostException {
if (host == null || host.isEmpty()) {
InetAddress[] ret = new InetAddress[1];
ret[0] = impl.loopbackAddress();
return ret;
}
validate(host);
boolean ipv6Expected = false;
if (host.charAt(0) == '[') {
// This is supposed to be an IPv6 literal
if (host.length() > 2 && host.charAt(host.length()-1) == ']') {
host = host.substring(1, host.length() -1);
ipv6Expected = true;
} else {
// This was supposed to be a IPv6 literal, but it's not
throw invalidIPv6LiteralException(host, false);
}
}
// Check and try to parse host string as an IP address literal
if (IPAddressUtil.digit(host.charAt(0), 16) != -1
|| (host.charAt(0) == ':')) {
byte[] addr = null;
int numericZone = -1;
String ifname = null;
if (!ipv6Expected) {
// check if it is IPv4 address only if host is not wrapped in '[]'
try {
addr = IPAddressUtil.validateNumericFormatV4(host);
} catch (IllegalArgumentException iae) {
var uhe = new UnknownHostException(host);
uhe.initCause(iae);
throw uhe;
}
}
if (addr == null) {
// Try to parse host string as an IPv6 literal
// Check if a numeric or string zone id is present first
int pos;
if ((pos = host.indexOf('%')) != -1) {
numericZone = checkNumericZone(host);
if (numericZone == -1) { /* remainder of string must be an ifname */
ifname = host.substring(pos + 1);
}
}
if ((addr = IPAddressUtil.textToNumericFormatV6(host)) == null &&
(host.contains(":") || ipv6Expected)) {
throw invalidIPv6LiteralException(host, ipv6Expected);
}
}
if(addr != null) {
InetAddress[] ret = new InetAddress[1];
if (addr.length == Inet4Address.INADDRSZ) {
if (numericZone != -1 || ifname != null) {
// IPv4-mapped address must not contain zone-id
throw new UnknownHostException(host + ": invalid IPv4-mapped address");
}
ret[0] = new Inet4Address(null, addr);
} else {
if (ifname != null) {
ret[0] = new Inet6Address(null, addr, ifname);
} else {
ret[0] = new Inet6Address(null, addr, numericZone);
}
}
return ret;
}
} else if (ipv6Expected) {
// We were expecting an IPv6 Literal since host string starts
// and ends with square brackets, but we got something else.
throw invalidIPv6LiteralException(host, true);
}
return getAllByName0(host, reqAddr, true, true);
}
static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr)
throws UnknownHostException
{
InetAddress[] addresses = null;
UnknownHostException ex = null;
validate(host);
try {
addresses = nameService.lookupAllHostAddr(host);
} catch (UnknownHostException uhe) {
if (host.equalsIgnoreCase("localhost")) {
addresses = new InetAddress[] { impl.loopbackAddress() };
}
else {
ex = uhe;
}
}
if (addresses == null) {
throw ex == null ? new UnknownHostException(host) : ex;
}
// More to do?
if (reqAddr != null && addresses.length > 1 && !addresses[0].equals(reqAddr)) {
// Find it?
int i = 1;
for (; i < addresses.length; i++) {
if (addresses[i].equals(reqAddr)) {
break;
}
}
// Rotate
if (i < addresses.length) {
InetAddress tmp, tmp2 = reqAddr;
for (int j = 0; j < i; j++) {
tmp = addresses[j];
addresses[j] = tmp2;
tmp2 = tmp;
}
addresses[i] = tmp2;
}
}
return addresses;
}
private static final class PlatformNameService implements NameService {
public InetAddress[] lookupAllHostAddr(String host)
throws UnknownHostException
{
return impl.lookupAllHostAddr(host);
}
public String getHostByAddr(byte[] addr)
throws UnknownHostException
{
return impl.getHostByAddr(addr);
}
}
public native InetAddress[] lookupAllHostAddr(String hostname)
throws UnknownHostException;
##openjdk17 对应解析域名的 C++源码。error = getaddrinfo(hostname, NULL, &hints, &res);
/*
* Class: java_net_Inet6AddressImpl
* Method: lookupAllHostAddr
* Signature: (Ljava/lang/String;)[[B
*/
JNIEXPORT jobjectArray JNICALL
Java_java_net_Inet6AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this,
jstring host) {
jobjectArray ret = NULL;
const char *hostname;
int error = 0;
struct addrinfo hints, *res = NULL, *resNew = NULL, *last = NULL,
*iterator;
initInetAddressIDs(env);
JNU_CHECK_EXCEPTION_RETURN(env, NULL);
if (IS_NULL(host)) {
JNU_ThrowNullPointerException(env, "host argument is null");
return NULL;
}
hostname = JNU_GetStringPlatformChars(env, host, JNI_FALSE);
CHECK_NULL_RETURN(hostname, NULL);
// try once, with our static buffer
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_UNSPEC;
error = getaddrinfo(hostname, NULL, &hints, &res);
if (error) {
#if defined(MACOSX)
// if getaddrinfo fails try getifaddrs
ret = lookupIfLocalhost(env, hostname, JNI_TRUE);
if (ret != NULL || (*env)->ExceptionCheck(env)) {
goto cleanupAndReturn;
}
#endif
// report error
NET_ThrowUnknownHostExceptionWithGaiError(env, hostname, error);
goto cleanupAndReturn;
} else {
int i = 0, inetCount = 0, inet6Count = 0, inetIndex = 0,
inet6Index = 0, originalIndex = 0;
int addressPreference =
(*env)->GetStaticIntField(env, ia_class, ia_preferIPv6AddressID);;
iterator = res;
while (iterator != NULL) {
// skip duplicates
int skip = 0;
struct addrinfo *iteratorNew = resNew;
while (iteratorNew != NULL) {
if (iterator->ai_family == iteratorNew->ai_family &&
iterator->ai_addrlen == iteratorNew->ai_addrlen) {
if (iteratorNew->ai_family == AF_INET) { /* AF_INET */
struct sockaddr_in *addr1, *addr2;
addr1 = (struct sockaddr_in *)iterator->ai_addr;
addr2 = (struct sockaddr_in *)iteratorNew->ai_addr;
if (addr1->sin_addr.s_addr == addr2->sin_addr.s_addr) {
skip = 1;
break;
}
} else {
int t;
struct sockaddr_in6 *addr1, *addr2;
addr1 = (struct sockaddr_in6 *)iterator->ai_addr;
addr2 = (struct sockaddr_in6 *)iteratorNew->ai_addr;
for (t = 0; t < 16; t++) {
if (addr1->sin6_addr.s6_addr[t] !=
addr2->sin6_addr.s6_addr[t]) {
break;
}
}
if (t < 16) {
iteratorNew = iteratorNew->ai_next;
continue;
} else {
skip = 1;
break;
}
}
} else if (iterator->ai_family != AF_INET &&
iterator->ai_family != AF_INET6) {
// we can't handle other family types
skip = 1;
break;
}
iteratorNew = iteratorNew->ai_next;
}
if (!skip) {
struct addrinfo *next
= (struct addrinfo *)malloc(sizeof(struct addrinfo));
if (!next) {
JNU_ThrowOutOfMemoryError(env, "Native heap allocation failed");
ret = NULL;
goto cleanupAndReturn;
}
memcpy(next, iterator, sizeof(struct addrinfo));
next->ai_next = NULL;
if (resNew == NULL) {
resNew = next;
} else {
last->ai_next = next;
}
last = next;
i++;
if (iterator->ai_family == AF_INET) {
inetCount++;
} else if (iterator->ai_family == AF_INET6) {
inet6Count++;
}
}
iterator = iterator->ai_next;
}
// allocate array - at this point i contains the number of addresses
ret = (*env)->NewObjectArray(env, i, ia_class, NULL);
if (IS_NULL(ret)) {
/* we may have memory to free at the end of this */
goto cleanupAndReturn;
}
if (addressPreference == java_net_InetAddress_PREFER_IPV6_VALUE) {
inetIndex = inet6Count;
inet6Index = 0;
} else if (addressPreference == java_net_InetAddress_PREFER_IPV4_VALUE) {
inetIndex = 0;
inet6Index = inetCount;
} else if (addressPreference == java_net_InetAddress_PREFER_SYSTEM_VALUE) {
inetIndex = inet6Index = originalIndex = 0;
}
iterator = resNew;
while (iterator != NULL) {
if (iterator->ai_family == AF_INET) {
jobject iaObj = (*env)->NewObject(env, ia4_class, ia4_ctrID);
if (IS_NULL(iaObj)) {
ret = NULL;
goto cleanupAndReturn;
}
setInetAddress_addr(env, iaObj, ntohl(((struct sockaddr_in*)iterator->ai_addr)->sin_addr.s_addr));
if ((*env)->ExceptionCheck(env))
goto cleanupAndReturn;
setInetAddress_hostName(env, iaObj, host);
if ((*env)->ExceptionCheck(env))
goto cleanupAndReturn;
(*env)->SetObjectArrayElement(env, ret, (inetIndex | originalIndex), iaObj);
inetIndex++;
} else if (iterator->ai_family == AF_INET6) {
jint scope = 0;
jboolean ret1;
jobject iaObj = (*env)->NewObject(env, ia6_class, ia6_ctrID);
if (IS_NULL(iaObj)) {
ret = NULL;
goto cleanupAndReturn;
}
ret1 = setInet6Address_ipaddress(env, iaObj, (char *)&(((struct sockaddr_in6*)iterator->ai_addr)->sin6_addr));
if (ret1 == JNI_FALSE) {
ret = NULL;
goto cleanupAndReturn;
}
scope = ((struct sockaddr_in6 *)iterator->ai_addr)->sin6_scope_id;
if (scope != 0) { // zero is default value, no need to set
setInet6Address_scopeid(env, iaObj, scope);
}
setInetAddress_hostName(env, iaObj, host);
if ((*env)->ExceptionCheck(env))
goto cleanupAndReturn;
(*env)->SetObjectArrayElement(env, ret, (inet6Index | originalIndex), iaObj);
inet6Index++;
}
if (addressPreference == java_net_InetAddress_PREFER_SYSTEM_VALUE) {
originalIndex++;
inetIndex = inet6Index = 0;
}
iterator = iterator->ai_next;
}
}
cleanupAndReturn:
JNU_ReleaseStringPlatformChars(env, host, hostname);
while (resNew != NULL) {
last = resNew;
resNew = resNew->ai_next;
free(last);
}
if (res != NULL) {
freeaddrinfo(res);
}
return ret;
}