我尝试以编程方式访问网站的信息,但在 Java 和 Python 上都无法解析主机名。如果我指定 IP 地址,则会将错误更改为 TLSV1_UNRECOGNIZED_NAME。不过,这个网站无需任何额外的工作就可以通过任何浏览器解决。
我在这里浏览了很多潜在的解决方案,但对于 Python,它说这个问题应该在 2.7 或 2.8 中得到解决,但我正在使用3.10 仍然出现该错误。在 Java 中,它声称这是一个已知错误,但提供的解决方案(例如通过编译选项删除 SNI 标头,或将空主机名数组传递给 HTTPSURLConnection 以取消 SNI 标头的创建)并不能解决该问题。我还尝试按照此处答案的建议将用户代理设置为 Mozilla,但这也没有改变任何内容。
我确信该网站有些不寻常,但它不是我拥有的,所以我无法检查太多有关其配置的信息。
具体来说,我想查看的网站是:
URL -> https://epic7db.com/heroes
IP -> 157.230.84.20
DNS Lookup -> https://www.nslookup.io/domains/epic7db.com/webservers/
在本地使用 nslookup 时,我返回:
nslookup epic7db.com
Server: UnKnown
Address: 10.0.0.1
Non-authoritative answer:
Name: epic7db.com
Address: 157.230.84.20
任何帮助将不胜感激,因为我本质上是在扔东西墙看看此时粘着什么。
编辑:添加代码示例 Python:
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36'} # This is chrome, you can set whatever browser you like
url = 'https://epic7db.com'
a = requests.get(url,headers)
print(a.content)
Kotlin 使用 Java 的 HttpsUrlConnection:
import http.SSLSocketFactoryWrapper
import java.net.URL
import javax.net.ssl.*
fun main() {
HttpsURLConnection.setDefaultHostnameVerifier { hostName, session -> true }
val url = URL("https://epic7db.com")
val sslParameters = SSLParameters()
val sniHostNames: MutableList<SNIHostName> = ArrayList<SNIHostName>(1)
// sniHostNames.add(SNIHostName(url.getHost()))
sslParameters.setServerNames(sniHostNames as List<SNIServerName>?)
val wrappedSSLSocketFactory: SSLSocketFactory =
SSLSocketFactoryWrapper(SSLContext.getDefault().socketFactory, sslParameters)
HttpsURLConnection.setDefaultSSLSocketFactory(wrappedSSLSocketFactory)
val conn = url.openConnection() as HttpsURLConnection
conn.hostnameVerifier = HostnameVerifier { s: String?, sslSession: SSLSession? -> true }
println(String(conn.inputStream.readAllBytes()))
}
Kotlin/Java 中建议的帮助器类:
package http;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class SSLSocketFactoryWrapper extends SSLSocketFactory {
private final SSLSocketFactory wrappedFactory;
private final SSLParameters sslParameters;
public SSLSocketFactoryWrapper(SSLSocketFactory factory, SSLParameters sslParameters) {
this.wrappedFactory = factory;
this.sslParameters = sslParameters;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(host, port);
setParameters(socket);
return socket;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException, UnknownHostException {
SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(host, port, localHost, localPort);
setParameters(socket);
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(host, port);
setParameters(socket);
return socket;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(address, port, localAddress, localPort);
setParameters(socket);
return socket;
}
@Override
public Socket createSocket() throws IOException {
SSLSocket socket = (SSLSocket) wrappedFactory.createSocket();
setParameters(socket);
return socket;
}
@Override
public String[] getDefaultCipherSuites() {
return wrappedFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return wrappedFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
SSLSocket socket = (SSLSocket) wrappedFactory.createSocket(s, host, port, autoClose);
setParameters(socket);
return socket;
}
private void setParameters(SSLSocket socket) {
socket.setSSLParameters(sslParameters);
}
}
你遇到的问题与网站的服务器配置有关,特别是与它如何处理服务器名称指示 (SNI) 相关。下面我会解释一下原因和可能的解决方案:
问题根源:
服务器名称指示 (SNI) 是 TLS 扩展,它允许客户端在 TLS 握手的早期阶段指定他们想要连接的主机名。这对于在单个 IP 地址上托管多个基于 HTTPS 的网站的服务器至关重要。
你遇到的网站似乎在 SNI 配置方面有问题。它可能使用不支持 SNI 的旧版 TLS 库或配置错误。这意味着:
-
当你的浏览器发送 SNI 信息时,服务器无法正确理解它。 然而,由于浏览器会回退到非 SNI 行为,它仍然能够建立连接(这可能使用默认证书或 IP 地址关联的第一个证书)。
-
当你的 Python 和 Java 代码尝试连接时,它们正确发送 SNI 信息。 但是,由于服务器配置错误,连接失败,并出现 "无法解析主机名" 或 "TLSV1_UNRECOGNIZED_NAME" 错误。
可能的解决方案:
-
禁用 SNI(不推荐):
- 不推荐这样做,因为它不安全,并且可能会导致其他问题。
- 禁用 SNI 意味着你的客户端将在不指定主机名的情况下建立 TLS 连接。这可能会导致服务器返回错误的证书,从而导致连接错误或安全漏洞。
-
使用支持 SNI 回退的 HTTP 库:
- 一些 HTTP 库(尤其是较旧的库)可能无法正确处理 SNI 回退。考虑使用更新的库,这些库明确支持 SNI 回退。
-
对于 Python,
requests
库应该可以正常处理 SNI 回退。 确保你使用的是最新版本。 - 对于 Java,请考虑使用更新的 HTTP 客户端库,例如 Apache HttpClient 或 OkHttp。 这些库通常具有更好的 SNI 支持。
-
联系网站所有者:
- 最好的解决方案是联系网站所有者,并告知他们这个问题。他们可以调查并修复其服务器配置以正确支持 SNI。
代码改进建议:
尽管代码本身并不是问题的根源,但你可以进行一些改进:
Python:
import requests
headers = {'User-Agent': 'Mozilla/5.0'}
# 无需指定完整的用户代理字符串,除非特定网站需要它
url = 'https://epic7db.com'
try:
response = requests.get(url, headers=headers)
response.raise_for_status() # 检查 HTTP 错误
print(response.content)
except requests.exceptions.RequestException as e:
print(f"请求出错: {e}")
Kotlin:
import java.net.URL
import java.net.UnknownHostException
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
fun main() {
val urlString = "https://epic7db.com"
try {
val url = URL(urlString)
val connection = url.openConnection() as HttpsURLConnection
// 设置更宽松的 HostnameVerifier (仅用于测试!)
connection.hostnameVerifier = { _, _ -> true }
println(connection.inputStream.bufferedReader().use { it.readText() })
} catch (e: UnknownHostException) {
println("无法解析主机名: ${e.message}")
} catch (e: Exception) {
println("请求出错: ${e.message}")
}
}
总结:
你遇到的问题很可能是由于网站服务器的 SNI 配置错误造成的。虽然禁用 SNI 或使用支持 SNI 回退的 HTTP 库可以作为临时解决方法,但联系网站所有者并要求他们修复服务器配置才是最佳的长期解决方案。
标签:python,java,ssl,https,python-requests From: 78830224