来自 Android 上的 NanoHTTPD 的意外 HTTP 400 状态代码

Unexpected HTTP 400 status code from NanoHTTPD on Android

朋友们!

我的 Android 应用程序中 nanohttpd 偶尔收到意外的 HTTP 400 响应。该错误遵循特定模式。我已经研究了一段时间了,但我已经到了需要不同角度或其他帮助来指引我正确方向的地步。

能否请您看看并分享您的想法甚至直接的观点和建议?

一些背景知识

我在我的Android项目中运行nanohttpd作为临时隔离层(由于服务器端还不够成熟)。我已将 nanohttpd 服务器隔离在 Android Service 中,一旦它被创建,我就从我的自定义 Application 对象开始。这样 nanohttpd 不受任何特定 Activity 的生命周期限制,而是可以独立于整个应用程序逻辑和组件生命周期。

问题

现在,(几乎)一切都运行良好且花花公子:我可以开始 nanohttpd 并执行一些初始登录请求,我预期的模拟响应甚至已交付。但是,当我执行第一个 "GET" 请求时,nanohttpd 向我抛出 400 Bad 请求状态, 但只是第一次 。如果我退出 Activity 负责特定的 "GET" 请求,并再次启动它(从主屏幕),它会完美地交付具有 200 状态的预期负载。

到目前为止我做了什么

我仔细查看了 nanohttpd 源代码,试图找出设置此 400 状态的位置和原因。使用此状态代码的地方并不多。粗略地说只有here, here and here。由于我不处理多部分内容,所以我只剩下第一个和第三个 "here"。但是-当然-我一生都找不到 400 状态的根本原因,也找不到哪个确切的块导致了我的状态。当我调试代码时,一切正常。

一些代码

我的 nanohttpd Service (MyNanoHttpdService) 大概是这样的:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (ACTION_START.equals(intent.getAction())) {
        String errorMessage = null;

        if (myNanoHttpd == null) {
            String hostUrl = intent.getStringExtra(EXTRA_HOST);
            Uri uri = Utils.notEmpty(hostUrl) ? Uri.parse(hostUrl) : Uri.EMPTY;
            myNanoHttpd = new MyNanoHttpd(this, uri.getHost(), uri.getPort(), null);
        }

        if (!myNanoHttpd.isAlive()) {
            try {
                myNanoHttpd.start();
            } catch (IOException e) {
                StringWriter stringWriter = new StringWriter();
                PrintWriter printWriter = new PrintWriter(stringWriter);
                e.printStackTrace(printWriter);
                errorMessage = stringWriter.toString();
                stopSelf();
            }
        }

        final ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RESULT_LISTENER);
        if (resultReceiver != null) {
            int status = myNanoHttpd.isAlive() ? CODE_SUCCESS : CODE_FAILURE;
            Bundle bundle = new Bundle();
            bundle.putString(EXTRA_MESSAGE, errorMessage);
            resultReceiver.send(status, bundle);
        }
    }

    return Service.START_STICKY;
}

这就是我从自定义 Application 对象启动服务、初始化客户端状态并获取一些内容的方式:

@Override
public void onCreate() {
    super.onCreate();

    // Yes, that is a Java 8 Lambda you see there!
    MyNanoHttpdService
            .start(this, "http://localhost:8080")
            .withStartupListener((status, message) -> {
                if (status == 0) {
                    // POST REQUEST: Works like a charm
                    myNetworkHelper.login();

                    // GET REQUEST: Always fails on first launch
                    myNetworkHelper.getContent();
                } else {
                    Log.e("LOG_TAG", "Couldn't start MyNanoHttpd: " + message);
                }
            });
}

可以安全地假设包装便利代码(.withStartupListener(...) - 本质上包装了上面 Service 使用的 ResultReceiver - 和 myNetworkHelper 对象)按预期工作。此外,在生产中,getContent() 调用将从 ActivityFragment 发出,但为了方便起见,我暂时将其移至 Application

我可能已经找到问题的根本原因,甚至可能暂时找到解决方法。

如果我的调查是正确的,那么问题是由之前 (POST) 请求中未使用的数据造成的,污染了当前 (POST) 请求。

NanoHTTPD 代码库中的

This 行(NanoHTTPD.HTTPSession.execute() 方法中的 header 解析块,就在调用任何自定义 serve(...) 之前方法 - 我上面问题中的第三个 "here")是抛出 HTTP 400 状态代码的那一行,正如代码所暗示的那样,"method" [=36= 没有正确的值].

该值 - 我希望以明文形式 "POST" - 被先前请求的 JSON 内容 body 的部分内容污染。一意识到这一点,我就尝试在我的自定义 MyNanoHttpd.serve(IHTTPSession session) 方法中使用整个请求 body,如下所示:

@Override
public Response serve(IHTTPSesion session) {
    InputStream inputStream = session.getInputStream();
    inputStream.skip(inputStream.available());

    // or
    // inputStream.skip(Long.MAX_VALUE);

    // or even
    // inputStream.close();

    ...

}

但是,这没有用,因为我不断收到各种异常。我最终轻轻地修改了 NanoHTTPD 代码,安全地关闭了非常 NanoHTTPD.HTTPSession.execute() 方法的 finally 块中的输入流。

尽管如此,我正在考虑联系 NanoHTTPD 社区以讨论合适且可持续的解决方案。