├── LICENSE ├── README.md └── simple-url-shorten ├── config.lua ├── functions.lua ├── index.lua └── shorten.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 peano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-url-shorten # 2 | ## 基于lua-ngx的简单URL缩短系统 ## 3 | 4 | simple-url-shorten是一个基于openresty的Lua模块和Redis模块开发的简单、快速的网址缩短“系统”,它能够对提交的网址就行编码,缩短到指定长度的网址。Nginx、Lua、Redis的我这里就不做介绍了,但是他们都有一个共同的优点——性能高。simple-url-shorten具有如下特点: 5 | 6 | 1. 使用Nginx+lua+redis,性能非常高; 7 | 2. 具有域名黑名单、白名单,支持简单认证; 8 | 3. 支持自定义短URL长度; 9 | 4. 支持自定义短网址字符前缀、后缀 10 | 5. 使用302方式跳转,302模板可以通过修改Nginx302模板修改 11 | 6. 使用json方式返回数据 12 | 13 | 既然是简单的URL网址缩短服务,当然也会有不足之处: 14 | 15 | 1. 没有计数及分析功能:前期考虑添加计数及分析功能,但是考虑跳转的高效性,省略了计数及统计分析功能,该功能可能在以后的更新中添加; 16 | 2. 需要手工编译安装Openresty,对于不熟悉Linux软件编译安装的用户有一定的难度; 17 | 18 | ## 安装方法 ## 19 | 1. 编译安装openresty,要启用lua模块。[http://openresty.org/](http://openresty.org/ "Openresty") 20 | 2. 安装并配置redis软件 21 | 3. 下载simple-url-shorten文件,lualib(默认/usr/local/openresty/lualib)目录中新建short目录,将lua代码文件放置于目录中 22 | [root@localhost ~]# ll /usr/local/openresty/lualib/short 23 | -rw-r--r--. 1 root root 2434 Dec 22 16:44 config.lua 24 | -rw-r--r--. 1 root root 3491 Dec 22 16:44 functions.lua 25 | -rw-r--r--. 1 root root 313 Dec 22 16:13 index.lua 26 | -rw-r--r--. 1 root root 369 Dec 22 16:35 shorten.lua 27 | 28 | 4. 配置nginx.conf(文件默认位置/usr/local/openresty/nginx/conf/nginx.conf) 29 | 30 | server { 31 | 32 | .... 33 | 34 | location / { 35 | lua_socket_keepalive_timeout 30s; 36 | content_by_lua_file /usr/local/openresty/lualib/short/index.lua; 37 | } 38 | location /short { 39 | lua_socket_keepalive_timeout 30s; 40 | content_by_lua_file /usr/local/openresty/lualib/short/shorten.lua; 41 | } 42 | 43 | ... 44 | } 45 | 5. 配置simple-url-shorten 46 | 47 | ... 48 | 49 | -- 配置Redis数据库信息 50 | local redis = {} 51 | redis['host'] = '127.0.0.1' 52 | redis['port'] = 6379 53 | redis['password'] = 'passwd' 54 | 55 | 56 | -- 设置起始的短网址,根据长度可以选择需要多少数量的短网址 57 | -- 1 -> 62 58 | -- 2 -> 3844 59 | -- 3 -> 238328 60 | -- 4 -> 14776336 61 | -- 5 -> 916132832 62 | -- 6 -> 56800235584 63 | -- 7 -> 3521614606208 64 | -- 8 -> 218340105584896 65 | -- 9 -> 13537086546263552 66 | -- 10 -> 839299365868340224 67 | -- 11 -> 52036560683837093888 68 | -- 12 -> 3226266762397899821056 69 | -- 13 -> 200028539268669788905472 70 | -- 14 -> 12401769434657526912139264 71 | -- 15 -> 768909704948766668552634368 72 | -- 16 -> 47672401706823533450263330816 73 | local start_url = {'0','0','0','0'} 74 | 75 | -- 短网址前缀 76 | local prefix = '' 77 | 78 | -- 短网址后缀 79 | local suffix = '' 80 | 81 | -- 短网址的域名 82 | local domain = 'http://192.168.56.201/' 83 | 84 | -- 接受网址缩短的白名单和黑名单,白名单优先级高于黑名单; 85 | -- 黑名单中默认包含短网址的域名 86 | local white_host = {} 87 | local black_host = {domain} 88 | 89 | ...... 90 | 91 | 92 | 5. 启动nginx服务 93 | 94 | 95 | ## 使用方法 ## 96 | 97 | 想要对网址 98 | 99 | https://www.google.com/search?newwindow=1&biw=1600&bih=753&q=simple+url+shorten&oq=simple+url+shorten&gs_l=serp.3..0i19l5j0i30i19j0i5i30i19l4.17382489.17389081.0.17389560.18.18.0.0.0.0.418.2756.0j5j3j2j1.11.0.msedr...0...1c.1.60.serp..7.11.2753.th4LFd5J5uU 100 | 101 | 进行网址缩短,只需进行GET方式请求即可: 102 | 103 | Request: 104 | http://XXX.com/short?url=https%3A%2f%2fwww.google.com%2fsearch%3Fnewwindow%3D1%26biw%3D1600%26bih%3D753%26q%3Dsimple%2burl%2bshorten%26oq%3Dsimple%2burl%2bshorten%26gs_l%3Dserp.3..0i19l5j0i30i19j0i5i30i19l4.17382489.17389081.0.17389560.18.18.0.0.0.0.418.2756.0j5j3j2j1.11.0.msedr...0...1c.1.60.serp..7.11.2753.th4LFd5J5uU 105 | 106 | Response: 107 | {"status":1,"shorturl":"http://XXX.com/Rqo3F"} 108 | 109 | 访问过程: 110 | Request: 111 | http://XXX.com/Rqo3F 112 | 113 | Response: 114 | 115 | HTTP/1.1 302 Moved Temporarily 116 | Server: openresty 117 | Date: Tue, 23 Dec 2014 06:57:47 GMT 118 | Content-Type: text/html 119 | Content-Length: 154 120 | Connection: keep-alive 121 | Location: http://www.baidu.com/index.php?xxx=234 122 | 123 | 124 | 302 Found 125 | 126 |

302 Found

127 |
nginx
128 | 129 | 130 | 131 | 132 | ## 注意事项 ## 133 | 1. host白名单优先级高于黑名单,设置了白名单将只能对白名单的host网址进行网址缩短 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /simple-url-shorten/config.lua: -------------------------------------------------------------------------------- 1 | local mod_name = ... 2 | local M = {} 3 | local base_table = {'e','Q','W','3','s','5','R','J','a','g','M','T','7','n','O','X','8','f','2','o','z','w','G','K','x','D','Z','F','b','u','N','L','S','l','Y','V','I','t','4','q','6','A','y','1','i','B','C','c','P','0','U','r','k','j','m','p','9','h','v','d','E','H'} 4 | 5 | -- Base table for short URL string 6 | -- You can use random table as this table 7 | -- local base_table = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} 8 | 9 | -- Base string of Base table 10 | local base_str = table.concat( base_table, "") 11 | 12 | -- analysis switch 13 | -- local analysis = 1 14 | 15 | -- Redis database config 16 | local redis = {} 17 | redis['host'] = '127.0.0.1' 18 | redis['port'] = 6379 19 | redis['password'] = 'passwd' 20 | 21 | 22 | -- Initial Short URL 23 | -- The first Short URL,will no use 24 | -- The length depens on the table's numbers 25 | -- chars length and the numbers of URL are following 26 | -- 1 -> 62 27 | -- 2 -> 3844 28 | -- 3 -> 238328 29 | -- 4 -> 14776336 30 | -- 5 -> 916132832 31 | -- 6 -> 56800235584 32 | -- 7 -> 3521614606208 33 | -- 8 -> 218340105584896 34 | -- 9 -> 13537086546263552 35 | -- 10 -> 839299365868340224 36 | -- 11 -> 52036560683837093888 37 | -- 12 -> 3226266762397899821056 38 | -- 13 -> 200028539268669788905472 39 | -- 14 -> 12401769434657526912139264 40 | -- 15 -> 768909704948766668552634368 41 | -- 16 -> 47672401706823533450263330816 42 | local start_url = {'0','0','0','0'} 43 | 44 | -- Short URL's prefix 45 | -- Default is NULL 46 | local prefix = '' 47 | 48 | -- Short URL's suffix 49 | local suffix = '' 50 | 51 | -- Domain for short URL 52 | -- if is empty,will return short string 53 | local domain = 'http://192.168.56.201/' 54 | 55 | -- White list of domain 56 | -- Host just as: www.example.com:8081 57 | -- if you set white_host,this tool is only allow the url in the white list host 58 | local white_host = {} 59 | local black_host = {domain} 60 | 61 | -- Errors 62 | local err_msg = {} 63 | err_msg[34] = 'URL has existed' 64 | err_msg[41] = 'URL is invalid' 65 | err_msg[42] = 'URL is not allow to shorten' 66 | err_msg[51] = 'Database is out of service' 67 | err_msg[52] = 'Getting data error' 68 | err_msg[53] = 'Larger than maximum numbers of URLs' 69 | 70 | M['base_table'] = base_table 71 | M['base_str'] = base_str 72 | M['prefix'] = prefix 73 | M['start_url'] = start_url 74 | M['suffix'] = suffix 75 | M['white_host'] = white_host 76 | M['black_host'] = black_host 77 | M['err_msg'] = err_msg 78 | M['redis'] = redis 79 | M['domain'] = domain 80 | 81 | 82 | return M 83 | -------------------------------------------------------------------------------- /simple-url-shorten/functions.lua: -------------------------------------------------------------------------------- 1 | local mod_name = (...) 2 | local config = require('short/config') 3 | local cjson = require('cjson') 4 | local redis = require ("resty.redis") 5 | local M = {} 6 | 7 | function M.find_in_table(table, value) 8 | if table == ngx.null or #table==0 then 9 | return false 10 | end 11 | for k,v in pairs(table) do 12 | if v == value then 13 | return true 14 | end 15 | end 16 | return false 17 | end 18 | 19 | -- get url details 20 | function M.get_url_details(long_url) 21 | if long_url==nil or long_url=='' or #long_url>2048 then 22 | return nil, 41 23 | end 24 | local result = {} 25 | local matches = ngx.re.match(long_url, '^(http|https|ftp)://([^/]+)((/[^\\\\?#]+)([^#]+)?(.+))?') 26 | if matches[2] then 27 | result['protocol'] = matches[1] 28 | result['host'] = matches[2] 29 | else 30 | return nil,41 31 | end 32 | if #config['white_host'] ~= 0 then 33 | if M.find_in_table(config['white_host'], result['host']) then 34 | return result 35 | else 36 | return nil, 42 37 | end 38 | end 39 | if M.find_in_table(config['black_host'], result['host']) then 40 | return nil, 42 41 | end 42 | return result 43 | end 44 | 45 | -- connect redis database 46 | function M.redis_connect() 47 | local red = redis:new() 48 | red:set_timeout(1000) 49 | local ok, err = red:connect(config['redis']['host'], config['redis']['port']) 50 | if not ok then 51 | return nil, 51 52 | end 53 | local res, err = red:auth(config['redis']['password']) 54 | if not res then 55 | return nil, 51 56 | end 57 | return red 58 | end 59 | 60 | function M.get_short_string(last) 61 | local result = {} 62 | local str = '' 63 | if last ~= ngx.null then 64 | str = string.sub(last, #config['prefix']+1, #last-#config['suffix']) 65 | for k in string.gmatch(str, "([0-9a-zA-Z])") do 66 | table.insert(result, k) 67 | end 68 | else 69 | str = table.concat(config['start_url'],'') 70 | result = config['start_url'] 71 | end 72 | for i=#str, 1, -1 do 73 | local char = string.sub(str, i, i) 74 | 75 | local pos = string.find(config['base_str'], char) 76 | 77 | local pos_start = string.find(config['base_str'], config['start_url'][i]) 78 | 79 | if pos == #config['base_str'] then 80 | pos =0 81 | end 82 | 83 | result[i] = config['base_table'][pos+1] 84 | if pos_start ~= pos+1 then 85 | break 86 | elseif i == 1 then 87 | return nil, 53 88 | end 89 | end 90 | 91 | return config['prefix'].. table.concat(result,'') .. config['suffix'] 92 | end 93 | 94 | function M.get_long_url(short_string) 95 | if ngx.re.find(short_string, '[^0-9a-zA-Z]') then 96 | return false,41 97 | end 98 | local red, err = M.redis_connect() 99 | if err then 100 | return false, err 101 | end 102 | local result, err = red:get('S_' .. short_string) 103 | if err then 104 | return nil, 52 105 | end 106 | red:set_keepalive(10000, 100) 107 | if result ~= ngx.null then 108 | return result 109 | else 110 | return nil, 52 111 | end 112 | end 113 | 114 | function M.show_error(err_code, long_url) 115 | local result = {} 116 | result['status'] = 0 117 | result['error'] = err_code 118 | result['msg'] = config['err_msg'][err_code] 119 | if long_url then 120 | result['url'] = long_url 121 | end 122 | ngx.say(cjson.encode(result)) 123 | ngx.exit(ngx.HTTP_OK) 124 | end 125 | 126 | function M.url_create(long_url, short_string) 127 | local red, err = M.redis_connect() 128 | if err then 129 | return false, err 130 | end 131 | local url_md5 = ngx.md5(long_url) 132 | local result, err = red:get('M_' .. url_md5) 133 | if err then 134 | return nil, 52 135 | end 136 | if result ~= ngx.null then 137 | return config['domain']..result 138 | end 139 | local url_details, err = M.get_url_details(long_url) 140 | if err then 141 | return nil, err 142 | end 143 | local last, err = red:get('V_last') 144 | if err or last==ngx.NULL then 145 | return nil, 52 146 | end 147 | local short_string, err = M.get_short_string(last) 148 | if err then 149 | return nil, err 150 | end 151 | red:set('M_' .. url_md5, short_string) 152 | red:set('V_last', short_string) 153 | red:set('S_' .. short_string, long_url) 154 | red:set_keepalive(10000, 100) 155 | return config['domain']..short_string 156 | end 157 | 158 | 159 | return M 160 | -------------------------------------------------------------------------------- /simple-url-shorten/index.lua: -------------------------------------------------------------------------------- 1 | ngx.header.content_type = 'text/html' 2 | local config = require('short/config') 3 | local functions = require('short/functions') 4 | local short_string = string.sub(ngx.var.uri, -#config['start_url']) 5 | local long_url, err = functions.get_long_url(short_string) 6 | if err then 7 | functions.show_err(err) 8 | end 9 | ngx.redirect(long_url) 10 | -------------------------------------------------------------------------------- /simple-url-shorten/shorten.lua: -------------------------------------------------------------------------------- 1 | ngx.header.content_type = 'text/json' 2 | 3 | local functions = require('short/functions') 4 | -- local cjson = require('cjson') 5 | local args = ngx.req.get_uri_args() 6 | 7 | local long_url = args['url'] 8 | local short_string = args['short'] 9 | local short_url, err = functions.url_create(long_url) 10 | if err then 11 | functions.show_error(err) 12 | end 13 | ngx.say('{"status":1,"shorturl":"'..short_url..'"}') 14 | --------------------------------------------------------------------------------