基于 lua-resty-upload 实现简单的文件上传服务

今天了解了一下 lua-resty-upload 模块,并基于 lua-resty-upload 模块简单实现了一个基本的表单文件上传服务。

lua-resty-upload 在 github 上的项目地址为: https://github.com/openresty/lua-resty-upload

从实现可以看到,其实 upload 服务的实现还是比较简单的,就一个源文件 lualib/resty/upload.lua,总的代码行数也只有 300 行不到。

下面我整理了一下搭建文件上传服务的过程:

1,前端页面很简单,就是使用 input file 的表单形式来触发文件上传,代码如下:

<!-- myupload.html -->
<!DOCTYPE html>
<html>
<head>
    <title>File upload example</title>
</head>
<body>
    <form action="upfile" method="post" enctype="multipart/form-data">
        <label for="testFileName">select file: </label>
        <input type="file" name="testFileName"/>
        <input type="submit" name="upload" value="Upload" />
    </form>
</body>
</html>

对应的 myupload.html 文件部署于 openresty/nginx/html/ 下。

2,实现接收文件上传表单信息,并保存至本地路径的 lua 代码,代码如下:

-- myupload.lua

--==========================================
-- 文件上传
--==========================================

local upload = require "resty.upload"
local cjson = require "cjson"

local chunk_size = 4096
local form, err = upload:new(chunk_size)
if not form then
    ngx.log(ngx.ERR, "failed to new upload: ", err)
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

form:set_timeout(1000)

-- 字符串 split 分割
string.split = function(s, p)
    local rt= {}
    string.gsub(s, ‘[^‘..p..‘]+‘, function(w) table.insert(rt, w) end )
    return rt
end

-- 支持字符串前后 trim
string.trim = function(s)
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

-- 文件保存的根路径
local saveRootPath = "/home/steven/openresty/nginx/upload/"

-- 保存的文件对象
local fileToSave

--文件是否成功保存
local ret_save = false

while true do
    local typ, res, err = form:read()
    if not typ then
        ngx.say("failed to read: ", err)
        return
    end

    if typ == "header" then
        -- 开始读取 http header
        -- 解析出本次上传的文件名
        local key = res[1]
        local value = res[2]
        if key == "Content-Disposition" then
            -- 解析出本次上传的文件名
            -- form-data; name="testFileName"; filename="testfile.txt"
            local kvlist = string.split(value, ‘;‘)
            for _, kv in ipairs(kvlist) do
                local seg = string.trim(kv)
                if seg:find("filename") then
                    local kvfile = string.split(seg, "=")
                    local filename = string.sub(kvfile[2], 2, -2)
                    if filename then
                        fileToSave = io.open(saveRootPath .. filename, "w+")
                        if not fileToSave then
                            ngx.say("failed to open file ", filename)
                            return
                        end
                        break
                    end
                end
            end
        end
    elseif typ == "body" then
        -- 开始读取 http body
        if fileToSave then
            fileToSave:write(res)
        end
    elseif typ == "part_end" then
        -- 文件写结束,关闭文件
        if fileToSave then
            fileToSave:close()
            fileToSave = nil
        end
        
        ret_save = true
    elseif typ == "eof" then
        -- 文件读取结束
        break
    else
        ngx.log(ngx.INFO, "do other things")
    end
end

if ret_save then
    ngx.say("save file ok")
end

通过阅读 lualib/resty/upload.lua 源码,该模块在解析文件上传请求的过程中,主要采用了简单的类似有限状态机的算法来实现的,在不同的状态由相应的 handler 进行处理,支持的状态包括如下状态:

  • STATE_BEGIN(1),初始状态,是在 upload:new 实例化的时候初始化的,如下源码(只保留了主干):
function _M.new(self, chunk_size, max_line_size)
    local boundary = get_boundary()
    local sock, err = req_socket()
    local read2boundary, err = sock:receiveuntil("--" .. boundary)
    local read_line, err = sock:receiveuntil("\r\n")

    return setmetatable({
        sock = sock,
        size = chunk_size or CHUNK_SIZE,
        line_size = max_line_size or MAX_LINE_SIZE,
        read2boundary = read2boundary,
        read_line = read_line,
        boundary = boundary,
        state = STATE_BEGIN
    }, mt)
end
  • STATE_READING_HEADER(2),开始解析 HTTP 头部消息,一般在这个阶段主要用于解析出其中的文件名, boundary 等信息;相应的 handler 为 read_header
  • STATE_READING_BODY(3),开始解析 HTTP 包体,这个阶段就是读取文件内容;
  • STATE_EOF(4),如果文件全部都解析和读取完后,则进入该状态,一般这个阶段表示文件都已经读取完;

这 4 个状态分别的 handler 为:

state_handlers = {
    read_preamble,
    read_header,
    read_body_part,
    eof
}

- 这里要注意的是不同阶段/状态下 read 返回的结构不同,如在 STATE_READING_HEADER 下返回的结构是 "header",{ key, value, line}

- 上传的文件会被保存在本地的路径 /home/steven/openresty/nginx/upload/

3,配置 nginx.conf,添加 location /upfile 用于接收文件上传的 action,并通过 myupload.lua 来解析文件上传内容后保存至本地文件系统,如下:

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       19080;
        server_name  localhost;
        
        location / {
            root   html;
            index  index.html index.htm;
        }

        location /upfile {
            content_by_lua_file lua/myupload.lua;
        }

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}
时间: 2024-09-29 18:19:39

基于 lua-resty-upload 实现简单的文件上传服务的相关文章

基于Java的一个简单的文件上传下载功能

最近在公司给客户端做接口,有一个图片上传和文件下载的功能,本来想用Struts来做文件上传下载,但是看了下公司好像没有这个配置,然后看了下同事的代码,才发现原来Apache也可以简单的实现文件上传下载. 首先引入commons-io-2.2.jar FileUtils为我们提供了很多对文件的操作的方法,比如上传整个文件夹的文件.上传单个文件等 然后请看代码下载: public static void uploadFile(String targetDirectory,String targetF

基于JSP+Servlet+JavaBean的图片或文件上传

基于JSP+Servlet+JavaBean的图片或文件上传 一.概述 现在不管是博客论坛还是企业办公,都离不开资源的共享.通过文件上传的方式,与大家同分享,从而达到大众间广泛的沟通和交流,我们既可以从中获得更多的知识和经验,也能通过他人的反馈达到自我改进和提升的目的. 下面我就为大家介绍 web项目中的这一上传功能,那么文件是如何从本地发送到服务器的呢?大家可以在在线视频课程进修学习<基于JSP+Servlet+JavaBean的人力资源管理系统开发>中第22课-项目开发-其它功能完善-图片

nodejs 简单http 文件上传demo

// 这是一个简单的Node HTTP,能处理当前目录的文件 // 并能实现良种特殊的URL用于测试 // 用http://localhost:8000 或http://127.0.0.1:8000 连接这个服务器 // 首先,加载所有要用的模块 var http = require('http'); // HTTP服务器API var fs = require('fs'); // 文件系统API var server = new http.Server(); // 创建新的HTTP服务器 va

jfinal初接触,一个简单的文件上传例子

写了个上传的小例子. 从jfinal官网下载jfinal-1.8_demo_for_jsp.zip 然后下载jfinal-1.8-lib.zip 按要求删掉该删除的,引入一些包,之后的项目结构: DemoConfig.java中配置路由,只留下了根路径: /** * 配置路由 */ public void configRoute(Routes me) { me.add("/", CommonController.class); //me.add("/blog", B

python写个简单的文件上传是有多难,要么那么复杂,要么各种,,,老子来写个简单的

def upload(url,params): ''' 上传文件到服务器,不适合大文件 @params url 你懂的 @params {"action":"xxx","@file","file_path"} 普通参数 key:value 文件key头部加@ ''' import os import urllib2 BOUNDARY = "0450de9528f040078csuxianbaozic06"

利用Socketserver实现简单的文件上传

利用Socketserver实现简单的文件上传 server.py #!/usr/bin/env python #coding:utf-8 import SocketServer import os class MyServer(SocketServer.BaseRequestHandler):     def handle(self):         base_path = 'C:/temp'         conn = self.request                 print

【Jsp】使用jspsmartupload完成简单的文件上传系统

请不要妄想,一个html的file控件,再加上javascript与jquery语句就可以完成文件上传, 文件上传系统从来是需要配合服务器来完成的 用户把自己的文件上传到服务器上 文件上传系统是很复杂的一件事. html的file控件向动态网页语言以post方式通过enctype="multipart/form-data"解码成二进制文件就不管了 动态网页需要对其通过asp,jsp,php,asp.net编程,才能正常把文件保存到服务器中 网络上对文件处理的方法是五花八门,让人无从下手

使用jsp/servlet简单实现文件上传与下载

使用JSP/Servlet简单实现文件上传与下载 通过学习黑马jsp教学视频,我学会了使用jsp与servlet简单地实现web的文件的上传与下载,首先感谢黑马.好了,下面来简单了解如何通过使用jsp与servlet实现文件上传与下载. 在写代码之前,我们需要导入两个额外的jar包,一个是common-io-2.2.jar,另一个是commons-fileupload-1.3.1.jar,将这个两个jar 包导入WEB-INF/lib目录里. 首先,想要在web端即网页上实现文件上传,必须要提供

javascript插件uploadify简单实现文件上传

最近在学习mvc,需要用到文件上传的功能,找了很多的jquery插件,最后决定使用uploadify这个插件,参照了各位大神的博客,终于勉勉强强会用了.在此,做一下笔记,方便以后忘了查看. 首先在官网上下载uploadify插件.http://www.uploadify.com/download/ 解压后有以下文件: 然后在把下载下来的文件解压到项目文件夹中(只要项目可以引用到就可以了,但还是应该规范一点,养成良好的习惯). 再然后,就是把uploadify.css和jquery.uploadi