Compare commits

...

17 Commits

Author SHA1 Message Date
Siggi
ca836f1944
Lfs fix (#131)
* added
- usage info for LFS targets
- lfs.img build rule (requires luac.cross from nodemcu-firmware)
- basic autodetection for USB serial device

* updated LFS code for recent nodemcu versions

* safe default (SOFTAP)

* Revert "safe default (SOFTAP)"

This reverts commit a76db2a153f421cc81e9c1caf0903d550043b1d6.

* removed compatibility code

* updated nodemcu-firmware requirement

Co-authored-by: langausd <langausd@github.com>
2021-04-10 15:03:41 -05:00
seregaxvm
8d5620a5c9 edit Makefile (#128) 2019-11-19 16:10:37 -06:00
seregaxvm
6511dad8df allow running server from LFS (#125) 2019-11-18 13:32:47 -06:00
Manoël Trapier
6c7e451663 Change text/plain to application/octet-stream (#126) 2019-11-14 12:01:01 -06:00
seregaxvm
f13a10a01f reformat html (#124) 2019-11-14 11:59:54 -06:00
Gregor Hartmann
ca4fb20c00 Serve static pages efficiently. Fix #53 (#118)
* 1st draft to serve static files faster

* Allow serving 5 images in a page

the 6th image cannot be served as the esp does not open more than 5 connections at the same time

* win the prize

* Update comments
2019-11-14 11:58:56 -06:00
Fractal147
9f4d7a9988 Fixed unnecessary globals as in issue #113. (#122)
* Fixed global assignment that should be local

Made result variable be local, see Issue #113

* Made global variable local

Made ASCII variable be local, see Issue #113

* Made more variables local

Related to Issue #113. questionMarkPos, and b,c,d,e,f all are global in scope, and are not cleared from memory, so leak.
Frankly, b, c, d, e, and f are not used either, but will now get GC'd later, if they ever were assigned, so not problematic
line 114 also has _ and i to make local too, so were put on their own line.
i on line 24 also was unnecessarily global, and undetected in issue #113

* Made module more local

Made the basicAuth table local in scope. Since it is returned when dofile is called in httpserver.lua, that already has a correctly scoped table, 'auth'. This is related to issue #113, and should reduce memory loss to globals

* Made bufferedConnection local

bufferedConnection was global and didn't have to be. Part of issue #113.
Now no longer remains in _G (globals table) after a connection has closed.
2018-01-14 22:03:37 -06:00
Gregor Hartmann
7c8fe9c164 Upload for all files fix 119 (#120)
* allow uploading files not only to http + replace symbol images by unicode symbols

allow uploading files to root dir and http.
+ replace symbol images by unicode symbols

* backend for uploading arbitrary files

* fix uploading to http
2018-01-14 22:02:51 -06:00
Gregor Hartmann
5cf303de79 make cars dynamic to load 1 to 6 cars (#114)
For lazy people like me for not having to change the page by hand every
time.
+ adding 2 more cars.
2017-10-02 11:15:12 -05:00
Manoël Trapier
ab9a5d365f Use file.exists instead of open/close when relevant (#112) 2017-10-02 11:14:26 -05:00
Gregor Hartmann
6158d4b5ba support changes json API (#116)
use sjson instead of cjson
Also take care of backward compatibility before 2.1
2017-10-02 11:13:39 -05:00
Gregor Hartmann
c67ed16e10 Support long filenames in upload (#115)
appending '.dnl' to the filename shortens the available filename length by 4 chars. Now a long filename ist cut off first and then the dnl is appended resulting in a unique but short enough temp filename.
2017-10-02 08:50:47 -05:00
Dmitry
a70fa745b0 Small fix to upload form (#117)
Fixed bug when you cannot browse or D&D same file multiple times without reloading the whole page.
2017-10-02 08:49:49 -05:00
Manoël Trapier
b5baf6cd48 Fix mistake in config file (#107) 2017-09-04 20:42:47 -05:00
Manoël Trapier
28fbb6e7a7 now user proper table based parameter for wifi.ap.setip instead of a string (#106) 2017-09-03 17:40:38 -05:00
Gerhard Schwärzler
0f852665ec WiFi configuration using new, table based API (#105) 2017-07-26 16:25:14 -05:00
Marcos Kirsch
dacaf31dc6 Get README.md is up to date. 2017-07-02 22:40:40 -05:00
32 changed files with 1229 additions and 860 deletions

View File

@ -3,18 +3,27 @@
###################################################################### ######################################################################
# Path to nodemcu-uploader (https://github.com/kmpm/nodemcu-uploader) # Path to nodemcu-uploader (https://github.com/kmpm/nodemcu-uploader)
NODEMCU-UPLOADER=../nodemcu-uploader/nodemcu-uploader.py NODEMCU-UPLOADER?=python ../nodemcu-uploader/nodemcu-uploader.py
# Path to LUA cross compiler (part of the nodemcu firmware; only needed to compile the LFS image yourself)
LUACC?=../nodemcu-firmware/luac.cross
# Serial port # Serial port
PORT=/dev/cu.SLAB_USBtoUART PORT?=$(shell ls /dev/cu.SLAB_USBtoUART /dev/ttyUSB* 2>/dev/null|head -n1)
SPEED=115200 SPEED?=115200
NODEMCU-COMMAND=$(NODEMCU-UPLOADER) -b $(SPEED) --start_baud $(SPEED) -p $(PORT) upload define _upload
@$(NODEMCU-UPLOADER) -b $(SPEED) --start_baud $(SPEED) -p $(PORT) upload $^
endef
###################################################################### ######################################################################
LFS_IMAGE ?= lfs.img
HTTP_FILES := $(wildcard http/*) HTTP_FILES := $(wildcard http/*)
LUA_FILES := $(wildcard *.lua) WIFI_CONFIG := $(wildcard *conf*.lua)
SERVER_FILES := $(filter-out $(WIFI_CONFIG), $(wildcard srv/*.lua) $(wildcard *.lua))
LFS_FILES := $(LFS_IMAGE) $(filter-out $(WIFI_CONFIG), $(wildcard *.lua))
FILE ?=
# Print usage # Print usage
usage: usage:
@ -22,21 +31,40 @@ usage:
@echo "make upload_http to upload files to be served" @echo "make upload_http to upload files to be served"
@echo "make upload_server to upload the server code and init.lua" @echo "make upload_server to upload the server code and init.lua"
@echo "make upload_all to upload all" @echo "make upload_all to upload all"
@echo $(TEST) @echo "make upload_lfs to upload lfs based server code"
@echo "make upload_all_lfs to upload all (LFS based)"
# Upload one files only # Upload one file only
upload: upload: $(FILE)
@python $(NODEMCU-COMMAND) $(FILE) $(_upload)
# Upload HTTP files only # Upload HTTP files only
upload_http: $(HTTP_FILES) upload_http: $(HTTP_FILES)
@python $(NODEMCU-COMMAND) $(foreach f, $^, $(f)) $(_upload)
# Upload httpserver lua files (init and server module) # Upload httpserver lua files
upload_server: $(LUA_FILES) upload_server: $(SERVER_FILES)
@python $(NODEMCU-COMMAND) $(foreach f, $^, $(f)) $(_upload)
# Upload all # Upload wifi configuration
upload_all: $(LUA_FILES) $(HTTP_FILES) upload_wifi_config: $(WIFI_CONFIG)
@python $(NODEMCU-COMMAND) $(foreach f, $^, $(f)) $(_upload)
# Upload lfs image
upload_lfs: $(LFS_FILES)
$(_upload)
$(LFS_IMAGE):
$(LUACC) -f -o $(LFS_IMAGE) srv/*.lua
# Upload all non-lfs files
upload_all: $(HTTP_FILES) $(SERVER_FILES) $(WIFI_CONFIG)
$(_upload)
# Upload all lfs files
upload_all_lfs: $(HTTP_FILES) $(LFS_FILES) $(WIFI_CONFIG)
$(_upload)
.ENTRY: usage
.PHONY: usage upload_http upload_server upload_wifi_config \
upload_lfs upload_all upload_all_lfs

117
README.md
View File

@ -7,16 +7,16 @@ A (very) simple web server written in Lua for the ESP8266 running the NodeMCU fi
> you are really abusing its intended purpose. When it comes to scoping your ESP8266 > you are really abusing its intended purpose. When it comes to scoping your ESP8266
> applications, the adage Keep It Simple Stupid truly applies. > applications, the adage Keep It Simple Stupid truly applies.
> >
> -- <cite>[Terry Ellison](https://github.com/TerryE)</cite>, nodemcu-firmware maintainer, > -- <cite>[Terry Ellison](https://github.com/TerryE)</cite>, nodemcu-firmware maintainer
Let the abuse begin. Let the abuse begin.
## Features ## Features
* GET, POST, PUT and minor changes to support other methods * GET, POST, PUT (other methods can be supported with minor changes)
* Multiple MIME types * Multiple MIME types
* Error pages (404 and others) * Error pages (404 and others)
* Server-side execution of Lua scripts * *Server-side execution of Lua scripts*
* Query string argument parsing with decoding of arguments * Query string argument parsing with decoding of arguments
* Serving .gz compressed files * Serving .gz compressed files
* HTTP Basic Authentication * HTTP Basic Authentication
@ -24,7 +24,9 @@ Let the abuse begin.
## How to use ## How to use
1. Upload server files using [nodemcu-uploader](https://github.com/kmpm/nodemcu-uploader). 1. Modify your local copy of the configuration file httpserver-conf.lua.
2. Upload server files using [nodemcu-uploader](https://github.com/kmpm/nodemcu-uploader).
The easiest is to use GNU Make with the bundled Makefile. Open the Makefile and modify the The easiest is to use GNU Make with the bundled Makefile. Open the Makefile and modify the
user configuration to point to your nodemcu-uploader script and your serial port. user configuration to point to your nodemcu-uploader script and your serial port.
Type the following to upload the server code, init.lua (which you may want to modify), Type the following to upload the server code, init.lua (which you may want to modify),
@ -32,52 +34,50 @@ Let the abuse begin.
make upload_all make upload_all
If you only want to upload the server code, then type: If you only want to upload just the server code, then type:
make upload_server make upload_server
And if you only want to upload the server files: If you only want to update wifi configuration, type:
make upload_wifi_config
And if you only want to upload just the files that can be served:
make upload_http make upload_http
Restart the server. This will execute init.lua which will compile the server code. Restart the server. This will execute included init.lua which will compile the server code,
Then, assuming init.lua doesn't have it, start the server yourself by typing: configure WiFi, and start the server.
dofile("httpserver.lc")(80) 3. Want to serve your own files? Put them under the http/ folder and upload to the chip.
For example, assuming you want to serve myfile.html, upload by typing:
In this example, 80 is the port your server is listening at, but you can change it. make upload FILE:=http/myfile.html
2. Want to upload your own files? Move them to the http/ folder. Be careful though, Notice that while NodeMCU's filesystem does not support folders, filenames *can* contain slashes.
the flash memory seems to fill up quickly and get corrupted. We take advantage of that and only files that begin with "http/" will be accessible through the server.
All the files you upload must be prefixed with "http/". Wait, what?
Yes: NodeMCU's filesystem does not support folders, but filenames *can* contain slashes.
Only files that begin with "http/" will be accessible through the server.
3. Visit your server from a web browser. 3. Visit your server from a web browser.
__Example:__ Say the IP for your ESP8266 is 2.2.2.2 and the server is __Example:__ Say the IP for your ESP8266 is 2.2.2.2 and the server is
running in the default port 80. Go to (http://2.2.2.2/index.html)[http://2.2.2.2/index.html] using your web browser. running in the default port 80. Go to <http://2.2.2.2/index.html> using your web browser.
The ESP8266 will serve you with the contents of the file "http/index.html" (if it exists). If you visit the root (/) The ESP8266 will serve you with the contents of the file "http/index.html" (if it exists). If you visit the root (/)
then index.html is served. By the way, unlike most HTTP servers, nodemcu_httpserver treats the URLs in a then index.html is served. By the way, unlike most HTTP servers, nodemcu_httpserver treats the URLs in a
case-sensitive manner. case-sensitive manner.
4. How to use HTTP Basic Authentication. ## HTTP Basic Authentication.
Modify variables in configuration file httpserver-conf.lua in order to enable and to configure usernames/passwords. It's supported. Turn it on in httpserver-conf.lua.
See comments in that file for more details.
When enabled, HTTP Basic Authentication is global to every file served by the server. Use it with care and don't fall into a false sense of security: HTTP Basic Authentication should not be
considered secure since the server is not using encryption. Username and passwords travel
in the clear.
Remember that HTTP Basic Authentication is a very basic authentication protocol, and should not be ## Server-side scripting using your own Lua scripts
considered as secure since the server is not using encryption. Username and passwords travel
in plain text.
## How to use server-side scripting using your own Lua scripts Yes, you can upload your own Lua scripts! This is pretty powerful.
Just put it under http/ and upload it. Make sure it has a .lua extension.
Similar to static files, upload a Lua script called "http/[name].lua where you replace [name] with your script's name. Your script should return a function that takes three parameters:
The script should return a function that takes three parameters:
return function (connection, req, args) return function (connection, req, args)
-- code goes here -- code goes here
@ -92,6 +92,20 @@ Let the abuse begin.
For example, if the client requests _http://2.2.2.2/foo.lua?color=red_ then the server will execute the function For example, if the client requests _http://2.2.2.2/foo.lua?color=red_ then the server will execute the function
in your Lua script _foo.lua_ and pass in _connection_ and _args_, where _args.color = "red"_. in your Lua script _foo.lua_ and pass in _connection_ and _args_, where _args.color = "red"_.
## [LFS](https://nodemcu.readthedocs.io/en/master/lfs/) support.
NodeMCU allows to run Lua scripts from LFS in order to save RAM resources.
*nodemcu-httpserver* makes it easy to move your code to LFS.
In order to run *nodemcu-httpserver* from LFS:
1. move your code to `srv` folder (if you want it to be included in LFS image)
1. Compile contents of `srv` into LFS image. There's a [cloud service](https://blog.ellisons.org.uk/article/nodemcu/a-lua-cross-compile-web-service/) and [docker image](https://github.com/marcelstoer/docker-nodemcu-build) that will help you with that.
1. Upload image file under `lfs.img` name. You may use Makefile rules `upload_lfs` and `upload_all_lfs` for this.
1. Reboot you NodeMCU. Init script will pick up image and apply it for you.
### Example: Garage door opener ### Example: Garage door opener
#### Purpose #### Purpose
@ -107,42 +121,40 @@ Let the abuse begin.
#### Hardware description #### Hardware description
This example assumes that GPIO1 and GPIO2 on the ESP8266 are connected each to a relay This example assumes that you are using a [Wemos D1 Pro](https://wiki.wemos.cc/products:d1:d1_mini_pro)
that can be controlled. How to wire such thing is outside of the scope of this document with two relay shields and two reed switches.
[but information is easily found online](https://www.google.com/search?q=opening+a+garage+door+with+a+microcontroller).
The relays are controlled by the microcontroller and act as the push button, The relays are controlled by the microcontroller and act as the push button,
and can actually be connected in parallel with the existing mechanical button. and can actually be connected in parallel with the existing mechanical button.
The switches are wired so that the ESP8266 can tell whether the doors are open
or closed at any given time.
#### Software description #### Software description
This example consists of the following files: This example consists of the following files:
* **garage_door_opener.html**: Static HTML displays a button with a link * **garage_door.html**: Static HTML displays a form with all options for controlling the
to the garage_door_opener.lua script. That's it! two garage doors.
* **garage_door_opener.css**: Provides styling for garage_door_opener.html * **garage_door_control.html**: Looks like a garage door remote, how neat!
just so it looks pretty. * **garage_door_control.css**: Provides styling for garage_door_control.html.
* **garage_door_opener.lua**: Does the actual work. The script first sends * **garage_door.lua**: Does the actual work. The script performs the desired action on
a little javascript snippet to redirect the client back to garage_door_opener.html the requested door and returns the results as JSON.
and then toggles the GPIO2 line for a short amount of time (roughly equivalent to
the typical button press for opening a garage door) and then toggles it back.
* **apple-touch-icon.png**: This is optional. Provides an icon that * **apple-touch-icon.png**: This is optional. Provides an icon that
will be used if you "Add to Home Screen" the demo on an iPhone. Now it looks like an app! will be used if you "Add to Home Screen" garage_door_control.html on an iPhone.
Now it looks like an app!
#### Security implications #### Security implications
Be careful permanently installing something like this in your home. The server provides Be careful permanently installing something like this in your home. The server provides
no encryption. Your only layers of security are the WiFi network's password and simple no encryption. Your only layers of security are the WiFi network's password and simple
HTTP authentication which sends your password unencrypted. HTTP authentication (if you enable it) which sends your password unencrypted.
This script is provided simply as an educational example. You've been warned. This script is provided for educational purposes. You've been warned.
## Not supported ## Not supported
* Other methods: HEAD, DELETE, TRACE, OPTIONS, CONNECT, PATCH * Other methods: HEAD, DELETE, TRACE, OPTIONS, CONNECT, PATCH
* Encryption / SSL * Encryption / SSL
* Multiple users (HTTP Basic Authentication) * Old nodemcu-firmware versions prior to January 2021) because I don't bother to test them.
* Only protect certain directories (HTTP Basic Authentication)
* nodemcu-firmware versions older 1.5.1 (January 2016) because that's what I tested on.
## Contributing ## Contributing
@ -151,7 +163,9 @@ Let the abuse begin.
and that you add examples for new features. I won't test all your changes myself but I and that you add examples for new features. I won't test all your changes myself but I
am very grateful of improvements and fixes. Open issues in GitHub too, that's useful. am very grateful of improvements and fixes. Open issues in GitHub too, that's useful.
Please follow the coding style as close as possible: Please keep your PRs focused on one thing. I don't mind lots of PRs. I mind PRs that fix multiple unrelated things.
Follow the coding style as closely as possible:
* No tabs, indent with 3 spaces * No tabs, indent with 3 spaces
* Unix (LF) line endings * Unix (LF) line endings
@ -163,8 +177,9 @@ Let the abuse begin.
The chip is very, very memory constrained. The chip is very, very memory constrained.
* Use a recent nodemcu-firmware with as few optional modules as possible. * Use a recent nodemcu-firmware. They've really improved memory usage and fixed leaks.
* Use a firmware build without floating point support. This takes up a good chunk of RAM as well. * Use only the modules you need.
* Any help reducing the memory needs of the server without crippling its functionality is appreciated! * Use a firmware build without floating point support if you can.
* Compile your Lua scripts in order to reduce their memory usage. The server knows to serve and treat * Any help reducing the memory needs of the server without crippling its functionality is much appreciated!
* Compile your Lua scripts in order to reduce their memory usage. The server knows to serve
both .lua and .lc files as scripts. both .lua and .lc files as scripts.

BIN
http/cars-bugatti.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
http/cars-mercedes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,4 +1,4 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta http-equiv="content-type" content="text/html; charset=utf-8">
@ -17,5 +17,3 @@
<figure><img src="cars-porsche.jpg" /><figcaption>Porsche</figcaption></figure> <figure><img src="cars-porsche.jpg" /><figcaption>Porsche</figcaption></figure>
</body> </body>
</html> </html>

55
http/cars.lua Normal file
View File

@ -0,0 +1,55 @@
return function (connection, req, args)
local function showCars(nr)
if not nr then return end
connection:send([===[<figure><img src="cars-ferrari.jpg" /><figcaption>Ferrari</figcaption></figure>]===])
if nr == "1" then return end
connection:send([===[<figure><img src="cars-lambo.jpg" /><figcaption>Lamborghini</figcaption></figure>]===])
if nr == "2" then return end
connection:send([===[<figure><img src="cars-mas.jpg" /><figcaption>Maserati</figcaption></figure>]===])
if nr == "3" then return end
connection:send([===[<figure><img src="cars-porsche.jpg" /><figcaption>Porsche</figcaption></figure>]===])
if nr == "4" then return end
connection:send([===[<figure><img src="cars-bugatti.jpg" /><figcaption>Bugatti</figcaption></figure>]===])
if nr == "5" then return end
connection:send([===[<figure><img src="cars-mercedes.jpg" /><figcaption>Mercedes</figcaption></figure>]===])
end
dofile("httpserver-header.lc")(connection, 200, 'html')
connection:send([===[
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Nice cars</title>
</head>
<body>
<h1>Nice cars!</h1>
<p>
This page loads "large" images of fancy cars. It is meant to serve as a stress test for nodemcu-httpserver.<br>
It works with three embedded images of cars, but the server crashes with four. Select the number of cars you want to see below.<br>
Whoever manages to modify nodemcu-httpserver to load all four images without crashing wins a prize!
</p>
<p>
OK I guess I win the prize, as now you can load five cars.<br>
Cheers HHHartmann
</p>
<p>
choose: <a href="?n=1">show one car</a>
<a href="?n=2">show two cars</a>
<a href="?n=3">show three cars</a>
<a href="?n=4">show four cars</a>
<a href="?n=5">show five cars</a>
<a href="?n=6">show six cars</a>
</p>
]===])
showCars(args.n)
connection:send([===[
</body>
</html>
]===])
end

View File

@ -1,24 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<title>Counter</title>
<script> <script>
document.addEventListener("DOMContentLoaded", function () document.addEventListener("DOMContentLoaded", function ()
{ {
var number = window.location.search; var number = window.location.search;
if ( number == '') number = 0; if ( number == '') number = 0;
else number = number.substring(1,); else number = number.substring(1,);
number = parseInt(number) + 1; number = parseInt(number) + 1;
document.getElementById('count').innerHTML = number; document.getElementById('count').innerHTML = number;
window.location.search = '?' + number; window.location.search = '?' + number;
} })
)
</script> </script>
</head> </head>
<body> <body>
<br>This page reloads itself as fast as it can to test the server.</br> <br>This page reloads itself as fast as it can to test the server.<br>
<br>It is meant as a stress test to see when and if the server fails.</br> <br>It is meant as a stress test to see when and if the server fails.<br>
<h1 id="count" class="countclass"> <h1 id="count" class="countclass">-</h1>
</h1>
</body> </body>
</html> </html>

View File

@ -16,7 +16,7 @@ return function (connection, req, args)
connection:send("<b>Flash Address: </b> " .. flashAddress .. " bytes<br/>\n" .. connection:send("<b>Flash Address: </b> " .. flashAddress .. " bytes<br/>\n" ..
"<b>Flash Size: </b> " .. flashSize .. " bytes<br/>\n") "<b>Flash Size: </b> " .. flashSize .. " bytes<br/>\n")
connection:send("<p>\n<b>Files:</b><br/>\n<ul>\n") connection:send("<b>Files:</b><br/>\n<ul>\n")
for name, size in pairs(file.list()) do for name, size in pairs(file.list()) do
local isHttpFile = string.match(name, "(http/)") ~= nil local isHttpFile = string.match(name, "(http/)") ~= nil
if isHttpFile then if isHttpFile then
@ -24,6 +24,6 @@ return function (connection, req, args)
connection:send(' <li><a href="' .. url .. '">' .. url .. "</a> (" .. size .. " bytes)</li>\n") connection:send(' <li><a href="' .. url .. '">' .. url .. "</a> (" .. size .. " bytes)</li>\n")
end end
end end
connection:send("</ul>\n</p>\n</body></html>") connection:send("</ul>\n</body></html>")
end end

View File

@ -1,32 +1,31 @@
<html> <!doctype html>
<html lang="en">
<head> <head>
<title>Garage control</title> <title>Garage control</title>
<style>
div{
text-align: center;
}
</style>
</head> </head>
<body> <body>
<div>
<div align="center">
<h2>Door 1</h2> <h2>Door 1</h2>
<form action="garage_door.lua" method="post">
<form action="garage_door.lua" method="post"/> <input type="hidden" name="door" value="1">
<input type="hidden" name="door" value="1"/> <input type="submit" name="action" value="status">
<input type="submit" name="action" value="status"/> <input type="submit" name="action" value="open">
<input type="submit" name="action" value="open"/> <input type="submit" name="action" value="close">
<input type="submit" name="action" value="close"/> <input type="submit" name="action" value="toggle">
<input type="submit" name="action" value="toggle"/>
</form> </form>
<h2>Door 2</h2> <h2>Door 2</h2>
<form action="garage_door.lua" method="post">
<form action="garage_door.lua" method="post"/> <input type="hidden" name="door" value="2">
<input type="hidden" name="door" value="2"/> <input type="submit" name="action" value="status">
<input type="submit" name="action" value="status"/> <input type="submit" name="action" value="open">
<input type="submit" name="action" value="open"/> <input type="submit" name="action" value="close">
<input type="submit" name="action" value="close"/> <input type="submit" name="action" value="toggle">
<input type="submit" name="action" value="toggle"/>
</form> </form>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,11 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<link rel="stylesheet" type="text/css" href="garage_door_control.css"> <link rel="stylesheet" type="text/css" href="garage_door_control.css">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Garage Remote</title> <title>Garage Remote</title>
</head>
<script> <script>
var xmlHttp = null; var xmlHttp = null;
@ -68,7 +67,13 @@
} }
} }
</script> </script>
<body bgcolor="#777777"> <style>
body {
background-color: #777;
}
</style>
</head>
<body>
<div id="remote"> <div id="remote">
<div id="label" class="start"></div> <div id="label" class="start"></div>
<a href="#" onclick="pushTheButton(1);" class="button button-1"> <a href="#" onclick="pushTheButton(1);" class="button button-1">
@ -81,4 +86,3 @@
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,13 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Served by an ESP8266</title> <title>Served by an ESP8266</title>
</head> <style>
<body> div{
text-align: center;
}
</style>
</head>
<body>
<h1>Hello World!</h1> <h1>Hello World!</h1>
<div align="center"> <div>
<img src="underconstruction.gif"> <img alt="under construction" src="underconstruction.gif">
</div> </div>
<p> <p>
This page is served by <b>nodemcu-httpserver</b> running on an ESP8266 that uses the <a href="https://github.com/nodemcu/nodemcu-firmware">NodeMCU</a> firmware. This page is served by <b>nodemcu-httpserver</b> running on an ESP8266 that uses the <a href="https://github.com/nodemcu/nodemcu-firmware">NodeMCU</a> firmware.
@ -22,6 +27,7 @@
<li><a href="index.html">Index</a>: This page (static)</li> <li><a href="index.html">Index</a>: This page (static)</li>
<li><a href="hello_world.txt">A text file</a>: Simple text, to verify MIME type is ok. (static)</li> <li><a href="hello_world.txt">A text file</a>: Simple text, to verify MIME type is ok. (static)</li>
<li><a href="cars.html">Nice cars</a>: Stress test, loads several "large" images. Makes the chip panic and restart :( (static)</li> <li><a href="cars.html">Nice cars</a>: Stress test, loads several "large" images. Makes the chip panic and restart :( (static)</li>
<li><a href="cars.lua">Nice cars</a>: Stress test, loads several "large" images. Makes the chip panic and restart :( (dynamic to change number of cars)</li>
<li><a href="counter.html">Count Requests</a>: Stress test, loads the same page over and over, counting every load. (static)</li> <li><a href="counter.html">Count Requests</a>: Stress test, loads the same page over and over, counting every load. (static)</li>
<li><a href="zipped.html">Zipped</a>: File is actually saved as zipped.html.gz. A compressed file! (static but gzipped)</li> <li><a href="zipped.html">Zipped</a>: File is actually saved as zipped.html.gz. A compressed file! (static but gzipped)</li>
<li><a href="zipped.html.gz">Zipped</a>: Same exact file as served above. Server is smart enough to treat the .gz extension correctly (static but gzipped)</li> <li><a href="zipped.html.gz">Zipped</a>: Same exact file as served above. Server is smart enough to treat the .gz extension correctly (static but gzipped)</li>
@ -33,5 +39,5 @@
<li><a href="upload.html">Upload</a>: Update, remove, list files on the server. Beware security implications. By <a href="https://github.com/ATAMAH">ATAMAH</a>.</li> <li><a href="upload.html">Upload</a>: Update, remove, list files on the server. Beware security implications. By <a href="https://github.com/ATAMAH">ATAMAH</a>.</li>
<li><a href="foo.html">Foo</a>: A file that doesn't exist. Should error (404 error)</li> <li><a href="foo.html">Foo</a>: A file that doesn't exist. Should error (404 error)</li>
</ul> </ul>
</body> </body>
</html> </html>

205
http/upload.css Normal file
View File

@ -0,0 +1,205 @@
html{
background-color:#ebebec;
background-image:-webkit-radial-gradient(center, #ebebec, #b4b4b4);
background-image:-moz-radial-gradient(center, #ebebec, #b4b4b4);
background-image:radial-gradient(center, #ebebec, #b4b4b4);
}
body{
font:15px/1.3 Arial, sans-serif;
color: #4f4f4f;
margin:0;
padding:0;
overflow-x:hidden;
}
a, a:visited {
outline:none;
color:#389dc1;
}
a:hover{
text-decoration:none;
}
section, footer, header, aside{
display: block;
}
.dropBox {width:100vw; height:100vh; margin-top: -200px; padding-top: 200px;}
#uploaddir{
background-color: #2E3134;
font-size:16px;
font-weight:bold;
color:#7f858a;
padding: 40px 50px;
margin-bottom: 30px;
}
#uploaddir a{
background-color:#007a96;
padding:12px 26px;
color:#fff;
font-size:14px;
border-radius:2px;
cursor:pointer;
margin-top:12px;
line-height:1;
margin-left: 10px;
}
#selectedDir {
margin-top:20px;
}
#upload{
font-family:'PT Sans Narrow', sans-serif;
background-color:#373a3d;
background-image:-webkit-linear-gradient(top, #373a3d, #313437);
background-image:-moz-linear-gradient(top, #373a3d, #313437);
background-image:linear-gradient(top, #373a3d, #313437);
width:450px;
padding:30px;
border-radius:3px;
margin:10px auto 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
#drop{
background-color: #2E3134;
padding: 40px 50px;
margin-bottom: 30px;
border: 20px solid rgba(0, 0, 0, 0);
border-radius: 3px;
/* no external files */
border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0MUU5RTNGRDk4QjFFMjExODE0NkUyMUJBNERDNDk0NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0OEZGM0JBREI3RTcxMUUyODFDRkE4MTU1MDRCRkVBRCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0OEZGM0JBQ0I3RTcxMUUyODFDRkE4MTU1MDRCRkVBRCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0MUU5RTNGRDk4QjFFMjExODE0NkUyMUJBNERDNDk0NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0MUU5RTNGRDk4QjFFMjExODE0NkUyMUJBNERDNDk0NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmuiwCwAAAF7SURBVHja7NpRSsMwAIDhZJbdoNLhdXwSvIBPg11CUBS9h7AnbyYr9hAxfRmIfRDdlqx8P2SwPqwjH01gS7y+uV2GEJ7yWOdxFf5RSikMfb9/H2MMbdeFYzWj+33ksc3jtckvL3ncB5VsfBAe8rhY5JeN+aimzQjSmodqahfmoK6aqYufu93BNsFDfdYc73e5Wv245gmpLCBABASIgAARECACAkRABASI/lRzyp+r9b2pufeEWLIE5Jz2kKm/Ee0rp8lfuJYsAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiILPM2d6COdtryRKQc99DnO0tl7O9liwBASIgQAQEiIAICBABASIgQAQEiIAICBABASIgs8zZ3oI522vJEpBz30Oc7S2Xs72WLAEBIiBABASIgAgIEAEBIiBzrZm6OPWz8G9KKYWh7/fvY4yh7bqjffk53s8TUuGSNZiGahpGkDfzUE3bcQ95zmOZx10enTkp0rgxvefx+CXAAFXjX+IoV9pGAAAAAElFTkSuQmCC') 25 repeat;
text-align: center;
text-transform: uppercase;
font-size:16px;
font-weight:bold;
color:#7f858a;
}
#drop a{
background-color:#007a96;
padding:12px 26px;
color:#fff;
font-size:14px;
border-radius:2px;
cursor:pointer;
display:block;
margin-top:12px;
line-height:1;
width:100px;
margin-left: 75px;
}
#drop a:hover{
background-color:#0986a3;
}
#drop input{
display:none;
}
#upload ul{
list-style:none;
margin:0 -30px;
border-top:1px solid #2b2e31;
border-bottom:1px solid #3d4043;
padding: 0;
}
#upload ul li{
background-color:#333639;
background-image:-webkit-linear-gradient(top, #333639, #303335);
background-image:-moz-linear-gradient(top, #333639, #303335);
background-image:linear-gradient(top, #333639, #303335);
border-top:1px solid #3d4043;
border-bottom:1px solid #2b2e31;
padding:15px;
height: 52px;
position: relative;
}
#upload ul li input{
display: none;
}
#upload ul li p{
width: 300px;
overflow: hidden;
white-space: nowrap;
color: #EEE;
font-size: 16px;
font-weight: bold;
position: absolute;
top: 8px;
left: 100px;
}
#upload ul li i{
font-weight: normal;
font-style:normal;
color:#7f7f7f;
display:block;
}
#upload ul li canvas{
top: 5px;
left: 20px;
position: absolute;
}
.delete:after{
color: #ff0000;
content: "\2718";
}
.uploaded:after{
color: #00ff00;
content: "\2714";
}
#upload ul li span{
width: 15px;
height: 12px;
cursor:pointer;
position: absolute;
top: 34px;
right: 33px;
font-size:18px;
}
#upload ul li.working span{
height: 16px;
background-position: 0 -12px;
}
#upload ul li.error p{
color:red;
}
.chart {
position:relative;
margin:0px;
width:48px; height:48px;
}
.fileInfo {
text-align: center;
font-size: 16px;
font-weight: bold;
color: #7f858a;
margin-top: 24px;
margin-bottom: 24px;
text-transform: uppercase;
}

View File

@ -1,560 +1,23 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<style type="text/css"> <title>Upload</title>
html{ <link rel="stylesheet" type="text/css" href="upload.css"/>
background-color:#ebebec; <script src="upload.js"></script>
background-image:-webkit-radial-gradient(center, #ebebec, #b4b4b4);
background-image:-moz-radial-gradient(center, #ebebec, #b4b4b4);
background-image:radial-gradient(center, #ebebec, #b4b4b4);
}
body{
font:15px/1.3 Arial, sans-serif;
color: #4f4f4f;
margin:0;
padding:0;
overflow-x:hidden;
}
a, a:visited {
outline:none;
color:#389dc1;
}
a:hover{
text-decoration:none;
}
section, footer, header, aside{
display: block;
}
.dropBox {width:100vw; height:100vh; margin-top: -200px; padding-top: 200px;}
#upload{
font-family:'PT Sans Narrow', sans-serif;
background-color:#373a3d;
background-image:-webkit-linear-gradient(top, #373a3d, #313437);
background-image:-moz-linear-gradient(top, #373a3d, #313437);
background-image:linear-gradient(top, #373a3d, #313437);
width:450px;
padding:30px;
border-radius:3px;
margin:10px auto 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
#drop{
background-color: #2E3134;
padding: 40px 50px;
margin-bottom: 30px;
border: 20px solid rgba(0, 0, 0, 0);
border-radius: 3px;
/* no external files */
border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0MUU5RTNGRDk4QjFFMjExODE0NkUyMUJBNERDNDk0NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0OEZGM0JBREI3RTcxMUUyODFDRkE4MTU1MDRCRkVBRCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0OEZGM0JBQ0I3RTcxMUUyODFDRkE4MTU1MDRCRkVBRCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0MUU5RTNGRDk4QjFFMjExODE0NkUyMUJBNERDNDk0NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0MUU5RTNGRDk4QjFFMjExODE0NkUyMUJBNERDNDk0NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmuiwCwAAAF7SURBVHja7NpRSsMwAIDhZJbdoNLhdXwSvIBPg11CUBS9h7AnbyYr9hAxfRmIfRDdlqx8P2SwPqwjH01gS7y+uV2GEJ7yWOdxFf5RSikMfb9/H2MMbdeFYzWj+33ksc3jtckvL3ncB5VsfBAe8rhY5JeN+aimzQjSmodqahfmoK6aqYufu93BNsFDfdYc73e5Wv245gmpLCBABASIgAARECACAkRABASI/lRzyp+r9b2pufeEWLIE5Jz2kKm/Ee0rp8lfuJYsAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiILPM2d6COdtryRKQc99DnO0tl7O9liwBASIgQAQEiIAICBABASIgQAQEiIAICBABASIgs8zZ3oI522vJEpBz30Oc7S2Xs72WLAEBIiBABASIgAgIEAEBIiBzrZm6OPWz8G9KKYWh7/fvY4yh7bqjffk53s8TUuGSNZiGahpGkDfzUE3bcQ95zmOZx10enTkp0rgxvefx+CXAAFXjX+IoV9pGAAAAAElFTkSuQmCC') 25 repeat;
text-align: center;
text-transform: uppercase;
font-size:16px;
font-weight:bold;
color:#7f858a;
}
#drop a{
background-color:#007a96;
padding:12px 26px;
color:#fff;
font-size:14px;
border-radius:2px;
cursor:pointer;
display:block;
margin-top:12px;
line-height:1;
width:100px;
margin-left: 75px;
}
#drop a:hover{
background-color:#0986a3;
}
#drop input{
display:none;
}
#upload ul{
list-style:none;
margin:0 -30px;
border-top:1px solid #2b2e31;
border-bottom:1px solid #3d4043;
padding: 0;
}
#upload ul li{
background-color:#333639;
background-image:-webkit-linear-gradient(top, #333639, #303335);
background-image:-moz-linear-gradient(top, #333639, #303335);
background-image:linear-gradient(top, #333639, #303335);
border-top:1px solid #3d4043;
border-bottom:1px solid #2b2e31;
padding:15px;
height: 52px;
position: relative;
}
#upload ul li input{
display: none;
}
#upload ul li p{
width: 300px;
overflow: hidden;
white-space: nowrap;
color: #EEE;
font-size: 16px;
font-weight: bold;
position: absolute;
top: 8px;
left: 100px;
}
#upload ul li i{
font-weight: normal;
font-style:normal;
color:#7f7f7f;
display:block;
}
#upload ul li canvas{
top: 5px;
left: 20px;
position: absolute;
}
#upload ul li span{
width: 15px;
height: 12px;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAaCAYAAABozQZiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpFQUY1MzY0QUU3QjdFMjExODE0NkUyMUJBNERDNDk0NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCOTc5MTBDQ0I3RUYxMUUyOUVBQkNFOURERDIzQkU4NSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCOTc5MTBDQkI3RUYxMUUyOUVBQkNFOURERDIzQkU4NSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFQUY1MzY0QUU3QjdFMjExODE0NkUyMUJBNERDNDk0NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFQUY1MzY0QUU3QjdFMjExODE0NkUyMUJBNERDNDk0NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvX6SiYAAAGjSURBVHjanJRPRMRREMfbNtE19hQlolNdomsRaZUiIlHKdiml7CpFolU6pEOWTUTRJZKIakmHsqQ99UedOiT2lG6xRPSdzFtjGi+/ho99836/78z8Zt7bUN3VfElAKwcRkC/9h/AAPIKmsgDCMNgB3ezvB8m8BQZ4/QkmSVwJbsCcR7gOYrz+Av0gQ2WfgWaGvimphEsgLvwRcEQLypxVLy4KP678cbDnHMqc4GZMiQBU2huX62wWbMqSXLenWeDKW1alr4A13QjZ7YT1AmwDLFhd1KOi0naFf8lVmWYdklHuQTXo8g3eOiT07UOgFXy4zcPB2wpTjAdpcA8iVgbs0yTe8dsi90N3NdEUfifYfwBtPCZn9CzF6wJXlGt8Of3JXCVebAAXfOXIxoTQfVJYlk3n9NgIQGc9LfYpaxRcSzHdkD4jwKoStqujXOy2FUBnzPpGRQHODfErePprzjHVHGf1qom/xCTcVlXkPFMoiocNYQ/PM+MLQOIZJexlUUGsZYBOKaYRPAvhieq0DJCUt45uVZ5LrLXGIQJ0uP8uZ98CDADM9WkEBoK0xwAAAABJRU5ErkJggg==') no-repeat;
position: absolute;
top: 34px;
right: 33px;
cursor:pointer;
}
#upload ul li.working span{
height: 16px;
background-position: 0 -12px;
}
#upload ul li.error p{
color:red;
}
.chart {
position:relative;
margin:0px;
width:48px; height:48px;
}
.fileInfo {
text-align: center;
font-size: 16px;
font-weight: bold;
color: #7f858a;
margin-top: 24px;
margin-bottom: 24px;
text-transform: uppercase;
}
</style>
<script>
var files = [];
var sendingOffset = 0;
var lastRequest = '';
var dataView;
var filesCount = 0;
var currentUploadingFile = 0;
var uploadOrder = [];
var uploadingInProgress = 0;
var fileUploadRequest;
var chunkSize = 128;
var totalUploaded = 0;
var tpl = '<li class="working" id="file%filenum%"><div class="chart" id="graph%filenum%" data-percent="0"></div><p>%filename%<i>%filesize%</i></p><span id="fileStatus%filenum%" onclick="DeleteFiles(%filenum%);"></span></li>';
document.addEventListener("DOMContentLoaded", function() {
var dropbox;
dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
UpdateFileList();
});
function dragenter(e) {
e.stopPropagation();
e.preventDefault();
}
function dragover(e) {
e.stopPropagation();
e.preventDefault();
}
function drop(e) {
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
handleFiles(dt.files);
}
function handleFiles(tfiles) {
var filesCount = tfiles.length;
files = tfiles;
currentUploadingFile = 0;
uploadOrder = [];
sendingOffset = 0;
lastRequest = '';
document.getElementById('fileList').innerHTML = '';
var fileNames = {};
for (var i = 0; i < filesCount; i++) {
fileNames[tfiles[i].name] = i;
}
Keys(fileNames).sort(function(a,b){var c=a.toLowerCase(),d=b.toLowerCase();return c<d?-1:c>d?1:0}).forEach(function(item) {
var i = fileNames[item];
var append = tpl.replace(/%filename%/g, tfiles[i].name);
append = append.replace(/%filesize%/g, formatFileSize(tfiles[i].size));
append = append.replace(/%filenum%/g, i);
document.getElementById('fileList').insertAdjacentHTML('beforeend', append);
UpdateGraph(0, i);
uploadOrder.push(i);
});
}
function DeleteFiles(filenum) {
var elem = document.getElementById('file' + filenum.toString());
elem.parentNode.removeChild(elem);
if (uploadingInProgress) {
if (parseInt(filenum) != uploadOrder[currentUploadingFile]) {
for (var i = 0; i < uploadOrder.length; i++) {
if (uploadOrder[i] == filenum) {
delete uploadOrder[i];
}
}
}
else {
uploadingInProgress = 0;
RemoveFile(files[uploadOrder[currentUploadingFile]].name + '.dnl');
for (var i = 0; i < uploadOrder.length; i++) {
if (uploadOrder[i] == filenum) {
delete uploadOrder[i];
}
}
currentUploadingFile++;
totalUploaded = 0;
sendingOffset = 0;
lastRequest = '';
fileUploadRequest.abort();
fileUploadRequest = 0;
UploadFiles();
}
}
else {
for (var i = 0; i < uploadOrder.length; i++) {
if (uploadOrder[i] == filenum) {
delete uploadOrder[i];
}
}
}
}
function UploadFiles() {
if (uploadOrder[currentUploadingFile] === undefined) {
uploadingInProgress = 0;
if (currentUploadingFile < files.length - 1) {
currentUploadingFile++;
UploadFiles();
}
return;
}
var fileNum = uploadOrder[currentUploadingFile];
var file = files[fileNum];
var chunkLen = 0;
var filedata = '';
uploadingInProgress = 1;
var fr = new FileReader();
fr.onload = function() {
dataView = null;
dataView = new Uint8Array(fr.result);
if (file.size <= chunkSize) {
sendingOffset = 0;
chunkLen = file.size;
for (var i = 0; i < dataView.length; i++) {
if (dataView[i] < 16) {
filedata += '0';
}
filedata += dataView[i].toString(16).toUpperCase();
}
}
else {
if (dataView.length - sendingOffset > chunkSize) {
chunkLen = chunkSize;
}
else {
chunkLen = dataView.length - sendingOffset;
}
for (var i = sendingOffset; i < sendingOffset + chunkLen; i++) {
if (dataView[i] < 16) {
filedata += '0';
}
filedata += dataView[i].toString(16).toUpperCase();
}
}
fileUploadRequest = new XMLHttpRequest();
fileUploadRequest.onreadystatechange = function() {
if (fileUploadRequest.readyState != 4) return;
if (fileUploadRequest.status == 200) {
if (chunkLen + sendingOffset < dataView.length) {
totalUploaded += chunkSize;
UpdateGraph(Math.round((totalUploaded / file.size) * 100), uploadOrder[currentUploadingFile]);
sendingOffset += chunkSize;
UploadFiles();
}
else {
var statusElement = document.getElementById('fileStatus' + uploadOrder[currentUploadingFile]);
sendingOffset = 0;
UpdateGraph(100, uploadOrder[currentUploadingFile]);
uploadingInProgress = 0;
UpdateFileList();
totalUploaded = 0;
if (statusElement) {
statusElement.style["background-position"] = "0 3px";
}
if (currentUploadingFile < files.length) {
currentUploadingFile++;
UploadFiles();
}
}
}
else {
UploadFiles();
}
fileUploadRequest = 0;
}
lastRequest = 'upload.lua?cmd=upload&filename=' + file.name + '&filesize=' + file.size + '&len=' + chunkLen + '&offset=' + sendingOffset + '&data=' + filedata;
fileUploadRequest.timeout = 5000;
fileUploadRequest.open('GET', lastRequest, true);
fileUploadRequest.send();
};
fr.readAsArrayBuffer(file);
}
function formatFileSize(bytes) {
if (typeof bytes !== 'number') {
return '';
}
if (bytes >= 1073741824) {
return (bytes / 1073741824).toFixed(2) + ' GB';
}
if (bytes >= 1048576) {
return (bytes / 1048576).toFixed(2) + ' MB';
}
return (bytes / 1024).toFixed(2) + ' KB';
}
function UpdateGraph(percent, id) {
var el = document.getElementById('graph' + id); // get canvas
if (!el) {
return;
}
var options = {
percent: el.getAttribute('data-percent') || 0,
size: el.getAttribute('data-size') || 48,
lineWidth: el.getAttribute('data-line') || 8,
rotate: el.getAttribute('data-rotate') || 0
}
var canvas = document.createElement('canvas');
if (typeof(G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
canvas.width = canvas.height = options.size;
el.appendChild(canvas);
ctx.translate(options.size / 2, options.size / 2); // change center
ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg
var radius = (options.size - options.lineWidth) / 2;
function drawCircle(color, lineWidth, percent) {
if (percent) {
percent = Math.min(Math.max(0, percent), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
}
};
options.percent = percent;
drawCircle('#2e3134', options.lineWidth + 1, 100 / 100);
drawCircle('#007a96', options.lineWidth, options.percent / 100);
}
function Keys(obj) {
var keys = [];
for(var key in obj){
if(obj.hasOwnProperty(key)){
keys.push(key);
}
}
return keys;
}
function UpdateFileList() {
var fileListRequest = new XMLHttpRequest();
fileListRequest.onreadystatechange = function() {
if (fileListRequest.readyState != 4) return;
if (fileListRequest.status == 200) {
var fileInfo = JSON.parse(fileListRequest.responseText);
var fileList = fileInfo['files'];
document.getElementById('fileInfo').innerHTML = '';
var tpl = '<li class="working"><p style="left: 30px;">%filenamelink%<i>%filesize%</i></p><span id="fileStatus" onclick="RemoveFile(\'%filename%\');"></span></li>';
var tplTotal = '<li class="working"><p style="left: 30px;">Used:<i>%used%</i></p></li><li class="working"><p style="left: 30px;">Free:<i>%free%</i></p></li><li class="working"><p style="left: 30px;">Total:<i>%total%</i></p></li>';
var append, link;
Keys(fileList).sort(function(a,b){var c=a.toLowerCase(),d=b.toLowerCase();return c<d?-1:c>d?1:0}).forEach(function(item) {
if (!(item.match(/\.lc$/ig))) {
link = item.replace(/\.gz$/g, '');
append = tpl.replace(/%filenamelink%/g, '<a href="' + link + '" target="_blank">' + item + '</a>');
}
else {
append = tpl.replace(/%filenamelink%/g, item);
}
append = append.replace(/%filename%/g, item);
append = append.replace(/%filesize%/g, formatFileSize(parseInt(fileList[item])));
document.getElementById('fileInfo').insertAdjacentHTML('beforeend', append);
});
append = tplTotal.replace(/%used%/g, formatFileSize(parseInt(fileInfo['used'])));
append = append.replace(/%free%/g, formatFileSize(parseInt(fileInfo['free'])));
append = append.replace(/%total%/g, formatFileSize(parseInt(fileInfo['total'])));
document.getElementById('fileInfo').insertAdjacentHTML('beforeend', append);
}
else {
}
fileListRequest = null;
}
fileListRequest.open('GET', 'upload.lua?cmd=list', true);
fileListRequest.send();
}
function RemoveFile(name) {
var fileRemoveRequest = new XMLHttpRequest();
fileRemoveRequest.onreadystatechange = function() {
if (fileRemoveRequest.readyState != 4) return;
if (fileRemoveRequest.status == 200) {
UpdateFileList();
}
}
fileRemoveRequest.open('GET', 'upload.lua?cmd=remove&filename=' + name, true);
fileRemoveRequest.send();
}
</script>
</head> </head>
<body> <body>
<div id="dropbox" class="dropBox"> <div id="dropbox" class="dropBox">
<div id="upload"> <div id="upload">
<div id="uploaddir" class="uploadDir">
<a onclick='UploadDir("");'>/</a>
<a onclick='UploadDir("http");'>/http</a>
<div id="selectedDir">selected Directory: <div id = "dir">bla</div></div></div>
<div id="drop"> <div id="drop">
Drop Here Drop Here
<a onclick='document.getElementById("browseInput").click();'>Browse</a> <a onclick='document.getElementById("browseInput").click();'>Browse</a>
<a onclick='UploadFiles();'>Upload</a> <a onclick='UploadFiles();'>Upload</a>
<input id="browseInput" type="file" name="upl" onchange="handleFiles(this.files);" multiple /> <form><input id="browseInput" type="file" name="upl" onclick="this.form.reset();" onchange="handleFiles(this.files);" multiple /></form>
</div> </div>
<ul id="fileList"> <ul id="fileList">

380
http/upload.js Normal file
View File

@ -0,0 +1,380 @@
var files = [];
var sendingOffset = 0;
var lastRequest = '';
var dataView;
var filesCount = 0;
var currentUploadingFile = 0;
var uploadOrder = [];
var uploadingInProgress = 0;
var fileUploadRequest;
var chunkSize = 128;
var totalUploaded = 0;
var tpl = '<li class="working" id="file%filenum%"><div class="chart" id="graph%filenum%" data-percent="0"></div><p>%filename%<i>%filesize%</i></p><span class="delete" id="fileStatus%filenum%" onclick="DeleteFiles(%filenum%);"></span></li>';
document.addEventListener("DOMContentLoaded", function() {
var dropbox;
dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
UpdateFileList();
UploadDir("http");
});
function dragenter(e) {
e.stopPropagation();
e.preventDefault();
}
function dragover(e) {
e.stopPropagation();
e.preventDefault();
}
function drop(e) {
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
handleFiles(dt.files);
}
function handleFiles(tfiles) {
var filesCount = tfiles.length;
files = tfiles;
currentUploadingFile = 0;
uploadOrder = [];
sendingOffset = 0;
lastRequest = '';
document.getElementById('fileList').innerHTML = '';
var fileNames = {};
for (var i = 0; i < filesCount; i++) {
fileNames[uploadDir + tfiles[i].name] = i;
}
Keys(fileNames).sort(function(a,b){var c=a.toLowerCase(),d=b.toLowerCase();return c<d?-1:c>d?1:0}).forEach(function(item) {
var i = fileNames[item];
var append = tpl.replace(/%filename%/g, uploadDir + tfiles[i].name);
append = append.replace(/%filesize%/g, formatFileSize(tfiles[i].size));
append = append.replace(/%filenum%/g, i);
document.getElementById('fileList').insertAdjacentHTML('beforeend', append);
UpdateGraph(0, i);
uploadOrder.push(i);
});
}
function DeleteFiles(filenum) {
var elem = document.getElementById('file' + filenum.toString());
elem.parentNode.removeChild(elem);
if (uploadingInProgress) {
if (parseInt(filenum) != uploadOrder[currentUploadingFile]) {
for (var i = 0; i < uploadOrder.length; i++) {
if (uploadOrder[i] == filenum) {
delete uploadOrder[i];
}
}
}
else {
uploadingInProgress = 0;
RemoveFile(files[uploadOrder[currentUploadingFile]].name + '.dnl');
for (var i = 0; i < uploadOrder.length; i++) {
if (uploadOrder[i] == filenum) {
delete uploadOrder[i];
}
}
currentUploadingFile++;
totalUploaded = 0;
sendingOffset = 0;
lastRequest = '';
fileUploadRequest.abort();
fileUploadRequest = 0;
UploadFiles();
}
}
else {
for (var i = 0; i < uploadOrder.length; i++) {
if (uploadOrder[i] == filenum) {
delete uploadOrder[i];
}
}
}
}
function UploadFiles() {
if (uploadOrder[currentUploadingFile] === undefined) {
uploadingInProgress = 0;
if (currentUploadingFile < files.length - 1) {
currentUploadingFile++;
UploadFiles();
}
return;
}
var fileNum = uploadOrder[currentUploadingFile];
var file = files[fileNum];
var chunkLen = 0;
var filedata = '';
uploadingInProgress = 1;
var fr = new FileReader();
fr.onload = function() {
dataView = null;
dataView = new Uint8Array(fr.result);
if (file.size <= chunkSize) {
sendingOffset = 0;
chunkLen = file.size;
for (var i = 0; i < dataView.length; i++) {
if (dataView[i] < 16) {
filedata += '0';
}
filedata += dataView[i].toString(16).toUpperCase();
}
}
else {
if (dataView.length - sendingOffset > chunkSize) {
chunkLen = chunkSize;
}
else {
chunkLen = dataView.length - sendingOffset;
}
for (var i = sendingOffset; i < sendingOffset + chunkLen; i++) {
if (dataView[i] < 16) {
filedata += '0';
}
filedata += dataView[i].toString(16).toUpperCase();
}
}
fileUploadRequest = new XMLHttpRequest();
fileUploadRequest.onreadystatechange = function() {
if (fileUploadRequest.readyState != 4) return;
if (fileUploadRequest.status == 200) {
if (chunkLen + sendingOffset < dataView.length) {
totalUploaded += chunkSize;
UpdateGraph(Math.round((totalUploaded / file.size) * 100), uploadOrder[currentUploadingFile]);
sendingOffset += chunkSize;
UploadFiles();
}
else {
var statusElement = document.getElementById('fileStatus' + uploadOrder[currentUploadingFile]);
sendingOffset = 0;
UpdateGraph(100, uploadOrder[currentUploadingFile]);
uploadingInProgress = 0;
UpdateFileList();
totalUploaded = 0;
if (statusElement) {
statusElement.classList.add("uploaded");
}
if (currentUploadingFile < files.length) {
currentUploadingFile++;
UploadFiles();
}
}
}
else {
UploadFiles();
}
fileUploadRequest = 0;
}
lastRequest = 'upload.lua?cmd=upload&filename=' + uploadDir + file.name + '&filesize=' + file.size + '&len=' + chunkLen + '&offset=' + sendingOffset + '&data=' + filedata;
fileUploadRequest.timeout = 5000;
fileUploadRequest.open('GET', lastRequest, true);
fileUploadRequest.send();
};
fr.readAsArrayBuffer(file);
}
function UploadDir(dir) {
if (uploadingInProgress == 0) {
document.getElementById('dir').innerHTML = "/" + dir;
uploadDir = dir;
if (!(uploadDir == "")) {
uploadDir += "/";
}
}
}
function formatFileSize(bytes) {
if (typeof bytes !== 'number') {
return '';
}
if (bytes >= 1073741824) {
return (bytes / 1073741824).toFixed(2) + ' GB';
}
if (bytes >= 1048576) {
return (bytes / 1048576).toFixed(2) + ' MB';
}
return (bytes / 1024).toFixed(2) + ' KB';
}
function UpdateGraph(percent, id) {
var el = document.getElementById('graph' + id); // get canvas
if (!el) {
return;
}
var options = {
percent: el.getAttribute('data-percent') || 0,
size: el.getAttribute('data-size') || 48,
lineWidth: el.getAttribute('data-line') || 8,
rotate: el.getAttribute('data-rotate') || 0
}
var canvas = document.createElement('canvas');
if (typeof(G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
canvas.width = canvas.height = options.size;
el.appendChild(canvas);
ctx.translate(options.size / 2, options.size / 2); // change center
ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg
var radius = (options.size - options.lineWidth) / 2;
function drawCircle(color, lineWidth, percent) {
if (percent) {
percent = Math.min(Math.max(0, percent), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
}
};
options.percent = percent;
drawCircle('#2e3134', options.lineWidth + 1, 100 / 100);
drawCircle('#007a96', options.lineWidth, options.percent / 100);
}
function Keys(obj) {
var keys = [];
for(var key in obj){
if(obj.hasOwnProperty(key)){
keys.push(key);
}
}
return keys;
}
function UpdateFileList() {
var fileListRequest = new XMLHttpRequest();
fileListRequest.onreadystatechange = function() {
if (fileListRequest.readyState != 4) return;
if (fileListRequest.status == 200) {
var fileInfo = JSON.parse(fileListRequest.responseText);
var fileList = fileInfo['files'];
document.getElementById('fileInfo').innerHTML = '';
var tpl = '<li class="working"><p style="left: 30px;">%filenamelink%<i>%filesize%</i></p><span class="delete" id="fileStatus" onclick="RemoveFile(\'%filename%\');"></span></li>';
var tplTotal = '<li class="working"><p style="left: 30px;">Used:<i>%used%</i></p></li><li class="working"><p style="left: 30px;">Free:<i>%free%</i></p></li><li class="working"><p style="left: 30px;">Total:<i>%total%</i></p></li>';
var append, link;
Keys(fileList).sort(function(a,b){var c=a.toLowerCase(),d=b.toLowerCase();return c<d?-1:c>d?1:0}).forEach(function(item) {
if (!item.match(/\.lc$/ig) && item.match(/^http\//ig)) {
link = item.replace(/\.gz$/g, '').replace(/^http\//g, '');
append = tpl.replace(/%filenamelink%/g, '<a href="' + link + '" target="_blank">' + item + '</a>');
}
else {
append = tpl.replace(/%filenamelink%/g, item);
}
append = append.replace(/%filename%/g, item);
append = append.replace(/%filesize%/g, formatFileSize(parseInt(fileList[item])));
document.getElementById('fileInfo').insertAdjacentHTML('beforeend', append);
});
append = tplTotal.replace(/%used%/g, formatFileSize(parseInt(fileInfo['used'])));
append = append.replace(/%free%/g, formatFileSize(parseInt(fileInfo['free'])));
append = append.replace(/%total%/g, formatFileSize(parseInt(fileInfo['total'])));
document.getElementById('fileInfo').insertAdjacentHTML('beforeend', append);
}
else {
}
fileListRequest = null;
}
fileListRequest.open('GET', 'upload.lua?cmd=list', true);
fileListRequest.send();
}
function RemoveFile(name) {
var fileRemoveRequest = new XMLHttpRequest();
fileRemoveRequest.onreadystatechange = function() {
if (fileRemoveRequest.readyState != 4) return;
if (fileRemoveRequest.status == 200) {
UpdateFileList();
}
}
fileRemoveRequest.open('GET', 'upload.lua?cmd=remove&filename=' + name, true);
fileRemoveRequest.send();
}

View File

@ -48,23 +48,22 @@ return function (connection, req, args)
if (mbCmd == 'upload') then if (mbCmd == 'upload') then
if (fieldsCount > 5) then if (fieldsCount > 5) then
if (mbFilename ~= 'upload.lua') then if (mbFilename ~= 'http/upload.lua') then
connection:send('"offset":"' .. mbOffset .. '",') connection:send('"offset":"' .. mbOffset .. '",')
connection:send('"len":"' .. mbLen .. '",') connection:send('"len":"' .. mbLen .. '",')
connection:send('"filename":"' .. mbFilename .. '"') connection:send('"filename":"' .. mbFilename .. '"')
mbFilename = 'http/' .. mbFilename
for i=1,string.len(mbData),2 do for i=1,string.len(mbData),2 do
currentByte = tonumber(string.sub(mbData, i, i + 1), 16) currentByte = tonumber(string.sub(mbData, i, i + 1), 16)
binaryData = binaryData .. string.char(currentByte) binaryData = binaryData .. string.char(currentByte)
end end
local mbTmpFilename = string.sub(mbFilename, 0, 27) .. '.dnl'
if (mbOffset > 0) then if (mbOffset > 0) then
file.open(mbFilename .. '.dnl','a+') file.open(mbTmpFilename,'a+')
else else
file.remove(mbFilename .. '.dnl') file.remove(mbTmpFilename)
file.open(mbFilename .. '.dnl','w+') file.open(mbTmpFilename,'w+')
end end
file.seek("set", mbOffset) file.seek("set", mbOffset)
file.write(binaryData) file.write(binaryData)
@ -74,8 +73,8 @@ return function (connection, req, args)
if (fileSize == mbLen + mbOffset) then if (fileSize == mbLen + mbOffset) then
file.remove(mbFilename) file.remove(mbFilename)
file.rename(mbFilename .. '.dnl', mbFilename) file.rename(mbTmpFilename, mbFilename)
file.remove(mbFilename .. '.dnl') file.remove(mbTmpFilename)
if (string.sub(mbFilename, -4) == '.lua') then if (string.sub(mbFilename, -4) == '.lua') then
file.remove(string.sub(mbFilename, 0, -3) .. "lc") file.remove(string.sub(mbFilename, 0, -3) .. "lc")
@ -93,20 +92,16 @@ return function (connection, req, args)
connection:send('"files":{') connection:send('"files":{')
for name, size in pairs(file.list()) do for name, size in pairs(file.list()) do
local isHttpFile = string.match(name, "(http/)") ~= nil
if isHttpFile then
if (headerExist > 0) then if (headerExist > 0) then
connection:send(',') connection:send(',')
end end
local url = string.match(name, ".*/(.*)") local url = string.match(name, ".*/(.*)")
url = name
connection:send('"' .. url .. '":"' .. size .. '"') connection:send('"' .. url .. '":"' .. size .. '"')
headerExist = 1 headerExist = 1
end end
end
connection:send('},') connection:send('},')
@ -115,8 +110,8 @@ return function (connection, req, args)
connection:send('"free":"' .. remaining .. '"') connection:send('"free":"' .. remaining .. '"')
elseif (mbCmd == 'remove') then elseif (mbCmd == 'remove') then
if (fieldsCount > 1) then if (fieldsCount > 1) then
if (mbFilename ~= 'upload.lua') and (mbFilename ~= 'upload.lc') and (mbFilename ~= 'upload.html.gz') then if (mbFilename ~= 'http/upload.lua') and (mbFilename ~= 'http/upload.lc') and (mbFilename ~= 'http/upload.html.gz') then
file.remove('http/' .. mbFilename) file.remove(mbFilename)
end end
end end
end end
@ -124,4 +119,3 @@ return function (connection, req, args)
connection:send('}') connection:send('}')
collectgarbage() collectgarbage()
end end

View File

@ -3,32 +3,38 @@
-- Author: Marcos Kirsch -- Author: Marcos Kirsch
local compileAndRemoveIfNeeded = function(f) local compileAndRemoveIfNeeded = function(f)
if file.open(f) then if file.exists(f) then
file.close() local newf = f:gsub("%w+/", "")
print('Compiling:', f) file.rename(f, newf)
node.compile(f) print('Compiling:', newf)
file.remove(f) node.compile(newf)
file.remove(newf)
collectgarbage() collectgarbage()
end end
end end
local serverFiles = { local serverFiles = {
'httpserver.lua', 'srv/httpserver.lua',
'httpserver-b64decode.lua', 'srv/httpserver-b64decode.lua',
'httpserver-basicauth.lua', 'srv/httpserver-basicauth.lua',
'httpserver-compile.lua', 'srv/httpserver-buffer.lua',
'httpserver-conf.lua', 'srv/httpserver-connection.lua',
'httpserver-connection.lua', 'srv/httpserver-error.lua',
'httpserver-error.lua', 'srv/httpserver-header.lua',
'httpserver-header.lua', 'srv/httpserver-init.lua',
'httpserver-init.lua', 'srv/httpserver-request.lua',
'httpserver-request.lua', 'srv/httpserver-static.lua',
'httpserver-static.lua', 'srv/httpserver-wifi.lua',
'httpserver-wifi.lua', }
local lfsFiles = {
'srv/_init.lua',
'srv/dummy_strings.lua',
} }
for i, f in ipairs(serverFiles) do compileAndRemoveIfNeeded(f) end for i, f in ipairs(serverFiles) do compileAndRemoveIfNeeded(f) end
for i, f in ipairs(lfsFiles) do file.remove(f) end
compileAndRemoveIfNeeded = nil compileAndRemoveIfNeeded = nil
serverFiles = nil serverFiles = nil
lfsFiles = nil
collectgarbage() collectgarbage()

View File

@ -20,9 +20,10 @@ if (conf.wifi.mode == wifi.SOFTAP) or (conf.wifi.mode == wifi.STATIONAP) then
conf.wifi.accessPoint.config = {} conf.wifi.accessPoint.config = {}
conf.wifi.accessPoint.config.ssid = "ESP-"..node.chipid() -- Name of the WiFi network to create. conf.wifi.accessPoint.config.ssid = "ESP-"..node.chipid() -- Name of the WiFi network to create.
conf.wifi.accessPoint.config.pwd = "ESP-"..node.chipid() -- WiFi password for joining - at least 8 characters conf.wifi.accessPoint.config.pwd = "ESP-"..node.chipid() -- WiFi password for joining - at least 8 characters
conf.wifi.accessPoint.ip = "192.168.111.1" conf.wifi.accessPoint.net = {}
-- conf.wifi.accessPoint.netmask = "255.255.255.0" conf.wifi.accessPoint.net.ip = "192.168.111.1"
-- conf.wifi.accessPoint.gateway = "192.168.111.1" conf.wifi.accessPoint.net.netmask="255.255.255.0"
conf.wifi.accessPoint.net.gateway="192.168.111.1"
end end
-- These apply only when connecting to a router as a client -- These apply only when connecting to a router as a client
if (conf.wifi.mode == wifi.STATION) or (conf.wifi.mode == wifi.STATIONAP) then if (conf.wifi.mode == wifi.STATION) or (conf.wifi.mode == wifi.STATIONAP) then

View File

@ -1,27 +0,0 @@
-- httpserver-static.lua
-- Part of nodemcu-httpserver, handles sending static files to client.
-- Author: Marcos Kirsch
return function (connection, req, args)
dofile("httpserver-header.lc")(connection, 200, args.ext, args.isGzipped)
-- Send file in little chunks
local bytesRemaining = file.list()[args.file]
-- Chunks larger than 1024 don't work.
-- https://github.com/nodemcu/nodemcu-firmware/issues/1075
local chunkSize = 1024
local fileHandle = file.open(args.file)
while bytesRemaining > 0 do
local bytesToRead = 0
if bytesRemaining > chunkSize then bytesToRead = chunkSize else bytesToRead = bytesRemaining end
local chunk = fileHandle:read(bytesToRead)
connection:send(chunk)
bytesRemaining = bytesRemaining - #chunk
--print(args.file .. ": Sent "..#chunk.. " bytes, " .. bytesRemaining .. " to go.")
chunk = nil
collectgarbage()
end
-- print("Finished sending: ", args.file)
fileHandle:close()
fileHandle = nil
collectgarbage()
end

View File

@ -1,10 +1,27 @@
-- Compile freshly uploaded nodemcu-httpserver lua files. -- check/flash/use LFS support, if possible
if file.exists("httpserver-compile.lc") then if node.getpartitiontable().lfs_size > 0 then
dofile("httpserver-compile.lc") if file.exists("lfs.img") then
else if file.exists("lfs_lock") then
dofile("httpserver-compile.lua") file.remove("lfs_lock")
file.remove("lfs.img")
else
local f = file.open("lfs_lock", "w")
f:flush()
f:close()
file.remove("httpserver-compile.lua")
node.LFS.reload("lfs.img")
end
end
pcall(node.flashindex("_init"))
end end
-- Compile freshly uploaded nodemcu-httpserver lua files.
if file.exists("httpserver-compile.lua") then
dofile("httpserver-compile.lua")
file.remove("httpserver-compile.lua")
end
-- Set up NodeMCU's WiFi -- Set up NodeMCU's WiFi
dofile("httpserver-wifi.lc") dofile("httpserver-wifi.lc")

96
srv/_init.lua Normal file
View File

@ -0,0 +1,96 @@
--
-- File: _init.lua
--[[
This is a template for the LFS equivalent of the SPIFFS init.lua.
It is a good idea to such an _init.lua module to your LFS and do most of the LFS
module related initialisaion in this. This example uses standard Lua features to
simplify the LFS API.
For Lua 5.1, the first section adds a 'LFS' table to _G and uses the __index
metamethod to resolve functions in the LFS, so you can execute the main
function of module 'fred' by executing LFS.fred(params), etc.
It also implements some standard readonly properties:
LFS._time The Unix Timestamp when the luac.cross was executed. This can be
used as a version identifier.
LFS._config This returns a table of useful configuration parameters, hence
print (("0x%6x"):format(LFS._config.lfs_base))
gives you the parameter to use in the luac.cross -a option.
LFS._list This returns a table of the LFS modules, hence
print(table.concat(LFS._list,'\n'))
gives you a single column listing of all modules in the LFS.
For Lua 5.3 LFS table is populated by the LFS implementation in C so this part
of the code is skipped.
---------------------------------------------------------------------------------]]
local lfsindex = node.LFS and node.LFS.get or node.flashindex
local G=_ENV or getfenv()
local lfs_t
if _VERSION == 'Lua 5.1' then
lfs_t = {
__index = function(_, name)
local fn_ut, ba, ma, size, modules = lfsindex(name)
if not ba then
return fn_ut
elseif name == '_time' then
return fn_ut
elseif name == '_config' then
local fs_ma, fs_size = file.fscfg()
return {lfs_base = ba, lfs_mapped = ma, lfs_size = size,
fs_mapped = fs_ma, fs_size = fs_size}
elseif name == '_list' then
return modules
else
return nil
end
end,
__newindex = function(_, name, value) -- luacheck: no unused
error("LFS is readonly. Invalid write to LFS." .. name, 2)
end,
}
setmetatable(lfs_t,lfs_t)
G.module = nil -- disable Lua 5.0 style modules to save RAM
package.seeall = nil
else
lfs_t = node.LFS
end
G.LFS = lfs_t
--[[-------------------------------------------------------------------------------
The second section adds the LFS to the require searchlist, so that you can
require a Lua module 'jean' in the LFS by simply doing require "jean". However
note that this is at the search entry following the FS searcher, so if you also
have jean.lc or jean.lua in SPIFFS, then this SPIFFS version will get loaded into
RAM instead of using. (Useful, for development).
See docs/en/lfs.md and the 'loaders' array in app/lua/loadlib.c for more details.
---------------------------------------------------------------------------------]]
package.loaders[3] = function(module) -- loader_flash
return lfs_t[module]
end
--[[----------------------------------------------------------------------------
These replace the builtins loadfile & dofile with ones which preferentially
load from the filesystem and fall back to LFS. Flipping the search order
is an exercise left to the reader.-
------------------------------------------------------------------------------]]
local lf = loadfile
G.loadfile = function(n)
if file.exists(n) then return lf(n) end
local mod = n:match("(.*)%.l[uc]a?$")
local fn = mod and lfsindex(mod)
return (fn or error (("Cannot find '%s' in FS or LFS"):format(n))) and fn
end
-- Lua's dofile (luaB_dofile) reaches directly for luaL_loadfile; shim instead
G.dofile = function(n) return assert(loadfile(n))() end

37
srv/dummy_strings.lua Normal file
View File

@ -0,0 +1,37 @@
--
-- File: LFS_dummy_strings.lua
--[[
luac.cross -f generates a ROM string table which is part of the compiled LFS
image. This table includes all strings referenced in the loaded modules.
If you want to preload other string constants, then one way to achieve this is
to include a dummy module in the LFS that references the strings that you want
to load. You never need to call this module; it's inclusion in the LFS image is
enough to add the strings to the ROM table. Your application can use any strings
in the ROM table without incuring any RAM or Lua Garbage Collector (LGC)
overhead.
The local preload example is a useful starting point. However, if you call the
following code in your application during testing, then this will provide a
listing of the current RAM string table.
do
local a=debug.getstrings'RAM'
for i =1, #a do a[i] = ('%q'):format(a[i]) end
print ('local preload='..table.concat(a,','))
end
This will exclude any strings already in the ROM table, so the output is the list
of putative strings that you should consider adding to LFS ROM table.
---------------------------------------------------------------------------------]]
local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
"_LOADLIB", "__add", "__call", "__concat", "__div", "__eq", "__gc", "__index",
"__le", "__len", "__lt", "__mod", "__mode", "__mul", "__newindex", "__pow",
"__sub", "__tostring", "__unm", "collectgarbage", "cpath", "debug", "file",
"file.obj", "file.vol", "flash", "getstrings", "index", "ipairs", "list", "loaded",
"loader", "loaders", "loadlib", "module", "net.tcpserver", "net.tcpsocket",
"net.udpsocket", "newproxy", "package", "pairs", "path", "preload", "reload",
"require", "seeall", "wdclr", "not enough memory", "sjson.decoder","sjson.encoder",
"tmr.timer"

View File

@ -37,14 +37,14 @@ end
-- logic OR for number values -- logic OR for number values
local function lor(x,y) local function lor(x,y)
result = 0 local result = 0
for p=1,8 do result = result + (((bit(x,p) or bit(y,p)) == true) and uipow(2, (p-1)) or 0) end for p=1,8 do result = result + (((bit(x,p) or bit(y,p)) == true) and uipow(2, (p-1)) or 0) end
return result return result
end end
-- Character decoding table -- Character decoding table
local function toBase64Byte(char) local function toBase64Byte(char)
ascii = string.byte(char, 1) local ascii = string.byte(char, 1)
if ascii >= string.byte('A', 1) and ascii <= string.byte('Z', 1) then return ascii - string.byte('A', 1) if ascii >= string.byte('A', 1) and ascii <= string.byte('Z', 1) then return ascii - string.byte('A', 1)
elseif ascii >= string.byte('a', 1) and ascii <= string.byte('z', 1) then return ascii - string.byte('a', 1) + 26 elseif ascii >= string.byte('a', 1) and ascii <= string.byte('z', 1) then return ascii - string.byte('a', 1) + 26
elseif ascii >= string.byte('0', 1) and ascii <= string.byte('9', 1) then return ascii + 4 elseif ascii >= string.byte('0', 1) and ascii <= string.byte('9', 1) then return ascii + 4

View File

@ -2,7 +2,7 @@
-- Part of nodemcu-httpserver, authenticates a user using http basic auth. -- Part of nodemcu-httpserver, authenticates a user using http basic auth.
-- Author: Sam Dieck -- Author: Sam Dieck
basicAuth = {} local basicAuth = {}
-- Returns true if the user/password match one of the users/passwords in httpserver-conf.lua. -- Returns true if the user/password match one of the users/passwords in httpserver-conf.lua.
-- Returns false otherwise. -- Returns false otherwise.
@ -18,7 +18,7 @@ end
-- Returns the username if header contains valid credentials, -- Returns the username if header contains valid credentials,
-- nil otherwise. -- nil otherwise.
function basicAuth.authenticate(header) function basicAuth.authenticate(header)
local conf = dofile("httpserver-conf.lc") local conf = dofile("httpserver-conf.lua")
local credentials_enc = header:match("Authorization: Basic ([A-Za-z0-9+/=]+)") local credentials_enc = header:match("Authorization: Basic ([A-Za-z0-9+/=]+)")
if not credentials_enc then if not credentials_enc then
return nil return nil
@ -35,7 +35,7 @@ function basicAuth.authenticate(header)
end end
function basicAuth.authErrorHeader() function basicAuth.authErrorHeader()
local conf = dofile("httpserver-conf.lc") local conf = dofile("httpserver-conf.lua")
return "WWW-Authenticate: Basic realm=\"" .. conf.auth.realm .. "\"" return "WWW-Authenticate: Basic realm=\"" .. conf.auth.realm .. "\""
end end

48
srv/httpserver-buffer.lua Normal file
View File

@ -0,0 +1,48 @@
-- httpserver-buffer
-- Part of nodemcu-httpserver, provides a buffer that behaves like a connection object
-- that can handle multiple consecutive send() calls, and buffers small payloads up to 1400 bytes.
-- This is primarily user to collect the send requests done by the head script.
-- The owner is responsible to call getBuffer and send its result
-- Author: Gregor Hartmann
local Buffer = {}
-- parameter is the nodemcu-firmware connection
function Buffer:new()
local newInstance = {}
newInstance.size = 0
newInstance.data = {}
-- Returns true if there was any data to be sent.
function newInstance:getBuffer()
local buffer = table.concat(self.data, "")
self.data = {}
self.size = 0
return buffer
end
function newInstance:getpeer()
return "no peer"
end
function newInstance:send(payload)
local flushThreshold = 1400
if (not payload) then print("nop payload") end
local newSize = self.size + payload:len()
if newSize >= flushThreshold then
print("Buffer is full. Cutting off "..newSize-flushThreshold.." chars")
--STEP1: cut out piece from payload to complete threshold bytes in table
local pieceSize = flushThreshold - self.size
if pieceSize then
payload = payload:sub(1, pieceSize)
end
end
table.insert(self.data, payload)
self.size = self.size + #payload
end
return newInstance
end
return Buffer

View File

@ -5,7 +5,7 @@
-- flush() and for closing the connection. -- flush() and for closing the connection.
-- Author: Philip Gladstone, Marcos Kirsch -- Author: Philip Gladstone, Marcos Kirsch
BufferedConnection = {} local BufferedConnection = {}
-- parameter is the nodemcu-firmware connection -- parameter is the nodemcu-firmware connection
function BufferedConnection:new(connection) function BufferedConnection:new(connection)

View File

@ -15,7 +15,7 @@ return function(connection, code, extension, isGzipped, extraHeaders)
-- A few MIME types. Keep list short. If you need something that is missing, let's add it. -- A few MIME types. Keep list short. If you need something that is missing, let's add it.
local mt = {css = "text/css", gif = "image/gif", html = "text/html", ico = "image/x-icon", jpeg = "image/jpeg", local mt = {css = "text/css", gif = "image/gif", html = "text/html", ico = "image/x-icon", jpeg = "image/jpeg",
jpg = "image/jpeg", js = "application/javascript", json = "application/json", png = "image/png", xml = "text/xml"} jpg = "image/jpeg", js = "application/javascript", json = "application/json", png = "image/png", xml = "text/xml"}
if mt[ext] then return mt[ext] else return "text/plain" end if mt[ext] then return mt[ext] else return "application/octet-stream" end
end end
local mimeType = getMimeType(extension) local mimeType = getMimeType(extension)

View File

@ -5,7 +5,7 @@
-- Function for starting the server. -- Function for starting the server.
-- If you compiled the mdns module, then it will also register with mDNS. -- If you compiled the mdns module, then it will also register with mDNS.
local startServer = function(ip) local startServer = function(ip)
local conf = dofile('httpserver-conf.lc') local conf = dofile('httpserver-conf.lua')
if (dofile("httpserver.lc")(conf['general']['port'])) then if (dofile("httpserver.lc")(conf['general']['port'])) then
print("nodemcu-httpserver running at:") print("nodemcu-httpserver running at:")
print(" http://" .. ip .. ":" .. conf['general']['port']) print(" http://" .. ip .. ":" .. conf['general']['port'])

View File

@ -21,7 +21,8 @@ local function uri_decode(input)
end end
local function parseArgs(args) local function parseArgs(args)
local r = {}; i=1 local r = {}
local i = 1
if args == nil or args == "" then return r end if args == nil or args == "" then return r end
for arg in string.gmatch(args, "([^&]+)") do for arg in string.gmatch(args, "([^&]+)") do
local name, value = string.match(arg, "(.*)=(.*)") local name, value = string.match(arg, "(.*)=(.*)")
@ -46,6 +47,10 @@ local function getRequestData(payload)
local requestData local requestData
return function () return function ()
--print("Getting Request Data") --print("Getting Request Data")
-- for backward compatibility before v2.1
if (sjson == nil) then
sjson = cjson
end
if requestData then if requestData then
return requestData return requestData
else else
@ -60,7 +65,7 @@ local function getRequestData(payload)
--print("body = [" .. body .. "]") --print("body = [" .. body .. "]")
if mimeType == "application/json" then if mimeType == "application/json" then
--print("JSON: " .. body) --print("JSON: " .. body)
requestData = cjson.decode(body) requestData = sjson.decode(body)
elseif mimeType == "application/x-www-form-urlencoded" then elseif mimeType == "application/x-www-form-urlencoded" then
requestData = parseFormData(body) requestData = parseFormData(body)
else else
@ -79,7 +84,7 @@ local function parseUri(uri)
if uri == nil then return r end if uri == nil then return r end
if uri == "/" then uri = "/index.html" end if uri == "/" then uri = "/index.html" end
questionMarkPos, b, c, d, e, f = uri:find("?") local questionMarkPos, b, c, d, e, f = uri:find("?")
if questionMarkPos == nil then if questionMarkPos == nil then
r.file = uri:sub(1, questionMarkPos) r.file = uri:sub(1, questionMarkPos)
r.args = {} r.args = {}
@ -111,6 +116,7 @@ return function (request)
if not e then return nil end if not e then return nil end
local line = request:sub(1, e - 1) local line = request:sub(1, e - 1)
local r = {} local r = {}
local _, i
_, i, r.method, r.request = line:find("^([A-Z]+) (.-) HTTP/[1-9]+.[0-9]+$") _, i, r.method, r.request = line:find("^([A-Z]+) (.-) HTTP/[1-9]+.[0-9]+$")
if not (r.method and r.request) then if not (r.method and r.request) then
--print("invalid request: ") --print("invalid request: ")

13
srv/httpserver-static.lua Normal file
View File

@ -0,0 +1,13 @@
-- httpserver-static.lua
-- Part of nodemcu-httpserver, handles sending static files to client.
-- Author: Gregor Hartmann
return function (connection, req, args)
local buffer = dofile("httpserver-buffer.lc"):new()
dofile("httpserver-header.lc")(buffer, req.code or 200, args.ext, args.isGzipped)
-- Send header and return fileInfo
connection:send(buffer:getBuffer())
return { file = args.file, sent = 0}
end

View File

@ -2,24 +2,19 @@
-- Part of nodemcu-httpserver, configures NodeMCU's WiFI in boot. -- Part of nodemcu-httpserver, configures NodeMCU's WiFI in boot.
-- Author: Marcos Kirsch -- Author: Marcos Kirsch
local conf = nil local conf = dofile("httpserver-conf.lua")
if file.exists("httpserver-conf.lc") then
conf = dofile("httpserver-conf.lc")
else
conf = dofile("httpserver-conf.lua")
end
wifi.setmode(conf.wifi.mode) wifi.setmode(conf.wifi.mode)
if (conf.wifi.mode == wifi.SOFTAP) or (conf.wifi.mode == wifi.STATIONAP) then if (conf.wifi.mode == wifi.SOFTAP) or (conf.wifi.mode == wifi.STATIONAP) then
print('AP MAC: ',wifi.ap.getmac()) print('AP MAC: ',wifi.ap.getmac())
wifi.ap.config(conf.wifi.accessPoint.config) wifi.ap.config(conf.wifi.accessPoint.config)
wifi.ap.setip(conf.wifi.accessPoint.ip) wifi.ap.setip(conf.wifi.accessPoint.net)
end end
if (conf.wifi.mode == wifi.STATION) or (conf.wifi.mode == wifi.STATIONAP) then if (conf.wifi.mode == wifi.STATION) or (conf.wifi.mode == wifi.STATIONAP) then
print('Client MAC: ',wifi.sta.getmac()) print('Client MAC: ',wifi.sta.getmac())
wifi.sta.config(conf.wifi.station.ssid, conf.wifi.station.pwd, 1) wifi.sta.config(conf.wifi.station)
end end
print('chip: ',node.chipid()) print('chip: ',node.chipid())

View File

@ -13,6 +13,7 @@ return function (port)
-- We do it in a separate thread because we need to send in little chunks and wait for the onSent event -- We do it in a separate thread because we need to send in little chunks and wait for the onSent event
-- before we can send more, or we risk overflowing the mcu's buffer. -- before we can send more, or we risk overflowing the mcu's buffer.
local connectionThread local connectionThread
local fileInfo
local allowStatic = {GET=true, HEAD=true, POST=false, PUT=false, DELETE=false, TRACE=false, OPTIONS=false, CONNECT=false, PATCH=false} local allowStatic = {GET=true, HEAD=true, POST=false, PUT=false, DELETE=false, TRACE=false, OPTIONS=false, CONNECT=false, PATCH=false}
@ -26,6 +27,10 @@ return function (port)
end end
end end
local function startServingStatic(connection, req, args)
fileInfo = dofile("httpserver-static.lc")(connection, req, args)
end
local function startServing(fileServeFunction, connection, req, args) local function startServing(fileServeFunction, connection, req, args)
connectionThread = coroutine.create(function(fileServeFunction, bufferedConnection, req, args) connectionThread = coroutine.create(function(fileServeFunction, bufferedConnection, req, args)
fileServeFunction(bufferedConnection, req, args) fileServeFunction(bufferedConnection, req, args)
@ -40,6 +45,7 @@ return function (port)
local BufferedConnectionClass = dofile("httpserver-connection.lc") local BufferedConnectionClass = dofile("httpserver-connection.lc")
local bufferedConnection = BufferedConnectionClass:new(connection) local bufferedConnection = BufferedConnectionClass:new(connection)
BufferedConnectionClass = nil
local status, err = coroutine.resume(connectionThread, fileServeFunction, bufferedConnection, req, args) local status, err = coroutine.resume(connectionThread, fileServeFunction, bufferedConnection, req, args)
if not status then if not status then
log(connection, "Error: "..err) log(connection, "Error: "..err)
@ -50,7 +56,7 @@ return function (port)
end end
end end
local function handleRequest(connection, req) local function handleRequest(connection, req, handleError)
collectgarbage() collectgarbage()
local method = req.method local method = req.method
local uri = req.uri local uri = req.uri
@ -61,19 +67,19 @@ return function (port)
uri.args = {code = 400, errorString = "Bad Request", logFunction = log} uri.args = {code = 400, errorString = "Bad Request", logFunction = log}
fileServeFunction = dofile("httpserver-error.lc") fileServeFunction = dofile("httpserver-error.lc")
else else
local fileExists = file.open(uri.file, "r") local fileExists = false
file.close()
if not fileExists then if not file.exists(uri.file) then
-- print(uri.file .. " not found, checking gz version...")
-- gzip check -- gzip check
fileExists = file.open(uri.file .. ".gz", "r") if file.exists(uri.file .. ".gz") then
file.close() -- print("gzip variant exists, serving that one")
if fileExists then
--print("gzip variant exists, serving that one")
uri.file = uri.file .. ".gz" uri.file = uri.file .. ".gz"
uri.isGzipped = true uri.isGzipped = true
fileExists = true
end end
else
fileExists = true
end end
if not fileExists then if not fileExists then
@ -84,7 +90,8 @@ return function (port)
else else
if allowStatic[method] then if allowStatic[method] then
uri.args = {file = uri.file, ext = uri.ext, isGzipped = uri.isGzipped} uri.args = {file = uri.file, ext = uri.ext, isGzipped = uri.isGzipped}
fileServeFunction = dofile("httpserver-static.lc") startServingStatic(connection, req, uri.args)
return
else else
uri.args = {code = 405, errorString = "Method not supported", logFunction = log} uri.args = {code = 405, errorString = "Method not supported", logFunction = log}
fileServeFunction = dofile("httpserver-error.lc") fileServeFunction = dofile("httpserver-error.lc")
@ -95,8 +102,8 @@ return function (port)
end end
local function onReceive(connection, payload) local function onReceive(connection, payload)
collectgarbage() -- collectgarbage()
local conf = dofile("httpserver-conf.lc") local conf = dofile("httpserver-conf.lua")
local auth local auth
local user = "Anonymous" local user = "Anonymous"
@ -162,6 +169,27 @@ return function (port)
connectionThread = nil connectionThread = nil
collectgarbage() collectgarbage()
end end
elseif fileInfo then
local fileSize = file.list()[fileInfo.file]
-- Chunks larger than 1024 don't work.
-- https://github.com/nodemcu/nodemcu-firmware/issues/1075
local chunkSize = 512
local fileHandle = file.open(fileInfo.file)
if fileSize > fileInfo.sent then
fileHandle:seek("set", fileInfo.sent)
local chunk = fileHandle:read(chunkSize)
fileHandle:close()
fileHandle = nil
fileInfo.sent = fileInfo.sent + #chunk
connection:send(chunk)
-- print(fileInfo.file .. ": Sent "..#chunk.. " bytes, " .. fileSize - fileInfo.sent .. " to go.")
chunk = nil
else
log(connection, "closing connetion", "Finished sending: "..fileInfo.file)
connection:close()
fileInfo = nil
end
collectgarbage()
end end
end end
@ -172,6 +200,10 @@ return function (port)
connectionThread = nil connectionThread = nil
collectgarbage() collectgarbage()
end end
if fileInfo then
fileInfo = nil
collectgarbage()
end
end end
connection:on("receive", onReceive) connection:on("receive", onReceive)