├── fxmanifest.lua ├── index.lua ├── readme.md ├── server.lua └── static ├── img.png ├── index.html └── style.css /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'common' 3 | 4 | server_scripts { 5 | 'server.lua', 6 | 'index.lua' 7 | } 8 | -------------------------------------------------------------------------------- /index.lua: -------------------------------------------------------------------------------- 1 | local visits = 0 2 | 3 | STATIC_FOLDER = 'static/' 4 | 5 | FILE_CACHE = { 6 | ['/hello.txt'] = 'Hello world', -- page is cached with this preset data 7 | ['/style.css'] = true -- page cached on first request 8 | } 9 | 10 | function Log (...) 11 | -- misc.flogisoft.com/bash/tip_colors_and_formatting 12 | -- stackoverflow.com/a/1718607 13 | print('\27[32m['.. GetCurrentResourceName() ..']\27[0m', ...) 14 | end 15 | 16 | ROUTES = { 17 | ['/404.html'] = function (req, res) 18 | res.writeHead(404) 19 | res.send('404: Page not found.') 20 | end, 21 | 22 | ['/visitors'] = function (req, res) 23 | visits = visits + 1 24 | 25 | local body = req.body 26 | local query = req.query 27 | local name = 'Unknown' 28 | 29 | if body and req.method == 'POST' then 30 | name = body.name 31 | end 32 | 33 | res.writeHead(200) 34 | 35 | -- res.writeHead(200, { 36 | -- ["Access-Control-Allow-Origin"] = "*", 37 | -- ["X-Frame-Options"] = "allowall", 38 | -- ["X-Powered-By"] = "FiveM" 39 | -- }) 40 | 41 | res.send(json.encode({ 42 | visitors = visits, 43 | message = 'Hello ' .. ((query and query.name) or name) .. ', there have been ' .. visits .. ' visitors.' 44 | })) 45 | end 46 | } 47 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### /http-server 3 | 4 | http-server resource for fivem. 5 | 6 | [![button](https://github.com/throwarray/fivem-http-server/raw/master/static/img.png)](https://forum.fivem.net/ "FiveM Forum") 7 | 8 | #### Usage 9 | 10 | `http://127.0.0.1:30120/http-server/index.html` 11 | 12 | `http://127.0.0.1:30120/http-server/visitors?name=Bob` 13 | 14 | 15 | __static/__ Static assets 16 | 17 | __index.lua__ Server routes and cache 18 | -------------------------------------------------------------------------------- /server.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | 3 | -- Defaults 4 | 5 | STATIC_FOLDER = 'static/' 6 | 7 | ROUTES = { } 8 | 9 | FILE_CACHE = { } 10 | 11 | PRINT_NAME = '\27[32m['.. GetCurrentResourceName() ..']\27[0m' 12 | 13 | function Log (...) 14 | print(PRINT_NAME, ...) 15 | end 16 | 17 | -- Decode query string 18 | local function _decodeQuery1 (h) 19 | return string.char(tonumber(h, 16)) 20 | end 21 | 22 | local function _decodeQuery (s) 23 | s = s:gsub('+', ' '):gsub('%%(%x%x)', _decodeQuery1) 24 | 25 | return s 26 | end 27 | 28 | function DecodeQueryParams (str) 29 | local query = {} 30 | 31 | for k,v in str:gmatch('([^&=?]-)=([^&=?]+)' ) do 32 | query[k] = _decodeQuery(v) 33 | end 34 | 35 | return query 36 | end 37 | 38 | -- Split the path and query string 39 | function SplitQueryString (s) 40 | local url = { 41 | path = s; 42 | query = nil; 43 | } 44 | 45 | for k,v in s:gmatch('(.+)?(.+)') do 46 | url.path = k 47 | url.query = v 48 | 49 | break 50 | end 51 | 52 | return url 53 | end 54 | 55 | -------------------------------------------------------------------------------- 56 | 57 | function PageNotFound (req, res, ...) 58 | local handler = ROUTES['/404.html'] 59 | 60 | -- Try 404 from ROUTES 61 | if handler then handler(req, res, ...) 62 | 63 | -- Generic 404 handler 64 | else 65 | res.writeHead(404) 66 | res.send('Not found.') 67 | end 68 | end 69 | 70 | -- Serve /static file 71 | function SendFile (req, res, ...) 72 | -- Send from cache 73 | 74 | local filepath = req.path 75 | local cached = FILE_CACHE[filepath] 76 | 77 | if cached and cached ~= true then 78 | Log('serve from cache', filepath) 79 | 80 | res.send(FILE_CACHE[filepath]) 81 | 82 | return 83 | end 84 | 85 | -- Load the file 86 | local data 87 | local file = io.open(GetResourcePath(GetCurrentResourceName()) .. '/' .. STATIC_FOLDER .. filepath, "r") 88 | 89 | if file then 90 | res.writeHead(200) 91 | io.input(file) 92 | data = io.read("*a") 93 | io.close(file) 94 | end 95 | 96 | -- Is the file being cached 97 | if cached == true then 98 | Log('save to cache', filepath) 99 | FILE_CACHE[filepath] = data 100 | end 101 | 102 | -- 404 103 | if not data then 104 | Log('serve 404', filepath) 105 | PageNotFound(req, res, ...) 106 | 107 | -- Send the file 108 | else 109 | res.send('' .. data) 110 | end 111 | end 112 | 113 | -- Handle dynamic route 114 | function HandleRoute (req, res, ...) 115 | local extra = { ... } 116 | 117 | Log('handle request as', req.path, req.query) 118 | 119 | if req.query ~= nil then 120 | -- Parse request query params for all routes 121 | req.query = DecodeQueryParams(req.query) 122 | end 123 | 124 | if req.method == 'POST' then 125 | -- Parse request body for post requests 126 | req.setDataHandler(function (body) 127 | req.body = json.decode(body) 128 | 129 | -- Handle route 130 | ROUTES[req.path](req, res, table.unpack(extra)) 131 | end) 132 | else 133 | ROUTES[req.path](req, res, table.unpack(extra)) 134 | end 135 | end 136 | 137 | -- Incomming request 138 | SetHttpHandler(function (req, res) 139 | -- Trim the query 140 | local url = SplitQueryString(req.path) 141 | 142 | req.path = url.path 143 | req.query = url.query 144 | 145 | -- Sanitizes path by removing any '..', preventing access to files outside 146 | -- the STATIC_FOLDER. 147 | req.path = req.path:gsub("%.%.", "") 148 | 149 | -- Log incomming request 150 | Log('incomming request for', req.path, req.query) 151 | 152 | -- Handle dynamic routes 153 | if ROUTES[req.path] then HandleRoute(req, res) return end 154 | 155 | -- Special case handle / is /index.html 156 | if req.path == '/' then req.path = '/index.html' end 157 | 158 | if ROUTES[req.path] then HandleRoute(req, res) 159 | 160 | -- Handle the request as static serve 161 | else 162 | Citizen.CreateThread(function () 163 | Wait(0) 164 | SendFile(req, res) 165 | end) 166 | end 167 | end) 168 | -------------------------------------------------------------------------------- /static/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/throwarray/fivem-http-server/fd945ed37ddc6ca1c05b5ebe3443e096099103be/static/img.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | /HOME 10 |
11 | LOADING ... 12 |
13 | 16 | 17 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: blue; 3 | margin: 0; 4 | color: white; 5 | } 6 | --------------------------------------------------------------------------------