You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Kotlin Volley请求FINRA债券数据时为何返回400错误?

Kotlin Volley请求FINRA债券数据返回400错误的解决方法

问题概述

将可正常运行的Python请求代码改写为Kotlin Volley版本,用于获取FINRA债券数据,但POST请求始终返回400错误。流程上需先通过GET请求获取XSRF-TOKEN(存储在cfruid cookie中),再携带该TOKEN发起POST请求,即使设置了headers和JSON参数,问题仍未解决。

错误原因分析

  • 异步请求顺序错误:Volley请求队列是异步执行的,原代码同时将GET和POST请求加入队列,POST请求可能在GET请求完成并获取到cfruid之前就已发送,此时cfruid为空,导致XSRF-TOKEN验证失败。
  • Cookie解析逻辑不严谨:直接取cookies[0]可能拿到非XSRF-TOKEN的cookie,无法保证获取到正确的TOKEN值。
  • Header中Cookie格式问题:Cookie字符串末尾的多余分号可能不符合服务器要求,且手动设置了Volley会自动处理的authority和host头,增加了不必要的风险。

修正后的代码

private var cfruid = ""

private fun finraGetBondInfo(companyName: String) {
    val url = "https://www.finra.org/finra-data/fixed-income/corp-and-agency"
    val queue = Volley.newRequestQueue(requireActivity())

    val stringRequest = object: StringRequest(Request.Method.GET, url,
        { response ->
            // GET请求成功后,再构造并发送POST请求
            prepareAndSendPostRequest(queue, companyName)
        },
        { error ->
            Log.e("Error", error.toString())
        })
    {
        override fun parseNetworkResponse(response : NetworkResponse) : Response<String> {
            response.headers?.get("Set-Cookie")?.let { cookieHeader ->
                val cookies = HttpCookie.parse(cookieHeader)
                // 精准筛选XSRF-TOKEN cookie
                cookies.firstOrNull { it.name == "XSRF-TOKEN" }?.let { xsrfCookie ->
                    cfruid = xsrfCookie.value
                    Log.d("Cfruid:", cfruid)
                } ?: run {
                    Log.e("Cookie Error", "未找到XSRF-TOKEN cookie")
                }
            } ?: run {
                Log.e("Cookie Error", "响应中无Set-Cookie头")
            }
            return super.parseNetworkResponse(response)
        }
    }

    queue.add(stringRequest)
}

// 单独提取POST请求逻辑,确保只有拿到有效TOKEN后才执行
private fun prepareAndSendPostRequest(queue: RequestQueue, companyName: String) {
    if (cfruid.isEmpty()) {
        Log.e("Request Error", "XSRF-TOKEN未获取到,无法发送POST请求")
        return
    }

    // 构造请求JSON(逻辑与原代码一致)
    val json = JSONObject()
    val fieldsArray = JSONArray()
    fieldsArray.put("issueSymbolIdentifier")
    fieldsArray.put("issuerName")
    fieldsArray.put("isCallable")
    fieldsArray.put("productSubTypeCode")
    fieldsArray.put("couponRate")
    fieldsArray.put("maturityDate")
    fieldsArray.put("industryGroup")
    fieldsArray.put("moodysRating")
    fieldsArray.put("standardAndPoorsRating")
    fieldsArray.put("lastSalePrice")
    fieldsArray.put("lastSaleYield")
    json.put("fields", fieldsArray)

    json.put("dateRangeFilters", JSONArray())
    json.put("domainFilters", JSONArray())
    json.put("compareFilters", JSONArray())

    val multiJson = JSONObject()
    multiJson.put("fuzzy", false)
    multiJson.put("searchValue", companyName)
    multiJson.put("synonym", true)
    val subfieldsJson = JSONObject()
    subfieldsJson.put("name", "issuerName")
    subfieldsJson.put("boost", 1)
    multiJson.put("fields", subfieldsJson)
    val multiArray = JSONArray()
    multiArray.put(multiJson)

    json.put("multiFieldMatchFilters", multiArray)
    json.put("orFilters", JSONArray())
    json.put("aggregationFilter", JSONObject.NULL)
    val sortFieldsArray = JSONArray()
    sortFieldsArray.put("+issuerName")
    json.put("sortFields", sortFieldsArray)
    json.put("limit", 50)
    json.put("offset", 0)
    json.put("delimiter", JSONObject.NULL)
    json.put("quoteValues", false)

    Log.d("JSON", json.toString())

    val url2 = "https://services-dynarep.ddwa.finra.org/public/reporting/v2/data/group/FixedIncomeMarket/name/CorporateAndAgencySecurities"
    val request2 = object: JsonObjectRequest(Request.Method.POST, url2, json,
        { response ->
            Log.d("Response:", "$response")
        },
        { error ->
            Log.e("Error", error.toString())
        })
    {
        override fun getHeaders(): MutableMap<String, String> {
            val headers = HashMap<String, String>()
            headers["accept"] = "application/json, text/plain, */*"
            headers["content-type"] = "application/json"
            headers["cookie"] = "XSRF-TOKEN=$cfruid"
            headers["origin"] = "https://www.finra.org"
            headers["referer"] = "https://www.finra.org/"
            headers["x-xsrf-token"] = cfruid
            Log.d("headers:", headers.toString())
            return headers
        }
    }
    request2.retryPolicy = DefaultRetryPolicy(
        50000,
        DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
        DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
    )
    queue.add(request2)
}

// 调用方法
finraGetBondInfo("Boeing")

关键修改说明

  • 调整请求顺序:将POST请求的构造和发送放在GET请求的成功回调中,确保只有获取到有效XSRF-TOKEN后才发起POST请求。
  • 优化Cookie解析:通过firstOrNull { it.name == "XSRF-TOKEN" }精准获取目标Cookie,避免取到无关Cookie。
  • 修正Header格式:去掉Cookie字符串末尾的多余分号,移除Volley自动处理的authority和host头,减少不必要的手动配置。
  • 使用类成员变量存储TOKEN:避免局部变量在异步回调中的生命周期问题,确保POST请求能获取到正确的TOKEN值。

内容的提问来源于stack exchange,提问作者David Frick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 13:19:58