返回

Android访问本地API报400?快速修复Bad Request错误

Android

解决 Android 访问本地主机 Web 服务 400 错误

遇到一个有点挠头的问题:你开发的应用程序,在 Windows 上跑得好好的,能和本地运行的 Web 服务(比如一个 ASP.NET Core API)顺畅通信。可一旦把这个 App 部署到 Android 模拟器或者真机上,尝试访问同一个 Web 服务,Duang!一个 "400 Bad Request" 错误就跳出来了,就像下面这样:

Error:
StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
[DOTNET] {
[DOTNET] Connection: close
[DOTNET] Date: Fri, 21 Feb 2025 09:51:10 GMT
[DOTNET] Server: Microsoft-HTTPAPI/2.0
[DOTNET] X-Android-Received-Millis: 1740131468716
[DOTNET] X-Android-Response-Source: NETWORK 400
[DOTNET] X-Android-Selected-Protocol: http/1.1
[DOTNET] X-Android-Sent-Millis: 1740131465418
[DOTNET] Content-Length: 334
[DOTNET] Content-Type: text/html; charset=us-ascii
[DOTNET] }

看看出问题的代码片段,主要是用 HttpClient 发送 POST 请求:

public async Task<int> PostServiceint(string UR, object obj)
{
    int bln = 0;
    try
    {
        // 打印对象属性,方便调试
        foreach (var prop in obj.GetType().GetProperties())
        {
            Console.WriteLine(
public async Task<int> PostServiceint(string UR, object obj)
{
    int bln = 0;
    try
    {
        // 打印对象属性,方便调试
        foreach (var prop in obj.GetType().GetProperties())
        {
            Console.WriteLine($"{prop.Name}: {prop.GetValue(obj)}");
        }

        // 创建 HttpClientHandler,这里允许了任何服务器证书 (仅建议开发环境使用)
        HttpClientHandler handler = new HttpClientHandler
        {
            ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
        };

        using (var client = new HttpClient(handler))
        {
            client.Timeout = TimeSpan.FromMinutes(10);
            Console.WriteLine(obj.ToString()); // 打印对象本身的 ToString()

            // 发送 POST 请求,等待响应
            var response = client.PostAsJsonAsync(UR, obj).Result;
            var data = response.Content.ReadAsStringAsync().Result; // 读取响应内容(无论成功失败)
            Console.WriteLine(response); // 打印整个响应信息

            if (response.IsSuccessStatusCode)
            {
                string JsonString = response.Content.ReadAsStringAsync().Result;
                bln = Convert.ToInt32(JsonString);
            }
            // 注意:即使请求失败(非 2xx 状态码),也应该记录 data 或 response,能看到详细的错误 HTML
        }
    }
    catch (Exception ex)
    {
        // 捕捉并打印异常
        Console.WriteLine(ex);
    }
    return bln;
}
quot;{prop.Name}: {prop.GetValue(obj)}"
); } // 创建 HttpClientHandler,这里允许了任何服务器证书 (仅建议开发环境使用) HttpClientHandler handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true }; using (var client = new HttpClient(handler)) { client.Timeout = TimeSpan.FromMinutes(10); Console.WriteLine(obj.ToString()); // 打印对象本身的 ToString() // 发送 POST 请求,等待响应 var response = client.PostAsJsonAsync(UR, obj).Result; var data = response.Content.ReadAsStringAsync().Result; // 读取响应内容(无论成功失败) Console.WriteLine(response); // 打印整个响应信息 if (response.IsSuccessStatusCode) { string JsonString = response.Content.ReadAsStringAsync().Result; bln = Convert.ToInt32(JsonString); } // 注意:即使请求失败(非 2xx 状态码),也应该记录 data 或 response,能看到详细的错误 HTML } } catch (Exception ex) { // 捕捉并打印异常 Console.WriteLine(ex); } return bln; }

关键在于访问的 URL 不同:

  • Windows: http://localhost:2824/api/Controller/Method
  • Android: http://10.0.2.2:2824/api/Controller/Method

Windows 用 localhost,Android 用 10.0.2.2。看起来是网络配置的问题,但为什么是 "400 Bad Request" 而不是连接超时或拒绝连接呢?咱们来分析分析。

探究根源:为什么 Windows 能行,Android 却不行?

表面看,Windows 成功,Android 失败,差别在于 URL。这直接指向了网络访问路径的不同。

  1. localhost vs 10.0.2.2

    • localhost (或 127.0.0.1) 是一个特殊的环回地址,仅代表本机。从本机访问 localhost,流量根本不会离开机器的网络接口。
    • 10.0.2.2 是 Android 官方模拟器提供的一个特殊 IP 地址,它指向宿主机的环回接口 (127.0.0.1)。当你从模拟器访问 10.0.2.2 时,模拟器会将请求转发给运行它的那台 Windows 机器。虽然目标还是本机服务,但请求的来源,从网络协议栈的角度看,不再是严格意义上的 "本机",而是来自模拟器这个 "外部" 设备。
    • 如果是用物理 Android 设备 通过 Wi-Fi 访问,那么需要使用开发电脑在局域网中的 IP 地址(比如 192.168.1.100),这更是典型的外部访问。
  2. Web 服务器绑定:

    • 很多 Web 服务器(包括 ASP.NET Core 开发服务器 Kestrel 或 IIS Express)默认配置可能只监听 localhost。这意味着它们只接受来自本机内部的连接。当请求来自 10.0.2.2 (模拟器) 或局域网 IP (物理设备) 时,即使请求最终目标是本机,但由于服务器没有在对应的网络接口或所有接口 (0.0.0.0) 上监听,连接可能在到达应用程序逻辑之前就被拒绝了。这通常导致的是连接失败错误,而不是 400。 但在某些复杂的网络栈或配置下(例如通过某些代理或 Windows HTTP Server API 层),请求可能到达了服务监听的基础设施层,但因为主机头或绑定配置不匹配,被基础设施直接判定为无效请求,返回 400。
  3. Windows 防火墙:

    • Windows 自带的防火墙默认会阻止来自外部网络的入站连接,除非有明确的规则允许。从 Android 模拟器或物理设备发起的连接,对于 Windows 来说都属于外部连接。如果端口 2824 没有在防火墙的入站规则中放行,连接就会被阻断。同样,这通常导致连接超时或失败,但特定场景下,若防火墙与 HTTP Server API (http.sys) 交互,也可能导致 400。
  4. 400 Bad Request 的特殊性:

    • 这个错误码标准含义是 "请求无效"。服务器收到了请求,但无法理解或处理它。常见的具体原因包括:请求语法错误、无效的请求消息帧、欺骗性请求路由等。
    • 结合我们的场景,虽然网络配置是最常见的嫌疑,但 400 错误提示问题可能更微妙:
      • 主机头(Host Header)问题: 当请求通过 10.0.2.2 或实际 IP 到达时,HTTP 请求头中的 Host 字段会是 10.0.2.2:2824<局域网IP>:2824,而不是 localhost:2824。如果服务器配置(尤其是 IIS 或某些反向代理)严格要求 Host 必须是 localhost,就可能返回 400。
      • ASP.NET Core 配置: Kestrel 或 Http.sys 的某些底层配置可能导致这种特定 IP 访问的问题。
      • 请求内容差异? 理论上,代码应该发送相同的 obj 内容。但排查时不能完全排除两端因环境差异导致序列化或其他环节产生微小但关键的不同,使得服务器端的模型绑定或验证失败,返回 400。

对症下药:修复 Android 访问 400 错误的几种方案

了解了可能的原因后,我们可以按部就班地尝试解决。建议从最常见的网络配置问题入手。

方案一:修改 Web 服务绑定地址

这是最可能解决问题的方案。你需要让你的 Web 服务监听来自外部的连接,而不仅仅是 localhost

  • 原理: 让服务器不仅监听 127.0.0.1 (localhost),还要监听 0.0.0.0 (监听所有可用的 IPv4 网络接口) 或你开发机器在局域网中的具体 IP 地址。这样,来自模拟器 (10.0.2.2 最终路由到本机) 或物理设备 (通过局域网 IP) 的请求才能被服务器接受。

  • 操作步骤 (以 ASP.NET Core 为例):

    1. 修改 launchSettings.json (主要影响 Visual Studio 调试启动):
      找到你的项目下的 Properties/launchSettings.json 文件。修改你正在使用的启动配置(比如 httphttps),将 applicationUrl 中的 localhost 改为 0.0.0.0 或者你的具体局域网 IP。

      {
        // ... 其他配置 ...
        "profiles": {
          "http": {
            "commandName": "Project",
            "dotnetRunMessages": true,
            "launchBrowser": true,
            // 修改这里,可以用分号分隔多个地址
            // "applicationUrl": "http://localhost:5000", // 原来的
            "applicationUrl": "http://0.0.0.0:2824", // 监听所有 IPv4 地址的 2824 端口
            // 或者指定多个: "applicationUrl": "http://localhost:2824;http://192.168.1.100:2824"
            "environmentVariables": {
              "ASPNETCORE_ENVIRONMENT": "Development"
            }
          },
          // ... 可能还有 https 配置,原理相同 ...
           "https": {
            "commandName": "Project",
            "dotnetRunMessages": true,
            "launchBrowser": true,
            // 修改这里
            // "applicationUrl": "https://localhost:7001;http://localhost:5001", // 原来的
             "applicationUrl": "https://0.0.0.0:7284;http://0.0.0.0:2824", // 监听所有IPv4
            "environmentVariables": {
              "ASPNETCORE_ENVIRONMENT": "Development"
            }
          }
        }
      }
      

      注意:端口号(示例中的 28247284)需要与你实际使用的端口匹配。

    2. 修改 Program.cs (适用于 dotnet run 或发布后运行):
      你也可以直接在代码中配置 Kestrel 监听的地址。

      • .NET 6+ (Minimal APIs):

        var builder = WebApplication.CreateBuilder(args);
        // ... 添加服务 ...
        
        // 配置 Kestrel 监听地址
        builder.WebHost.UseUrls("http://0.0.0.0:2824", "https://0.0.0.0:7284"); // 根据需要选择 http/https 和端口
        
        var app = builder.Build();
        // ... 配置中间件 ...
        app.Run();
        
      • .NET Core 3.1 / .NET 5 (带 Startup.cs):

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    // 配置 Kestrel 监听地址
                    webBuilder.UseUrls("http://0.0.0.0:2824", "https://0.0.0.0:7284"); // 根据需要选择 http/https 和端口
                    webBuilder.UseStartup<Startup>();
                });
        
    3. 使用命令行参数:
      运行应用时直接指定 URL:

      dotnet run --urls="http://0.0.0.0:2824;https://0.0.0.0:7284"
      
  • 如果你用的是 IIS Express:
    配置稍微麻烦点。需要修改 solution 目录下的 .vs/<YourSolutionName>/config/applicationhost.config 文件。找到你的站点 <site> 配置,修改 <bindings> 部分,添加一个新的绑定,将 bindingInformation 中的 localhost 替换为 * 或者你的 IP 地址。

    <bindings>
        <binding protocol="http" bindingInformation="*:2824:localhost" />
        <!-- 添加下面这行 -->
        <binding protocol="http" bindingInformation="*:2824:*" />
        <!-- 或者指定 IP -->
        <!-- <binding protocol="http" bindingInformation="*:2824:192.168.1.100" /> -->
    </bindings>
    

    同时,还需要用管理员权限运行 netsh http add urlacl url=http://*:2824/ user=everyone (或特定用户)来授予 URL 监听权限。修改 IIS Express 配置通常还需要以管理员身份启动 Visual Studio。

  • 安全建议:
    将服务绑定到 0.0.0.0 或具体 IP 意味着局域网内的任何人都可以尝试访问你的开发服务。虽然通常开发环境数据不敏感,但还是要注意:

    • 确保防火墙配置得当(见方案二),仅允许来自你需要测试的设备(如模拟器或你的手机 IP)的访问,如果网络环境复杂的话。
    • 开发服务不应暴露在公网上。
  • 进阶技巧:
    使用环境变量(如 ASPNETCORE_URLS)来配置监听地址,这样可以在不同环境(开发、测试)中灵活切换,而无需修改代码或配置文件。

    # 设置环境变量后直接运行 dotnet run
    $env:ASPNETCORE_URLS="http://0.0.0.0:2824"
    dotnet run
    

方案二:检查和配置 Windows 防火墙

即使服务器配置好了监听外部地址,Windows 防火墙也可能拦住来自 Android 的请求。

  • 原理: 防火墙是操作系统的安全屏障。对于入站连接(从外部到本机),需要有规则明确允许特定端口(这里是 2824)的 TCP 流量通过。

  • 操作步骤 (图形界面):

    1. 打开 "具有高级安全性的 Windows Defender 防火墙"。可以通过搜索 wf.msc 或在控制面板中找到。
    2. 在左侧面板中,点击 "入站规则"。
    3. 在右侧面板中,点击 "新建规则..."。
    4. 选择 "端口",点击 "下一步"。
    5. 选择 "TCP",然后在 "特定本地端口" 框中输入 2824,点击 "下一步"。
    6. 选择 "允许连接",点击 "下一步"。
    7. 选择应用此规则的网络配置文件。为了安全,如果你的电脑和测试设备都在私人网络(家里或办公室),取消勾选 "公用" ,保留 "专用" 和 "域"(如果适用)。点击 "下一步"。
    8. 给规则起个名字,例如 "Allow Android Local Dev Service (TCP 2824)",然后点击 "完成"。
  • 操作步骤 (命令行 netsh,需管理员权限):
    打开管理员命令提示符或 PowerShell,执行以下命令:

    netsh advfirewall firewall add rule name="Allow Android Local Dev Service (TCP 2824)" dir=in action=allow protocol=TCP localport=2824 profile=private,domain
    
    • name: 规则名称,方便识别。
    • dir=in: 入站规则。
    • action=allow: 允许连接。
    • protocol=TCP: 协议类型。
    • localport=2824: 指定端口。
    • profile=private,domain: 应用于专用和域网络。如果你的网络是公用类型,可能需要加上 public,但要特别注意安全风险。
  • 安全建议:

    • 仅开放确实需要的端口。
    • 尽可能限制规则应用的配置文件(profile),避免在不安全的网络(如公共 Wi-Fi)中暴露端口。
    • 调试完成后,可以禁用或删除此防火墙规则。

方案三:确认 Android 端网络配置和权限

虽然不太可能直接导致 400 错误,但基础的网络设置也需要检查一遍。

  • 原理: 确保 Android 应用能正确访问网络,并且使用的目标 IP 地址确实能指向你的开发机。

  • 操作步骤:

    1. 检查 AndroidManifest.xml 确保应用有网络访问权限。
      <manifest ...>
          <uses-permission android:name="android.permission.INTERNET" />
          <application ...>
              ...
          </application>
      </manifest>
      
    2. 确认 IP 地址:
      • 模拟器: 10.0.2.2 是标准的模拟器访问宿主机 IP。通常是对的。
      • 物理设备: 确保设备和你的开发电脑连接到同一个 局域网 (Wi-Fi)。在开发电脑上打开命令提示符,输入 ipconfig (Windows) 或 ifconfig/ip addr (Linux/Mac),找到你连接到局域网的网络适配器(通常是 Wi-Fi 或以太网),记下其 IPv4 地址。在 Android 代码中,将 10.0.2.2 替换为这个局域网 IP 地址。
    3. (特定情况) Android 网络安全配置: 如果你的 Web 服务只提供 HTTP 而非 HTTPS,并且你的 App 的 targetSdkVersion >= 28 (Android 9),Android 默认阻止明文 HTTP 通信。这通常会导致连接错误或特定异常,但某些情况下网络库的底层行为可能难以预料。
      • 临时允许 HTTP (仅限调试):AndroidManifest.xml<application> 标签内添加 android:usesCleartextTraffic="true"
        <application
            ...
            android:usesCleartextTraffic="true">
            ...
        </application>
        
      • 推荐做法: 为本地开发环境配置 HTTPS。使用 .NET 的开发证书 (dotnet dev-certs https --trust) 并确保服务器监听 HTTPS 地址。在 Android 端,你的 HttpClientHandler 已经设置了 ServerCertificateCustomValidationCallback 来接受这个自签名开发证书,所以 HTTPS 应该是可行的(尽管这种接受所有证书的方式在生产环境绝对禁止 )。
  • 进阶技巧:

    • 在 Android 设备或模拟器上使用 Ping 工具(需要安装第三方 App 或使用 adb shell)尝试 Ping 开发机的局域网 IP,确认网络基本连通性(防火墙可能会阻止 ICMP Ping 请求,但这仍是一个有用的测试)。
    • 使用 Android Studio 的网络分析器或第三方抓包工具(如 Charles Proxy 配置代理)来查看实际发出的 HTTP 请求详情。

方案四:服务端请求验证或模型绑定问题

回头看那个 "400 Bad Request",它暗示服务器端收到了请求,但觉得它格式不对或内容无效。虽然网络问题更常见,但也不要忽视服务器端的逻辑。

  • 原理: API 端点可能有严格的验证规则(比如必须包含某个 Header、Body 格式必须精确匹配某个 Model)。或许从 Android 发出的请求在序列化、编码或某个环节与 Windows 发出的有所不同,导致服务器验证失败。

  • 操作步骤:

    1. 检查服务端日志: 这是最关键的一步!查看 ASP.NET Core 服务在收到来自 Android 的请求时,控制台或日志文件中输出的详细信息。标准的 ASP.NET Core 应用在模型绑定失败或验证失败时,通常会记录下具体原因。查找紧跟在收到 POST /api/Controller/Method 请求后的错误或警告信息。
    2. 细化服务端日志记录: 如果默认日志不够详细,可以在 API 控制器方法入口处添加更细致的日志,记录收到的请求头、请求体原文等。
      [HttpPost]
      [Route("Method")]
      public async Task<IActionResult> YourApiMethod([FromBody] YourModel model) // 假设这是你的方法签名
      {
          // 记录原始请求体 (需要启用 Request Buffering)
          // 在 Program.cs 或 Startup.cs 中调用 app.Use(async (context, next) => { context.Request.EnableBuffering(); await next(); });
          Request.EnableBuffering();
          string rawRequestBody = string.Empty;
          using (var reader = new StreamReader(Request.Body, Encoding.UTF8, true, 1024, true)) // 'leaveOpen: true' is important
          {
              rawRequestBody = await reader.ReadToEndAsync();
              Request.Body.Position = 0; // 重置流位置,以便后续模型绑定可以读取
          }
          _logger.LogInformation(
      [HttpPost]
      [Route("Method")]
      public async Task<IActionResult> YourApiMethod([FromBody] YourModel model) // 假设这是你的方法签名
      {
          // 记录原始请求体 (需要启用 Request Buffering)
          // 在 Program.cs 或 Startup.cs 中调用 app.Use(async (context, next) => { context.Request.EnableBuffering(); await next(); });
          Request.EnableBuffering();
          string rawRequestBody = string.Empty;
          using (var reader = new StreamReader(Request.Body, Encoding.UTF8, true, 1024, true)) // 'leaveOpen: true' is important
          {
              rawRequestBody = await reader.ReadToEndAsync();
              Request.Body.Position = 0; // 重置流位置,以便后续模型绑定可以读取
          }
          _logger.LogInformation($"Received request body: {rawRequestBody}");
      
          // 检查模型绑定状态
          if (!ModelState.IsValid)
          {
              _logger.LogWarning("Model validation failed: {@ModelState}", ModelState);
              return BadRequest(ModelState);
          }
      
          // ... 你的业务逻辑 ...
      
          // 假设 PostServiceint 逻辑在这里,并返回 int 值
          int result = await _yourService.ProcessAsync(model); // 示例
          return Ok(result);
      }
      
      quot;Received request body: {rawRequestBody}"
      ); // 检查模型绑定状态 if (!ModelState.IsValid) { _logger.LogWarning("Model validation failed: {@ModelState}", ModelState); return BadRequest(ModelState); } // ... 你的业务逻辑 ... // 假设 PostServiceint 逻辑在这里,并返回 int 值 int result = await _yourService.ProcessAsync(model); // 示例 return Ok(result); }
    3. 对比请求: 使用工具(如 Postman)分别模拟从 Windows (使用 localhost) 和从 Android (使用 10.0.2.2 或局域网 IP) 发送的请求,仔细对比两者的 Headers 和 Body 是否完全一致。注意 Content-Type header 是否为 application/jsonPostAsJsonAsync 通常会正确设置这个,但检查一下总没错。
    4. 审视 obj 对象: 确认在 Android 环境下传递给 PostServiceint 方法的 obj 对象的所有属性都已正确填充,没有 null 值(除非服务器允许)。之前添加的 Console.WriteLine 打印属性是有帮助的。

总结思路

面对 Android 访问本地服务出现 400 错误:

  1. 最先检查并修复 Web 服务器绑定地址, 确保它监听 0.0.0.0 或开发机局域网 IP,而不仅仅是 localhost
  2. 接着检查并配置 Windows 防火墙, 为服务使用的端口(如 2824)添加入站规则,允许来自专用网络的 TCP 连接。
  3. 然后确认 Android 端的配置, 包括 INTERNET 权限、正确的 IP 地址(10.0.2.2 用于模拟器,局域网 IP 用于物理设备),以及必要时处理明文流量限制(usesCleartextTraffic 或改用 HTTPS)。
  4. 如果以上网络层面的调整都无效,深入调查服务器端日志, 查找关于请求处理失败的具体原因,可能是模型绑定、验证或预期之外的请求格式问题。

按照这个顺序排查,大概率能找到症结所在,让你的 Android 应用也能顺利地和本地开发服务“聊上天”。