首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

apisix~自定义文件上传代理插件~支持form-data文件和kv参数

编程知识
2024年10月12日 08:40

参考文献

问题产生的原因

后端有个文件上传服务,前端可以直接像文件上传到服务器,但这个上传服务除了有form-data文件流之外,还需要有其它key/value的表单参数,这些参数是固定的,或者有一定的规则,这时我们通过apisix代理一下,就显得更加灵活和理了。

http中的multipart/form-data消息体如下

修改后的请求,是一个标准的http请求,你通过postman的codesnippet视图也可以看到,代码如下

POST /mobile-server/manager/6.0.0.0.0/cdnManage/customUpload HTTP/1.1
Host: api-gw-test.pkulaw.com
Cookie: CookieId=b97385476b3c721c81a9163f1c8a85dd; SUB=347c9e9e-076c-45e3-be74-c482fffcc6e5; preferred_username=test; session_state=458053bd-5970-4200-9b6f-cf538ec9808b
Content-Length: 508
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="folder"

app/icon
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="domain"

https://static.pkulaw.com
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="fileName"

xzcf.png
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="multipartFile"; filename="/C:/Users/User/Pictures/21111.png"
Content-Type: image/png

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW--

开发过程中的一些坑

  1. 参数拼接错误,form-data的文件流应该是第一个参数
服务端收到的请求体和参数为空
  1. 后端服务直接报错,原因有以下几个
  • 有空的boundary,
  • boundary与字段之间没有\r\n换行
  • 将所有\n替换为\r\n,可能会解决上传文件和参数在接收端为空的问题
  • http请求头中的boundary是没有开头的两个减号的,这块非常容易出错,例如ngx.req.set_header("Content-Type", "multipart/form-data; boundary=" .. boundary)
  • boundary在各字段之前并不相同,需要着重看一下,一般是------开头,看看是否-的数量不同,可能接收端会有下面的错误,表示请求体拼接不正确
Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: Stream ended unexpectedly
  • 请求报400,大于8K的文件无法上传
    • 原因:apisix中的nginx配置中,client_max_body_size默认设置为0,表示没有限制,不是它的原因
    • 原因:client_body_buffer_size在apisix中没有设置,它会使用默认值,默认为8K,大于8K的文件就会报错,是这个原因
    • 解决:修改apisix-helm/values.yaml文件,nginx.configurationSnippet.httpStart节点添加配置client_body_buffer_size:15m
    nginx:
        # -- Custom configuration snippet.
        configurationSnippet:
          main: |
    
          httpStart: |
            client_body_buffer_size 20m;
    

file-upload-proxy文件上传转发插件源码

-- author: zhangzhanling
-- 文件上传服务代理
-- 代理前端,与文件上传服务进行通讯
-- 在请求体中,添加统一的参数
local core = require("apisix.core")
local uuid = require("resty.jit-uuid")
local ngx = require("ngx")
-- 定义原数据格式
local schema = {
    type = "object",
    properties = {
        folder = {
            type = "string",
            description = "相对目录"
        },
        domain = {
            type = "string",
            description = "图片服务的域名"
        }
    }
}

local _M = {
    version = 0.1,
    priority = 1009, --数值超大,优先级越高,因为authz-keycloak是2000,它需要在authz-keycloak之后执行,所以把它定为1000,因为咱们也依赖proxy_rewrite插件
    name = "file-upload-proxy",
    schema = schema
}

local function get_specific_header(ctx, header_name)
    local headers = core.request.headers(ctx)
    local value = headers[header_name]
    if type(value) == "table" then
        return table.concat(value, ", ")
    else
        return value
    end

end
-- 辅助函数:查找边界字符串
local function find_boundary(content_type)
    return content_type:match("boundary=([^;]+)")
end

function _M.rewrite(conf, ctx)
    ngx.req.read_body()
    local body_data = ngx.req.get_body_data()

    if not body_data then
        core.log.warn("Failed to read request body.")
        return 400
    end

    local content_type = ngx.req.get_headers()["content-type"]
    local boundary = find_boundary(content_type)

    if not boundary then
        core.log.warn("No boundary found in content type.")
        return 400
    end

    local startBoundary = "--" .. boundary

    local sub_value = get_specific_header(ctx, "sub")
    local folder = conf.folder
    if sub_value then
        folder = folder .. "/" .. sub_value
    end

    ---- 构建新的请求体
    local new_body = ""

    local fileExt = ".jpg"
    local filename = string.match(body_data, 'filename="([^"]+)"')

    if filename then
        -- 从filename中提取扩展名
        local _, _, ext = string.find(filename, "%.([^.]+)$")
        if ext then
            core.log.info("文件扩展名为: " .. ext)
            fileExt = "." .. ext;
        end
    end

    -- 添加新字段
    local new_fields = {
        { name = "domain", value = conf.domain },
        { name = "fileName", value = uuid() .. fileExt },
        { name = "folder", value = folder }
    }
    ---- 添加新字段
    for _, field in ipairs(new_fields) do
        new_body = new_body .. string.format("\r\n%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s", startBoundary, field.name, field.value)
    end

    new_body = new_body .. "\r\n" .. body_data

    -- 设置新的请求体
    ngx.req.set_body_data(new_body)

    -- 更新 Content-Type 头
    ngx.req.set_header("Content-Type", "multipart/form-data; boundary=" .. boundary)

    -- 计算并设置 Content-Length
    local content_length = string.len(new_body)
    ngx.req.set_header("Content-Length", content_length)

    -- 日志输出新请求体和内容长度
    core.log.warn("boundary:", boundary)
    core.log.warn("New request body: ", new_body)
    core.log.warn("Content-Length: ", content_length)
end

-- 注册插件
return _M

From:https://www.cnblogs.com/lori/p/18459845
本文地址: http://www.shuzixingkong.net/article/2471
0评论
提交 加载更多评论
其他文章 Android 车载应用开发指南 - CAN Bus 协议详解
​ 在现代车载应用开发中,CAN(Controller Area Network)总线协议扮演着不可或缺的角色。作为一个汽车内部网络的标准协议,CAN Bus 已经成为了车载系统通信的基础。而在 Android 车载应用开发的过程中,理解并利用好 CAN Bus 协议是必不可少的。 那么,CAN B
Android 车载应用开发指南 - CAN Bus 协议详解 Android 车载应用开发指南 - CAN Bus 协议详解 Android 车载应用开发指南 - CAN Bus 协议详解
Nuxt3+PM2集群模式启动及勘误
起因 之前写过一篇 Nuxt3 的文章,Nuxt3 环境变量配置,用到了 PM2,但是里面的一些配置存在问题,最近有空又验证了一下,这里做一个勘误。 问题 PM2 的启动配置中有一项是exec_mode,默认是fork,另一个可选值是cluster,fork 是单进程模式,cluster 是多进程模
Nuxt3+PM2集群模式启动及勘误 Nuxt3+PM2集群模式启动及勘误 Nuxt3+PM2集群模式启动及勘误
Spark任务OOM问题如何解决?
大家好,我是 V 哥。在实际的业务场景中,Spark任务出现OOM(Out of Memory) 问题通常是由于任务处理的数据量过大、资源分配不合理或者代码存在性能瓶颈等原因造成的。针对不同的业务场景和原因,可以从以下几个方面进行优化和解决。 一、业务场景及可能的OOM原因分析 数据量过大: 业务场
数据结构 - 栈
栈是一种特殊线性数据结构,操作遵循后进先出原则,可解决表达式求值等问题。栈分为顺序栈和链栈,各有特点。文章详细介绍了栈的定义、分类及实现方式,包括顺序栈和链栈的ADT定义及基本操作实现。
数据结构 - 栈 数据结构 - 栈 数据结构 - 栈
WiFi基础(六):天线基础知识
liwen01 2024.10.01 前言 麦克斯韦预言了电磁波的存在,赫兹通过实验证实了麦克斯韦的预言,马可尼基于无线电磁波的原理发明了无线电报系统,从此人类进入无线通信系统时代。 天线是通信系统中必不可少的组成部分,它的作用是将电信号转换为电磁波信号发射出去,也可以将接收到的电磁波信号转换为电信
WiFi基础(六):天线基础知识 WiFi基础(六):天线基础知识 WiFi基础(六):天线基础知识
SaaS架构:中央库存系统架构设计
大家好,我是汤师爷~ 近年来,越来越多的零售企业大力发展全渠道业务。在销售额增长上,通过线上的小程序、直播、平台渠道等方式,拓展流量变现渠道。在会员增长方面,通过多样的互动方式,全渠道触达消费者,扩大会员规模。而全渠道的库存管理,逐渐变成零售商在渠道运营方面的核心活动,也是提高库存周转率,保证利润的
SaaS架构:中央库存系统架构设计 SaaS架构:中央库存系统架构设计 SaaS架构:中央库存系统架构设计
AvaloniaTCP-v1.0.0:学习使用Avalonia/C#进行TCP通讯的一个简单Demo
AvaloniaTCP-v1.0.0:学习使用Avalonia/C#进行TCP通讯的一个简单Demo。
AvaloniaTCP-v1.0.0:学习使用Avalonia/C#进行TCP通讯的一个简单Demo AvaloniaTCP-v1.0.0:学习使用Avalonia/C#进行TCP通讯的一个简单Demo AvaloniaTCP-v1.0.0:学习使用Avalonia/C#进行TCP通讯的一个简单Demo
nicegui太香了,跨平台开发和跨平台运行--使用Python+nicegui实现系统布局界面的开发
在现今国产化浪潮的驱动下,跨平台或者缩小范围说基于国产化Linux或者基于国产鸿蒙系统的开发才是未来的趋势了,风口浪尖上,我们开发人员也只能顺势而为,本篇随笔介绍在Python开发中,使用使用Python+nicegui实现系统布局界面的开发。
nicegui太香了,跨平台开发和跨平台运行--使用Python+nicegui实现系统布局界面的开发 nicegui太香了,跨平台开发和跨平台运行--使用Python+nicegui实现系统布局界面的开发 nicegui太香了,跨平台开发和跨平台运行--使用Python+nicegui实现系统布局界面的开发