参考文献

问题产生的原因

后端有个文件上传服务,前端可以直接像文件上传到服务器,但这个上传服务除了有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

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

apisix~自定义文件上传代理插件~支持form-data文件和kv参数的更多相关文章

  1. 表单多文件上传样式美化 && 支持选中文件后删除相关项

    开发中会经常涉及到文件上传的需求,根据业务不同的需求,有不同的文件上传情况. 有简单的单文件上传,有多文件上传,因浏览器原生的文件上传样式及功能的支持度不算太高,很多时候我们会对样式进行美化,对功能进 ...

  2. Nginx集群之WCF大文件上传及下载(支持6G传输)

    目录 1       大概思路... 1 2       Nginx集群之WCF大文件上传及下载... 1 3       BasicHttpBinding相关配置解析... 2 4       编写 ...

  3. Struts2文件上传(基于表单的文件上传)

    •Commons-FileUpload组件 –Commons是Apache开放源代码组织的一个Java子项目,其中的FileUpload是用来处理HTTP文件上传的子项目   •Commons-Fil ...

  4. python selenium 操作文件上传,并发操作时,文件选择窗口混乱解决方案

    上传文件 使用的是 python + autoit 模块,这种方式有一个问题,当出现多条任务同时选择文件上传的时候,无法判断那个文件选择窗口的归属,从而出现上传了错误的文件! 解决方法: 要上载文件而 ...

  5. jQuery.uploadify-----文件上传带进度条,支持多文件上传的插件

    借鉴别人总结的uploadify:基于jquery的文件上传插件,支持ajax无刷新上传,多个文件同时上传,上传进行进度显示,控制文件上传大小,删除已上传文件. uploadify有两个版本,一个用f ...

  6. python 全栈开发,Day75(Django与Ajax,文件上传,ajax发送json数据,基于Ajax的文件上传,SweetAlert插件)

    昨日内容回顾 基于对象的跨表查询 正向查询:关联属性在A表中,所以A对象找关联B表数据,正向查询 反向查询:关联属性在A表中,所以B对象找A对象,反向查询 一对多: 按字段:xx book ----- ...

  7. ajaxfileupload多文件上传 - 修复只支持单个文件上传的bug

    搜索: jquery ajaxFileUpload AjaxFileUpload同时上传多个文件 原生的AjaxFileUpload插件是不支持多文件上传的,通过修改AjaxFileUpload少量代 ...

  8. Django与Ajax,文件上传,ajax发送json数据,基于Ajax的文件上传,SweetAlert插件

    一.Django与Ajax AJAX准备知识:JSON 什么是 JSON ? JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation) JSON 是轻 ...

  9. 考虑浏览器兼容的文件上传(IE8不支持FormData)

    方法一:使用FormData(因IE8不支持FormData, IE10才支持,因此此方法不兼容IE10以下的IE浏览器) 也可参考文章 http://www.jianshu.com/p/46e6e0 ...

  10. JS_单个或多个文件上传_不支持单独修改

    A-From表单直接填写提交地址,不过干预: 1. 单文件上传 最简单的文件上传,是单文件上传,form标签中加入enctype="multipart/form-data",for ...

随机推荐

  1. 【SpringMVC】12 文件上传和下载

    编写一个请求上传和下载的JSP页面 <%@ page contentType="text/html;charset=UTF-8" language="java&qu ...

  2. 【Docker】03 基础操作

    [Docker 本体操作相关] 检查Docker版本: docker -v 检查Docker当前状态: systemctl status docker 停止Docker与开启Docker system ...

  3. PyCharm2024 专业版激活设置中文

    PyCharm2024 专业版激活设置中文 官网下载最新版:https://www.jetbrains.com/zh-cn/pycharm/download 「hack-jet激活idea家族.zip ...

  4. 【转载】 SLI导致双显卡被TensorFlow同时占用问题(Windows下) ---------- (windows环境下如何为tensorflow安装多个独立的消费级显卡)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/qq_21368481/article/d ...

  5. 免费领取云主机,在华为开发者空间玩转YOLOV3

    摘要:YOLOv3(You Only Look Once version 3)是一种高效的目标检测算法,旨在实现快速而准确的对象检测. 本文分享自华为云社区<华为云开发者云主机体验[玩转华为云] ...

  6. 增强用户体验:2个功能强大的.NET控制台应用帮助库

    前言 对于.NET开发者而言,构建控制台应用程序时,如何提升用户交互的流畅性和满意度,是一个持续探索与优化的话题.今天大姚给大家分享2个功能强大的.NET控制台应用帮助库,希望可以帮助大家能够快速的构 ...

  7. 白鲸开源 X SelectDB 金融大数据联合解决方案公布!从源头解决大数据开发挑战

    业务挑战与痛点 随着互联网技术的发展.云计算技术的成熟.人工智能技术的兴起和数字化经济的崛起,数据已成为企业的核心资产.在金融行业中,数字化已成为了支撑各类业务场景的核心力量,包括个人理财.企业融资. ...

  8. 通过JUnit源码分析学习编程的奇技淫巧

    打开 Maven仓库,左边选项栏排在第一的就是测试框架与工具,今天的文章,V 哥要来聊一聊程序员必备的测试框架JUnit 的源码实现,整理的学习笔记,分享给大家. 有人说,不就一个测试框架嘛,有必要去 ...

  9. Orleans初体验

    Orleans: 是一个跨平台框架,用于构建可靠且可缩放的分散式应用. 分布式应用定义为跨多个进程的应用,通常使用对等通信来超越硬件边界. 从单个本地服务器扩展到了云中数千个分布式.高度可用的应用. ...

  10. SMU Spring 2023 Trial Contest Round 11

    A. The Text Splitting 题意:给出字符串长度,给出p和q两种切割方式,任选其中一种,把字符串分割输出结果. 题解:先进行判断,p和q是否能整个的分割n,利用p和q的函数关系判断(见 ...