Custom DNS resolver for the default HTTP client in Go

本文介绍了如何在Go语言的默认HTTP客户端中替换DNS解析器,通过创建自定义的DNS解析器实例并覆盖`net/http`包中的`DialContext`方法来实现。作者分享了Violetnorth公司的实践案例,展示了如何仅需几行代码即可定制DNS查询过程。

1. Custom DNS resolver for the default HTTP client in Go

The default HTTP client in Go uses the default DNS resolver available on the machine. There are some cases where you might want to use a different DNS resolver.

I want to quickly show how you can change the DNS resolver for the default HTTP client. We are using this same method for our custom DNS resolver in my company: Violetnorth

// Changing DNS resolver
package main

import (
  "context"
  "io/ioutil"
  "log"
  "net"
  "net/http"
  "time"
)

func main() {
  var (
    dnsResolverIP        = "8.8.8.8:53" // Google DNS resolver.
    dnsResolverProto     = "udp"        // Protocol to use for the DNS resolver
    dnsResolverTimeoutMs = 5000         // Timeout (ms) for the DNS resolver (optional)
  )

  dialer := &net.Dialer{
    Resolver: &net.Resolver{
      PreferGo: true,
      Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
        d := net.Dialer{
          Timeout: time.Duration(dnsResolverTimeoutMs) * time.Millisecond,
        }
        return d.DialContext(ctx, dnsResolverProto, dnsResolverIP)
      },
    },
  }

  dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
    return dialer.DialContext(ctx, network, addr)
  }

  http.DefaultTransport.(*http.Transport).DialContext = dialContext
  httpClient := &http.Client{}

  // Testing the new HTTP client with the custom DNS resolver.
  resp, err := httpClient.Get("https://www.violetnorth.com")
  if err != nil {
    log.Fatalln(err)
  }
  defer resp.Body.Close()

  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Fatalln(err)
  }

  log.Println(string(body))
}

It only takes a few lines but you have to overwrite the Dial function in the Resolver which is inside the Dialer which is inside the Transport function.

It might seem weird but it actually makes a lot of sense, follow the functions from top to bottom, DefaultTransport -> DialContext -> Dialer -> Resolver -> Dial

We are changing the Dial function which dials the DNS server to resolve the request, so it should be inside the Resolver. But this whole thing is in the DialContext of the actual HTTP request.

You have to dial the DNS resolver first, resolve the IP, then dial that resolved IP to make the HTTP request.

Anyways that’s pretty much it. Changing the DNS resolver in the default HTTP client.

/* * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. */ package com.huawei.peripheral.servicecall.Utils; import org.apache.commons.lang3.StringUtils; import org.apache.http.Consts; import org.apache.http.Header; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.config.ConnectionConfig; import org.apache.http.config.MessageConstraints; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; import org.apache.http.conn.DnsResolver; import org.apache.http.conn.HttpConnectionFactory; import org.apache.http.conn.ManagedHttpClientConnection; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.DefaultHttpResponseParser; import org.apache.http.impl.conn.DefaultHttpResponseParserFactory; import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.SystemDefaultDnsResolver; import org.apache.http.impl.io.DefaultHttpRequestWriterFactory; import org.apache.http.io.HttpMessageParser; import org.apache.http.io.HttpMessageParserFactory; import org.apache.http.io.HttpMessageWriterFactory; import org.apache.http.io.SessionInputBuffer; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicLineParser; import org.apache.http.message.LineParser; import org.apache.http.util.CharArrayBuffer; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.net.ssl.SSLContext; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.CodingErrorAction; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; /** * 功能描述 HttpUtil * * @since 2019-09-03 */ @Configuration public class HttpUtil { /** * APPLICATION_JSON */ public static final String APPLICATION_JSON = "application/json"; /** * 字符串格式 */ public static final String UTF8 = "utf-8"; @Value("${httpclient.max.pool}") public int maxPool; @Value("${httpclient.max.perroute}") public int maxPerRoute; @Value("${httpclient.connect.timeout}") public int connectTimeOut; @Value("${httpclient.socket.timeout}") public int socketTimeOut; @Value("${httpclient.connection.request.timeout}") public int connectionRequestTimeOut; /** * 单例 */ //public static final HttpUtil INSTANCE = new HttpUtil(); private static Logger logger = LoggerFactory.getLogger(HttpUtil.class); // HTTP Get请求(POST雷同) //RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); //private CloseableHttpClient httpClient; @Bean public CloseableHttpClient getHttpUtilClient(){ PoolingHttpClientConnectionManager connManager = getClientConnectionManager(); SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build(); connManager.setDefaultSocketConfig(socketConfig); connManager.setValidateAfterInactivity(1000); MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(200).setMaxLineLength(2000).build(); ConnectionConfig connectionConfig = ConnectionConfig.custom() .setMalformedInputAction(CodingErrorAction.IGNORE) .setUnmappableInputAction(CodingErrorAction.IGNORE) .setCharset(Consts.UTF_8) .setMessageConstraints(messageConstraints) .build(); connManager.setDefaultConnectionConfig(connectionConfig); connManager.setMaxTotal(maxPool); connManager.setDefaultMaxPerRoute(maxPerRoute); CookieStore cookieStore = new BasicCookieStore(); CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); RequestConfig defaultRequestConfig = RequestConfig.custom() .setCookieSpec(CookieSpecs.DEFAULT) .setExpectContinueEnabled(true) .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST)) .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)) .setConnectTimeout(connectTimeOut) .setSocketTimeout(socketTimeOut) .setConnectionRequestTimeout(connectionRequestTimeOut) .build(); HttpClientBuilder builder = HttpClients.custom().setConnectionManager(connManager); builder.setDefaultCookieStore(cookieStore); builder.setDefaultCredentialsProvider(credentialsProvider); builder.setDefaultRequestConfig(defaultRequestConfig); // 创建自定义的httpclient对象 //httpClient = builder.build(); return builder.build(); } /** * 项目报文格式转换工厂 * * @return responseParserFactory */ private HttpMessageParserFactory<HttpResponse> getResponseParserFactory() { HttpMessageParserFactory<HttpResponse> responseParserFactory = new DefaultHttpResponseParserFactory() { @Override public HttpMessageParser<HttpResponse> create(SessionInputBuffer buffer, MessageConstraints constraints) { LineParser lineParser = new BasicLineParser() { @Override public Header parseHeader(CharArrayBuffer buffer) { try { return super.parseHeader(buffer); } catch (Exception ex) { return new BasicHeader(buffer.toString(), ""); } } }; return new DefaultHttpResponseParser(buffer, lineParser, DefaultHttpResponseFactory.INSTANCE, constraints) { @Override protected boolean reject(CharArrayBuffer line, int count) { // try to ignore all garbage preceding a status line // infinitely return false; } }; } }; return responseParserFactory; } private PoolingHttpClientConnectionManager getClientConnectionManager() { HttpMessageParserFactory<HttpResponse> responseParserFactory = getResponseParserFactory(); HttpMessageWriterFactory<HttpRequest> requestWriterFactory = new DefaultHttpRequestWriterFactory(); HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory(requestWriterFactory, responseParserFactory); // 采用绕过验证的方式处理https请求 SSLContext sslcontext = null; try { sslcontext = SSLContextUtil.createIgnoreVerifySSL(); } catch (NoSuchAlgorithmException e) { System.out.println("init exception......"); } catch (KeyManagementException e) { System.out.println("init exception......"); } Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.INSTANCE) .register("https", new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)) .build(); DnsResolver dnsResolver = new SystemDefaultDnsResolver() { @Override public InetAddress[] resolve(String host) throws UnknownHostException { if ("myhost".equalsIgnoreCase(host)) { return new InetAddress[]{InetAddress.getByAddress(new byte[]{127, 0, 0, 1})}; } else { return super.resolve(host); } } }; PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, connFactory, dnsResolver); return connManager; } /** * sendGetReq * * @param url url * @param header header * @param param param * @return HttpResp */ public HttpResp sendGetReq(String url, Map<String, String> header, Map<String, Object> param) { HttpGet httpget = new HttpGet(buildGetUrl(url, param)); // 设置请求和传输超时时间 //httpget.setConfig(requestConfig); return sendReq(httpget, header); } /** * sendGetReq * * @param url url * @param header header * @return HttpResp */ public HttpResp sendGetReq(String url, Map<String, String> header) { HttpGet httpget = new HttpGet(url); // 设置请求和传输超时时间 //httpget.setConfig(requestConfig); return sendReq(httpget, header); } /** * sendPostReq * * @param url url * @param header header * @param jsonParams jsonParams * @return HttpResp */ public HttpResp sendPostReq(String url, Map<String, String> header, String jsonParams) { HttpPost httpPost = new HttpPost(url); // 设置请求和传输超时时间 //httpPost.setConfig(requestConfig); return sendJsonReq(httpPost, header, jsonParams); } /** * 设置post请求超时时间 * * @param url url * @param header header * @param jsonParams jsonParams * @param second second * @return HttpResp */ public HttpResp sendPostReqLong(String url, Map<String, String> header, String jsonParams, int second) { HttpPost httpPost = new HttpPost(url); if (second > 0) { RequestConfig requestTime = RequestConfig.custom().setSocketTimeout(second * 1000).setConnectTimeout(second * 1000).build(); // 设置请求和传输超时时间 httpPost.setConfig(requestTime); } return sendJsonReq(httpPost, header, jsonParams); } /** * sendPutReq * * @param url url * @param header header * @param jsonParams 参数 * @return HttpResp */ public HttpResp sendPutReq(String url, Map<String, String> header, String jsonParams) { HttpPut httpPut = new HttpPut(url); return sendJsonReq(httpPut, header, jsonParams); } /** * sendPatchReq * * @param url url * @param header header * @param jsonParams 参数 * @return HttpResp */ public HttpResp sendPatchReq(String url, Map<String, String> header, String jsonParams) { HttpPatch httpPatch = new HttpPatch(url); return sendJsonReq(httpPatch, header, jsonParams); } /** * sendDeleteReq * * @param url url * @param header url * @return HttpResp */ public HttpResp sendDeleteReq(String url, Map<String, String> header) { HttpDelete httpDelete = new HttpDelete(url); // 设置请求和传输超时时间 //httpDelete.setConfig(requestConfig); return sendReq(httpDelete, header); } /** * sendReq * * @param httpRequest httpRequest * @param headers headers * @return HttpResp */ private HttpResp sendReq(HttpRequestBase httpRequest, Map<String, String> headers) { HttpResp resp = new HttpResp(); // 设置请求头 setHeader(httpRequest, headers); try { CloseableHttpResponse response = getHttpUtilClient().execute(httpRequest); String respBody = null; if (response.getEntity() != null) { respBody = EntityUtils.toString(response.getEntity()); } StatusLine statusLine = response.getStatusLine(); resp.setHttpStatus(statusLine.getStatusCode()); resp.setRespContent(respBody); Header[] headerArr = response.getAllHeaders(); if (headerArr != null) { Map<String, String> headerMap = new HashMap<>(); for (Header header : headerArr) { headerMap.put(header.getName(), header.getValue()); } resp.setRespHeader(headerMap); } } catch (IOException e) { logger.error("send Req:",e); System.out.println("Failed to sendReq,url=" + httpRequest.getURI()); } finally { httpRequest.releaseConnection(); } return resp; } /** * sendJsonReq * * @param httpRequest httpRequest * @param header header * @param jsonParams jsonParams * @return HttpResp */ private HttpResp sendJsonReq(HttpEntityEnclosingRequestBase httpRequest, Map<String, String> header, String jsonParams) { if (!StringUtils.isEmpty(jsonParams)) { httpRequest.setEntity(new StringEntity(jsonParams, ContentType.create(APPLICATION_JSON, UTF8))); } return sendReq(httpRequest, header); } /** * 设置HTTP请求头 * * @param httpRequest httpRequest * @param header header */ private void setHeader(HttpRequestBase httpRequest, Map<String, String> header) { if (header == null || header.isEmpty()) { return; } for (Entry<String, String> entry : header.entrySet()) { httpRequest.addHeader(entry.getKey(), entry.getValue()); } } /** * 拼接GET请求的URL和参数 * * @param url url * @param body body * @return String */ private String buildGetUrl(String url, Map<String, Object> body) { StringBuffer realPath = new StringBuffer(); realPath.append(url); if (!url.contains("?")) { realPath.append("?"); } else { realPath.append("&"); } if (body != null && !body.isEmpty()) { for (Entry<String, Object> entry : body.entrySet()) { realPath.append(entry.getKey()).append("=").append(String.valueOf(entry.getValue())).append("&"); } } realPath.deleteCharAt(realPath.length() - 1); return realPath.toString(); } } 这个是4的,我要改为5的版本
10-10
package com.hw.dataasset.util; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.Map; @Configuration public class HttpUtil { private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class); @Value("${httpclient.max.pool}") private int maxPool; @Value("${httpclient.max.perroute}") private int maxPerRoute; @Value("${httpclient.connect.timeout}") private int connectTimeOut; @Value("${httpclient.socket.timeout}") private int socketTimeOut; @Value("${httpclient.connection.request.timeout}") private int connectionRequestTimeOut; @Bean public CloseableHttpClient getHttpUtilClient() { try { // 创建 SSLContext 忽略证书验证(测试用) SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial((chain, authType) -> true) .build(); HostnameVerifier hostnameVerifier = (hostname, session) -> true; // 配置 SSL 连接工厂 SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( sslContext, hostnameVerifier ); // 配置连接池 PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager( RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslSocketFactory) .build(), null, // DnsResolver null, // ConnectionFactory TimeValue.ofSeconds(10), // Validate after inactivity null // ConnectionPoolListener ); connManager.setMaxTotal(maxPool); connManager.setDefaultMaxPerRoute(maxPerRoute); // Socket配置 SocketConfig socketConfig = SocketConfig.custom() .setTcpNoDelay(true) .setSoTimeout(Timeout.ofMilliseconds(socketTimeOut)) .build(); connManager.setDefaultSocketConfig(socketConfig); // 请求配置 RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(Timeout.ofMilliseconds(connectTimeOut)) .setResponseTimeout(Timeout.ofMilliseconds(socketTimeOut)) .setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeOut)) .setCookieSpec("default") .setExpectContinueEnabled(true) .build(); // 构建客户端 return HttpClients.custom() .setConnectionManager(connManager) .setDefaultRequestConfig(requestConfig) .build(); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { logger.error("初始化HttpClient失败", e); throw new RuntimeException("初始化HttpClient失败", e); } } /** * 发送 GET 请求 * * @param url 请求地址 * @return 响应内容 * @throws IOException */ public String get(String url) throws Exception { HttpGet request = new HttpGet(url); try (CloseableHttpClient client = httpClient) { ClassicHttpResponse response = client.executeOpen(null, request, null); HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; } } /** * 发送 POST 请求(JSON 数据) * * @param url 请求地址 * @param json 请求体(JSON 格式) * @return 响应内容 * @throws IOException */ public String post(String url, String json) throws Exception { HttpPost request = new HttpPost(url); request.setHeader("Content-Type", "application/json"); request.setEntity(new StringEntity(json)); try (CloseableHttpClient client = httpClient) { ClassicHttpResponse response = client.executeOpen(null, request, null); HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; } } } 没有httpClient
10-10
你遇到的问题是因为 **ESP-IDF 不同版本** 的 `menuconfig` 路径或选项名称有所变化。以下是针对不同版本的解决方案,以及如何手动检查/修改 LWIP 的 DNS 配置。 --- ### **1. 确认 ESP-IDF 版本** 首先运行以下命令查看版本: ```bash git describe --tags # 如果使用 Git 仓库 # 或 idf.py --version # ESP-IDF v4.x+ ``` - **v4.x** 和 **v5.x** 的 `menuconfig` 路径可能不同。 - **v3.x** 的选项更简单,可能直接在 `LWIP` 主菜单下。 --- ### **2. 不同版本的 DNS 配置路径** #### **ESP-IDF v5.x(推荐)** 路径应为: ``` Component config → LWIP → IPv4 → DNS resolver options → [*] Enable DNS resolver [*] Use statically allocated DNS table (8.8.8.8) Default DNS server ``` - 如果找不到,尝试搜索 `DNS`(按 `/` 键输入关键词)。 #### **ESP-IDF v4.x** 路径类似: ``` Component config → LWIP → DNS resolver → [*] DNS client (enable this) [*] Use default DNS server (optional) ``` - 如果选项不存在,可能是编译时未包含 LWIP 的 DNS 模块。 #### **ESP-IDF v3.x** 路径更简单: ``` LWIP Configuration → DNS resolver → [*] Enable DNS resolver ``` --- ### **3. 如果 `menuconfig` 中没有 DNS 选项** #### **方法 1:直接修改 `sdkconfig` 文件** 1. 运行 `idf.py menuconfig` 至少一次,生成 `sdkconfig` 文件。 2. 手动编辑 `sdkconfig`,添加以下内容: ```ini CONFIG_LWIP_DNS_SUPPORT_RFC1035=y CONFIG_LWIP_DNS_USE_STATIC_BUF=y CONFIG_LWIP_DNS_SERVER_ADDRESS="8.8.8.8" ``` 3. 保存后重新编译: ```bash idf.py fullclean idf.py build ``` #### **方法 2:通过代码强制启用 DNS** 在代码中调用 LWIP API 初始化 DNS(绕过 `menuconfig`): ```c #include "lwip/dns.h" void init_dns() { // 设置主 DNS 服务器为 8.8.8.8 ip_addr_t dns_server; IP_ADDR4(&dns_server, 8, 8, 8, 8); dns_setserver(0, &dns_server); // 启用 DNS 客户端(如果 LWIP 未自动启用) extern err_t dns_init(void); dns_init(); // 确保 LWIP 已初始化 } void app_main() { init_dns(); ESP_LOGI(TAG, "DNS initialized"); // ... 其他代码 } ``` --- ### **4. 验证 DNS 是否生效** 在代码中添加测试逻辑: ```c #include "lwip/dns.h" void test_dns() { const char *hostname = "httpbin.org"; ip_addr_t addr; ESP_LOGI(TAG, "Resolving DNS for %s...", hostname); err_t err = dns_gethostbyname(hostname, &addr, NULL, NULL); if (err == ERR_OK) { ESP_LOGI(TAG, "Resolved IP: %s", ipaddr_ntoa(&addr)); } else { ESP_LOGE(TAG, "DNS resolve failed: %d", err); } } ``` --- ### **5. 其他可能的问题** #### **QEMU 网络问题** 即使 DNS 配置正确,QEMU 的默认网络模式(SLIRP)可能仍无法解析域名。尝试: 1. **使用 TAP 网络**(需 root 权限): ```bash sudo qemu-system-xtensa \ -nographic \ -machine esp32 \ -drive file=build/your_app.bin,if=mtd,format=raw \ -net nic,model=vlan0 \ -net tap,ifname=tap0 ``` 2. **在宿主机配置 DNS 转发**: ```bash echo "nameserver 8.8.8.8" > /etc/resolv.conf # Linux 临时方案 ``` #### **LWIP 编译选项** 检查 `components/lwip/lwip/src/include/lwip/opt.h` 是否包含: ```c #define LWIP_DNS 1 // 确保为 1 #define LWIP_DNS_SUPPORT_RFC1035 1 // 支持标准 DNS 查询 ``` --- ### **6. 完整修复步骤总结** 1. **确认版本**:运行 `idf.py --version`。 2. **检查 `menuconfig`**: - v5.x:`Component config → LWIP → IPv4 → DNS resolver options`。 - v4.x:`Component config → LWIP → DNS resolver`。 3. **手动配置**: - 编辑 `sdkconfig` 或通过代码调用 `dns_setserver()`。 4. **测试 DNS**: - 调用 `dns_gethostbyname()` 验证解析是否成功。 5. **检查 QEMU 网络**: - 改用 TAP 网络或显式指定 DNS 服务器。 --- ### **7. 示例代码(最终版)** ```c #include <stdio.h> #include "esp_log.h" #include "esp_http_client.h" #include "lwip/dns.h" static const char *TAG = "HTTP_CLIENT"; void init_dns() { ip_addr_t dns_server; IP_ADDR4(&dns_server, 8, 8, 8, 8); dns_setserver(0, &dns_server); ESP_LOGI(TAG, "DNS server set to 8.8.8.8"); } void test_dns() { ip_addr_t addr; err_t err = dns_gethostbyname("httpbin.org", &addr, NULL, NULL); if (err == ERR_OK) { ESP_LOGI(TAG, "Resolved IP: %s", ipaddr_ntoa(&addr)); } else { ESP_LOGE(TAG, "DNS resolve failed: %d", err); } } esp_err_t _http_event_handler(esp_http_client_event_t *evt) { // ... 原有事件处理逻辑 } void http_client_example() { esp_http_client_config_t config = { .url = "http://httpbin.org/get", .event_handler = _http_event_handler, }; esp_http_client_handle_t client = esp_http_client_init(&config); err_t err = esp_http_client_perform(client); if (err == ESP_OK) { ESP_LOGI(TAG, "Status code: %d", esp_http_client_get_status_code(client)); } else { ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); } esp_http_client_cleanup(client); } void app_main() { init_dns(); test_dns(); // 先测试 DNS 是否工作 http_client_example(); } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云满笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值