简介
本文讲解在centos系统使用openresty,并实现基本的用户认证和文件访问、上传、删除的功能。
安装openresty所需环境
openresty本质上还是个nginx,所以安装openresty需要有nginx的环境,如果服务器没有安装过nginx,请参考《Nginx从安装到配置》一文中的“安装所需环境”一节,安装nginx所需要的环境。
下载openresty安装包
可在官方页面http://openresty.org/cn/download.html下载最新的版本。下载后解压:tar -zxvf xxx.tar.gz
配置openresty
我解压后的目录在/home/freebytes/work/openresty,进入目录后运行:
./configure \ --prefix= /home/freebytes/work/imoon/openresty \ --with-luajit
–prefix将安装目录设置到自定义位置。接着运行:
make
make install
OK,在/home/freebytes/work/imoon/openresty下将会出现所有需要的文件。如果卸载,那就把这个文件删了就行了。
在安装目录下输入ls,可以看到——
这里的nginx目录,其实跟正常安装nginx之后产生的安装目录是一样的。nginx目录下是这些熟悉的文件——
运行openresty
注意,之后的所有文件操作都是基于这个目录下面 /home/freebytes/work/imoon/openresty
运行openresty其实就是运行nginx,我们可以看到openresty目录下有个nginx目录,里面有运行文件和配置文件,现在先配置一下nginx,所以输入:
vi ./nginx/conf/nginx.conf
编辑文本,只需如下代码:
user freebytes(这里写你自己的用户名); worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { server { listen 80; location / { default_type text/html; content_by_lua_block { ngx.say("<p>hello, world</p>") } } } }
在当前目录下运行指令:
./nginx/sbin/nginx
如此便可启动openresty。重载的命令是 ./nginx/sbin/nginx -s reload ,停止的命令是 ./nginx/sbin/nginx -s stop 。
当然你也可以通过执行bin目录中的名为“openresty”的文件来启动openresty,但它本质上还是链接到了nginx的启动文件,所以其实都是一样的。
然后,在浏览器输入http://localhost:80,应显示:hello,word 。 则成功!
配置上传、删除文件功能
简单地来说,你可以把openresty理解成nginx+lua脚本。也就是nginx将业务处理逻辑交给了lua去做。因此,我们在openresty上实现上传和删除的功能理应是这样的流程:
- 编辑nginx.conf文本,配置两个location,一个对应上传请求,一个对应删除请求。
- 编写两个lua脚本,一个处理上传,一个处理删除。
- 在对应上传请求的location中,引入上传脚本,将上传请求交给lua去处理; 在对应删除请求的location中,引入删除脚本,将删除请求交给lua去处理 。
因此我们要做的第一步就是, 将刚才的nginx.conf文本的http模块替换如下:
http { include mime.types; default_type application/octet-stream; charset utf-8; server { listen 80; server_name localhost; # 最大允许上传的文件大小 client_max_body_size 400m; location / { root html; index index.html index.htm; } # 设置文件存储路径给变量$store_dir,这个变量值会传递到lua中 set $store_dir "/home/freebytes/work/imoon/openresty/nginx/html/file/all/"; # 文件上传接口:http://xxx:80/file/upload location /file/upload { #这里实现用户认证 暂不使用 所以注释掉 # auth_basic "input your password"; # auth_basic_user_file auth.user; # 这里配置上传脚本,实现文件上传的逻辑 content_by_lua_file /home/freebytes/work/imoon/openresty/lualib/resty/freebytes_upload.lua; } # 文件删除接口:http://xxx:80/file/delete location /file/delete { #这里实现用户认证 暂不使用 所以注释掉 # auth_basic "please input your password"; # auth_basic_user_file auth.user; # 这里配置删除脚本,实现文件删除的逻辑 content_by_lua_file /home/freebytes/work/imoon/openresty/lualib/resty/freebytes_delete.lua; } # 文件访问、下载入口: http://xxx:80/file/all location /file/all{ autoindex on; autoindex_localtime on; root html; index index.html; } # redirect server error pages to the static page /50x.html error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
配置文件中,设置了三个location,分别用于文件上传、删除、访问下载。并且,设置了文件的存储路径为/home/freebytes/work/imoon/openresty/nginx/html/file/all,因此我要建立这个文件夹:
mkdir /home/freebytes/work/imoon/openresty/nginx/html/file/all
并将 file文件夹及其子文件夹的权限拥有者变成freebytes用户,以避免文件读写的权限问题。
chown -R freebytes:freebytes /home/freebytes/work/imoon/openresty/nginx/html/file/
第二步,准备文件上传处理、删除处理的lua脚本,将脚本放在/home/freebytes/work/imoon/openresty/lualib/resty目录下面,因为我在上面的nginx.conf中已经配置好了路径。这里提供一份标准的上传脚本和一份标准的删除脚本:
-- freebytes_upload.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 = ngx.var.store_dir -- 保存的文件对象 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
--========================================== -- 文件删除 freebytes_delete.lua --========================================== local upload = require "resty.upload" local cjson = require "cjson" -- 获取http请求的所有参数 local args = ngx.req.get_uri_args() if not args then ngx.exit(ngx.HTTP_BAD_REQUEST) end local filename = args["filename"] or "noname.file" local billingcode = args["billingcode"] or "" local filetype = args["type"] local response = {["code"] = 200, ["msg"] = "remove success!"} -- 保存文件根目录 local save_file_root = ngx.var.store_dir -- 确定删除文件路径 local remove_file_path = save_file_root remove_file = remove_file_path .. "/" .. filename -- 判断删除文件是否存在 local dfile = io.open(remove_file, "rb") if dfile then dfile:close() else response.code = 403 response.msg = "the remove file is not exist!" ngx.say(cjson.encode(response)) return end -- 执行删除 local res, err = os.remove(remove_file) if not res then response.code = 404 response.msg = "failed to remove " .. remove_file .. ", err: " .. (err or '') else ngx.log(ngx.ERR, "success to remove file: " .. remove_file) end ngx.say(cjson.encode(response))
此时,重启nginx,浏览器访问 http://localhost:80/file/all/ , 如果出现如下画面,就证明文件访问请求没有问题。
再利用postman工具测试文件上传接口。
上图是上传接口的测试,可以看到返回数据为 save file ok , 表明上传成功。测试再访问 /file/all,就会看到已经存在一个文件了——
继续测试删除接口 /file/delete?filename=xxx:
删除接口测试成功,然后再看/file/all,就会发现刚才那个文件已经被删了。
此时,openresty已经具备了文件上传、访问(下载)、删除功能。
配置用户认证功能
文件的上传和删除请求,理应都需要用户认证来保证安全性。openresty的用户认证功能,可以通过配置nginx的用户认证来实现。先利用htppd工具生成密码文件:
#安装工具 yum -y install httpd-tools #进入conf目录 cd ./nginx/conf #生成密码文件,用户名是freebytes,密码需要手动输入 htpasswd -c auth.user freebytes
此时会在nignx/conf目录下生成一个auth.user文件,内容是这样的:
freebytes:$apr1$mxIfqNJF$twGmXQsbS/ykHhl0aa4Al/
前者是用户名,后者是加密后的密码。
然后再来改一下nginx.conf文件,也就是在对应上传请求和删除请求的location中, 把刚刚注释掉的两段有关认证的代码的注释符号去掉——
所以现在的完整的具备文件上传、删除、访问下载和用户认证功能的nginx.conf是这样的——
http { include mime.types; default_type application/octet-stream; charset utf-8; server { listen 80; server_name localhost; # 最大允许上传的文件大小 client_max_body_size 400m; location / { root html; index index.html index.htm; } # 设置文件存储路径给变量$store_dir,这个变量值会传递到lua中 set $store_dir "/home/freebytes/work/imoon/openresty/nginx/html/file/all/"; # 文件上传接口:http://xxx:80/file/upload location /file/upload { #这里实现用户认证 auth_basic "input your password"; auth_basic_user_file auth.user; # 这里配置上传脚本,实现文件上传的逻辑 content_by_lua_file /home/freebytes/work/imoon/openresty/lualib/resty/freebytes_upload.lua; } # 文件删除接口:http://xxx:80/file/delete location /file/delete { #这里实现用户认证 auth_basic "please input your password"; auth_basic_user_file auth.user; # 这里配置删除脚本,实现文件删除的逻辑 content_by_lua_file /home/freebytes/work/imoon/openresty/lualib/resty/freebytes_delete.lua; } # 文件访问、下载入口: http://xxx:80/file/all location /file/all{ autoindex on; autoindex_localtime on; root html; index index.html; } # redirect server error pages to the static page /50x.html error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
重载nginx ,然后用postman重新访问上传和删除接口,就会发现报401错误——
用浏览器访问会要求你输入用户名和密码;但是文件访问接口/file/all是正常的。那么怎样才能正常访问到上传和删除接口呢?
nginx的用户认证是基于http的基本认证,所以访问nignx的请求中需要带有 Authorization 请求头。利用postman很容易构建 Authorization 请求头——
如此配置,发出的的请求中就会带上 Authorization 请求头,key值是Authorization,value值是用户名和密码经过base64加密后的字符串。
这样,再次请求上传和删除接口,都能正常了。
java访问openresty的文件上传和删除功能
上文对于openresty的功能测试,都是利用postman工具完成的。但是如果需要在web系统中用到这些功能,还是需要一个java构建的客户端的。下面提供一个标准的java类及其所需依赖,具备文件上传和删除功能——
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5.3</version> </dependency>
import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import javax.xml.bind.DatatypeConverter; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.List; /** * 千里 • 明月 * freebytes.net */ public class ToOpenresty { private String requestUrl; private String filename; private String authUser; private String authPassword; private int connectTimeout = 10000; private int socketTimeout = 10000; public ToOpenresty(String requestUrl, String filename, String authUser, String authPassword) { this.requestUrl = requestUrl; this.filename = filename; this.authUser = authUser; this.authPassword = authPassword; } public static void main(String[] args) { //上传文件 ToOpenresty upload = new ToOpenresty("http://192.168.50.41:80/file/upload", "F:\\1500945430967.jpg", "freebytes", "123456"); upload.uploadToOpenresty(); //删除文件 // ToOpenresty delete = new ToOpenresty("http://192.168.50.41:80/file/delete", "1500945430967.jpg", "freebytes", "123456"); // delete.deleteByFilename(); } /** * 上传接口 */ public void uploadToOpenresty() { CloseableHttpClient httpclient = HttpClients.createDefault(); CloseableHttpResponse response = null; try { HttpPost httppost = new HttpPost(requestUrl); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build(); httppost.setConfig(requestConfig); FileBody bin = new FileBody(new File(filename)); HttpEntity reqEntity = MultipartEntityBuilder.create().addPart("file", bin).build(); httppost.setEntity(reqEntity); String authorization = DatatypeConverter.printBase64Binary((authUser + ":" + authPassword).getBytes("UTF-8")); httppost.setHeader("Authorization", "Basic " + authorization); System.out.println("执行http请求-- " + httppost.getRequestLine()); response = httpclient.execute(httppost); System.out.println("http响应状态行--" + response.getStatusLine()); HttpEntity resEntity = response.getEntity(); EntityUtils.consume(resEntity); } catch (Exception e) { e.printStackTrace(); } finally { try { response.close(); httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 删除接口 */ public void deleteByFilename() { CloseableHttpClient httpclient = HttpClients.createDefault(); List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("filename", filename)); HttpGet httpGet = null; CloseableHttpResponse response = null; try { httpGet = new HttpGet(new URIBuilder(new URI(requestUrl)).setParameters(params).build()); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build(); httpGet.setConfig(requestConfig); String authorization = DatatypeConverter.printBase64Binary((authUser + ":" + authPassword).getBytes("UTF-8")); //构建Authorization请求头 httpGet.setHeader("Authorization", "Basic " + authorization); System.out.println("执行http请求-- " + httpGet.getRequestLine()); response = httpclient.execute(httpGet); System.out.println("http响应状态行--" + response.getStatusLine()); HttpEntity resEntity = response.getEntity(); EntityUtils.consume(resEntity); } catch (Exception e) { e.printStackTrace(); } finally { try { response.close(); httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } } }