使用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
相关产品推荐
相关产品推荐

