liangjingkanji / Net

Android 基于协程/OkHttp网络请求工具
http://liangjingkanji.github.io/Net/
MIT License
1.87k stars 211 forks source link

咨询为何固定filename为RequestBody #205

Closed TxcA closed 11 months ago

TxcA commented 11 months ago

问题描述

咨询为何固定filenameRequestBody,是否有其它的意义。

关联提交: 412e293 refactor: 删除param(RequestBody, Header)

fun param(name: String, value: RequestBody?...) {

   fun param(name: String, value: RequestBody?) {
        value ?: return
       - partBody.addFormDataPart(name, null, value)
       + // 指定fileName可以作为判断为文件类型依据
       + partBody.addFormDataPart(name, "RequestBody", value)
    }

相关问题

接口比较奇特,需要在FormData中提交 Content-Type: application/json,具体数据也为json,同时也会提交一个文件。 之前实现的方法为

val response = Put<ApiResponse<FaceSearchResp?>>("${FACE_SERVER_URL}/face/search") {
    param("json", FaceRequest(user.relKey, mapOf("searchMe" to searchMe)).toRequestBody())
    param("file", "image.jpg", imageFile)
}.await()

... 
// 对应上面json的 toRequestBody
fun toRequestBody(): RequestBody {
    id = //---
    time = //---
    sign = //---
    return ApiConfig.GLOBAL_GSON.toJson(this).toRequestBody(MediaConst.JSON)
}

之前不带filename="RequestBody",正常解析为非文件段,所以可以正常解析,目前自动带了filename,应该是被解析成文件了,所以参数获取失败。(PS: 第三方后端,改不了) 咨询为何固定filenameRequestBody,是否有其它的意义。

错误的请求 image

正确的请求 image

期望行为

如果此处的filename "RequestBody" 有其存在的意义,是否可以

    fun param(name: String, value: RequestBody?, filename: String? = "RequestBody" ) {
        value ?: return
        // 指定fileName可以作为判断为文件类型依据
        partBody.addFormDataPart(name, filename, value)
    }

如何复现

无需复现 fork仓库并复现问题可以快速解决, 猜测只会让问题晦涩难懂, 耽误所有人时间

截图

异常堆栈信息或者手机截图/视频(拖拽到输入框即可上传)

版本

TxcA commented 11 months ago

当然可以修改为

param("json", xxx.toRequestBody(MediaConst.JSON))

👇

partBody.addFormDataPart("json", null,  xxx.toRequestBody(MediaConst.JSON))

主要想了解添加"RequestBody"的意义,或是否可以优化。

liangjingkanji commented 11 months ago

历史提交备注不明确

我认为应该是之前为了避免RequestBody打印文件日志, 包含filename就可以作为当前参数为文件类型的依据

fun MultipartBody.Part.fileName(): String? {
    val contentDisposition = headers?.get("Content-Disposition") ?: return null
    val regex = ";\\s${"filename"}=\"(.+?)\"".toRegex()
    val matchResult = regex.find(contentDisposition)
    return matchResult?.groupValues?.getOrNull(1)
}
TxcA commented 11 months ago

虽然这个接口格式奇异,但是正常form-data也非一定为文件。

接口工具示例,非真实请求


OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("multipart/form-data; boundary=--------------------------753244884812106692899942");
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("name","TxcA")
.addFormDataPart("data","abc123")
.addFormDataPart("num","111")
.addFormDataPart("file","X:\\68b9254587874f5ba28288bbe9bbe26a.jpg",
RequestBody.create(MediaType.parse("application/octet-stream"),
new File("X:\\68b9254587874f5ba28288bbe9bbe26a.jpg")))
.build();

Request request = new Request.Builder() // ... .build(); Response response = client.newCall(request).execute();

liangjingkanji commented 11 months ago

那我删了这个默认文件名吧

liangjingkanji commented 11 months ago

另外再新增一个方法如何

fun param(name: String, filename: String?, value: RequestBody?) {
    value ?: return
    partBody.addFormDataPart(name, filename, value)
}

由于参照okhtpp函数规则, 另外顺便兼容java函数重载我并没有使用默认参数

liangjingkanji commented 11 months ago

之前因为RequestBody和表单参数结合导致日志乱七八糟, 我认为日志不需要打印Part中的RequestBoyd具体内容

Part中附带RequestBody几乎可以肯定为文件吧

TxcA commented 11 months ago

默认参数@JvmOverloads应该可以覆盖。

去年你都不考虑兼容Java的,今年居然变了。另,实在要兼容Java感觉还是再做一层比较合适😁

TxcA commented 11 months ago

之前因为RequestBody和表单参数结合导致日志乱七八糟, 我认为日志不需要打印Part中的RequestBoyd具体内容

Part中附带RequestBody几乎可以肯定为文件吧

当下90%是,这个也不一定。一些上古接口json没流行时,除了x-www-form-urlencodedform-data里提交参数的也不少。

liangjingkanji commented 11 months ago

我不考虑是没有精力并且害怕臃肿, 但是有考虑支持java二次封装来使用

liangjingkanji commented 11 months ago

默认参数@JvmOverloads应该可以覆盖。

去年你都不考虑兼容Java的,今年居然变了。另,实在要兼容Java感觉还是再做一层比较合适😁

fun param(name: String, fileName: String?, value: File?)  // 但是之前的filename顺序在File之前
liangjingkanji commented 11 months ago

我直接限制日志对Part的RequestBody打印最大长度512

fun MultipartBody.Part.value(): String? {
    return fileName() ?: body.peekBytes(512).utf8()
}
TxcA commented 11 months ago

上传File的,大部分应该还是会用fun param(name: String, fileName: String?, value: File?),用fun param(name: String, value: RequestBody?)的,大部分就应该不是传File

如果因为优化日志改动了其它地方,感觉处理目标错了。应该去找日志地方筛除File的二进制Body段。

TxcA commented 11 months ago

我直接限制日志对Part的RequestBody打印最大长度512

fun MultipartBody.Part.value(): String? {
    return fileName() ?: body.peekBytes(512).utf8()
}

个人感觉打印File body 意义不大,除了纯文本,其它任何文件也看不出是什么东西。可以置一个变量是否打印File Body,默认false。

liangjingkanji commented 11 months ago

我直接限制日志对Part的RequestBody打印最大长度512

fun MultipartBody.Part.value(): String? {
    return fileName() ?: body.peekBytes(512).utf8()
}

个人感觉打印File body 意义不大,除了纯文本,其它任何文件也看不出是什么东西。可以置一个变量是否打印File Body,默认false。

怎么判断是file? 没filename存在无法判断

TxcA commented 11 months ago

okhttp3.MultipartBody

line: 210

@JvmStatic
fun createFormData(name: String, filename: String?, body: RequestBody): Part {
val disposition = buildString {
append("form-data; name=")
appendQuotedString(name)
if (filename != null) {
append("; filename=")
appendQuotedString(filename)
}
}

听你的意思,当初加filename = "RequestBody"应该是为了

fun MultipartBody.Part.value(): String? {
    return fileName() ?: body.peekBytes().utf8()
}

但目前的问题是

  1. form-data不一定必须是File
  2. 不一定从Content-Disposition判断是否有filename,也可以从Content-Type判断非文件类型,但是做框架的话非做项目,要考虑各异人使用的兼容(比如我这边这个奇葩接口的问题),所以的确也麻烦。
TxcA commented 11 months ago

iana media-types Content-Type太多了,且自生不断也再更新🥲

但是常用的应该能缕一缕 WiKi MediaType

liangjingkanji commented 11 months ago

Content-Type可以针对单个PartBody吗? 我依然不确定如何处理Part日志打印