Browse Source

Merged with main repository

Marcos Kirsch 8 years ago
parent
commit
fbc6506faa
13 changed files with 334 additions and 166 deletions
  1. 3 2
      README.md
  2. 26 25
      http/args.lua
  3. 28 27
      http/file_list.lua
  4. 1 1
      http/garage_door_opener.lua
  5. 1 1
      http/index.html
  6. 0 7
      http/post.html
  7. 33 0
      http/post.lua
  8. 1 1
      httpserver-error.lua
  9. 1 6
      httpserver-header.lua
  10. 58 2
      httpserver-request.lua
  11. 2 2
      httpserver-static.lua
  12. 85 11
      httpserver.lua
  13. 95 81
      init.lua

+ 3 - 2
README.md

@@ -3,13 +3,14 @@ A (very) simple web server written in Lua for the ESP8266 running the NodeMCU fi
 
 ## Features
 
-* GET
+* GET, POST, PUT and minor changes to support other methods
 * Multiple MIME types
 * Error pages (404 and others)
 * Server-side execution of Lua scripts
-* Query string argument parsing
+* Query string argument parsing with decoding of arguments
 * Serving .gz compressed files
 * HTTP Basic Authentication
+* Decoding of request bodies in both application/x-www-form-urlencoded and application/json (if cjson is available)
 
 ## How to use
 

+ 26 - 25
http/args.lua

@@ -1,25 +1,26 @@
-return function (connection, args)
-   dofile("httpserver-header.lc")(connection, 200, 'html')
-
-   connection:send([===[
-   <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Arguments</title></head><body><h1>Arguments</h1>
-   <form method="GET">
-      First name:<br><input type="text" name="firstName"><br>
-      Last name:<br><input type="text" name="lastName"><br>
-      <input type="radio" name="sex" value="male" checked>Male<input type="radio" name="sex" value="female">Female<br>
-      <input type="submit" name="submit" value="Submit">
-   </form>
-   ]===])
-   coroutine.yield()
-
-   if args["submit"] ~= nil then
-      connection:send("<h2>Received the following values:</h2><ul>")
-      coroutine.yield()
-      for name, value in pairs(args) do
-         connection:send('<li><b>' .. name .. ':</b> ' .. tostring(value) .. "<br></li>\n")
-         coroutine.yield()
-      end
-   end
-
-   connection:send("</ul>\n</body></html>")
-end
+return function (connection, args)
+   dofile("httpserver-header.lc")(connection, 200, 'html')
+   connection:send([===[
+   <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Arguments</title></head><body><h1>Arguments</h1>
+   <form method="GET">
+      First name:<br><input type="text" name="firstName"><br>
+      Last name:<br><input type="text" name="lastName"><br>
+      <input type="radio" name="sex" value="male" checked>Male<input type="radio" name="sex" value="female">Female<br>
+      <input type="submit" name="submit" value="Submit">
+   </form>
+   ]===])
+   coroutine.yield()
+
+   if args["submit"] ~= nil then
+      connection:send("<h2>Received the following values:</h2><ul>")
+      coroutine.yield()
+      for name, value in pairs(args) do
+         connection:send('<li><b>' .. name .. ':</b> ' .. tostring(value) .. "<br></li>\n")
+         coroutine.yield()
+      end
+   end
+
+   connection:send("</ul>\n</body></html>")
+
+end
+

+ 28 - 27
http/file_list.lua

@@ -1,27 +1,28 @@
-return function (connection, args)
-   dofile("httpserver-header.lc")(connection, 200, 'html')
-
-   connection:send([===[
-      <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Server File Listing</title></head>
-      <body>
-   <h1>Server File Listing</h1>
-   ]===])
-   coroutine.yield()
-
-   local remaining, used, total=file.fsinfo()
-   connection:send("<b>Total size: </b> " .. total .. " bytes<br/>\n" ..
-                   "<b>In Use: </b> " .. used .. " bytes<br/>\n" ..
-                   "<b>Free: </b> " .. remaining .. " bytes<br/>\n" ..
-                   "<p>\n<b>Files:</b><br/>\n<ul>\n")
-   coroutine.yield()
-
-   for name, size in pairs(file.list()) do
-      local isHttpFile = string.match(name, "(http/)") ~= nil
-      if isHttpFile then
-         local url = string.match(name, ".*/(.*)")
-         connection:send('   <li><a href="' .. url .. '">' .. url .. "</a> (" .. size .. " bytes)</li>\n")
-         coroutine.yield()
-      end
-   end
-   connection:send("</ul>\n</p>\n</body></html>")
-end
+return function (connection, args)
+   dofile("httpserver-header.lc")(connection, 200, 'html')
+
+   connection:send([===[
+      <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Server File Listing</title></head>
+      <body>
+   <h1>Server File Listing</h1>
+   ]===])
+   coroutine.yield()
+
+   local remaining, used, total=file.fsinfo()
+   connection:send("<b>Total size: </b> " .. total .. " bytes<br/>\n" ..
+                   "<b>In Use: </b> " .. used .. " bytes<br/>\n" ..
+                   "<b>Free: </b> " .. remaining .. " bytes<br/>\n" ..
+                   "<p>\n<b>Files:</b><br/>\n<ul>\n")
+   coroutine.yield()
+
+   for name, size in pairs(file.list()) do
+      local isHttpFile = string.match(name, "(http/)") ~= nil
+      if isHttpFile then
+         local url = string.match(name, ".*/(.*)")
+         connection:send('   <li><a href="' .. url .. '">' .. url .. "</a> (" .. size .. " bytes)</li>\n")
+         coroutine.yield()
+      end
+   end
+   connection:send("</ul>\n</p>\n</body></html>")
+end
+

+ 1 - 1
http/garage_door_opener.lua

@@ -22,7 +22,7 @@ local function pushTheButton(connection, pin)
 
 end
 
-return function (connection, args)
+return function (connection, req, args)
    print('Garage door button was pressed!', args.door)
    if     args.door == "1" then pushTheButton(connection, 1)   -- GPIO1
    elseif args.door == "2" then pushTheButton(connection, 2)   -- GPIO2

+ 1 - 1
http/index.html

@@ -22,7 +22,7 @@
       <li><a href="index.html">Index</a>: This page (static)</li>
       <li><a href="zipped.html.gz">Zipped</a>: A compressed file (static)</li>
       <li><a href="args.lua">Arguments</a>: Parses arguments passed in the URL and prints them. (Lua)</li>
-      <li><a href="post.html">Post</a>: A form that uses POST method, should error. (static)</li>
+      <li><a href="post.lua">Post</a>: A form that uses POST method.  Displays different content based on HTTP method. (Lua)</li>
       <li><a href="garage_door_opener.html">Garage door opener</a>: Control GPIO lines via the server. (Lua)</li>
       <li><a href="node_info.lua">NodeMCU info</a>: Shows some basic NodeMCU(Lua)</li>
       <li><a href="file_list.lua">List all server files</a>: Displays a list of all the server files. (Lua)</li>

+ 0 - 7
http/post.html

@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<html lang="en"><head><meta charset="utf-8"><title>Post</title></head><body>
-<h1>Post</h1>
-This form uses POST method which is not supported by nodemcu-httpserver.<br>
-You should get an error when you press submit.
-<form method="POST"><input type="submit" value="Submit"></form>
-</body></html>

+ 33 - 0
http/post.lua

@@ -0,0 +1,33 @@
+return function (connection, req, args)
+   connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n")
+   connection:send('<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Arguments</title></head>')
+   connection:send('<body>')
+   connection:send('<h1>Arguments</h1>')
+
+   local form = [===[
+   <form method="POST">
+      First name:<br><input type="text" name="firstName"><br>
+      Last name:<br><input type="text" name="lastName"><br>
+      <input type="radio" name="sex" value="male" checked>Male<input type="radio" name="sex" value="female">Female<br>
+      <input type="submit" value="Submit">
+   </form>
+   ]===]
+   
+   if req.method == "GET" then
+      connection:send(form)
+   elseif req.method == "POST" then
+     local rd = req.getRequestData()
+   --   connection:send(cjson.encode(rd))
+      connection:send('<h2>Received the following values:</h2>')
+      connection:send("<ul>\n")
+      for name, value in pairs(rd) do
+          connection:send('<li><b>' .. name .. ':</b> ' .. tostring(value) .. "<br></li>\n")
+      end
+
+      connection:send("</ul>\n")
+   else
+      connection:send("NOT IMPLEMENTED")
+   end
+   
+   connection:send('</body></html>')
+end

+ 1 - 1
httpserver-error.lua

@@ -2,7 +2,7 @@
 -- Part of nodemcu-httpserver, handles sending error pages to client.
 -- Author: Marcos Kirsch
 
-return function (connection, args)
+return function (connection, req, args)
 
    local function getHeader(connection, code, errorString, extraHeaders, mimeType)
       local header = "HTTP/1.0 " .. code .. " " .. errorString .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType .. "\r\n"

+ 1 - 6
httpserver-header.lua

@@ -2,7 +2,7 @@
 -- Part of nodemcu-httpserver, knows how to send an HTTP header.
 -- Author: Marcos Kirsch
 
-return function (connection, code, extension)
+return function (connection, code, extension, gzip)
 
    local function getHTTPStatusString(code)
       local codez = {[200]="OK", [400]="Bad Request", [404]="Not Found",}
@@ -15,11 +15,6 @@ return function (connection, code, extension)
       local gzip = false
       -- 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", jpg = "image/jpeg", js = "application/javascript", json = "application/json", png = "image/png", xml = "text/xml"}
-      -- add comressed flag if file ends with gz
-      if ext:find("%.gz$") then
-          ext = ext:sub(1, -4)
-          gzip = true
-      end
       if mt[ext] then contentType = mt[ext] else contentType = "text/plain" end
       return {contentType = contentType, gzip = gzip}
    end

+ 58 - 2
httpserver-request.lua

@@ -12,17 +12,67 @@ local function uriToFilename(uri)
    return "http/" .. string.sub(uri, 2, -1)
 end
 
+local function hex_to_char(x)
+  return string.char(tonumber(x, 16))
+end
+
+local function uri_decode(input)
+  return input:gsub("%+", " "):gsub("%%(%x%x)", hex_to_char)
+end
+
 local function parseArgs(args)
    local r = {}; i=1
    if args == nil or args == "" then return r end
    for arg in string.gmatch(args, "([^&]+)") do
       local name, value = string.match(arg, "(.*)=(.*)")
-      if name ~= nil then r[name] = value end
+      if name ~= nil then r[name] = uri_decode(value) end
       i = i + 1
    end
    return r
 end
 
+local function parseFormData(body)
+  local data = {}
+  print("Parsing Form Data")
+  for kv in body.gmatch(body, "%s*&?([^=]+=[^&]+)") do
+    local key, value = string.match(kv, "(.*)=(.*)")
+    
+    print("Parsed: " .. key .. " => " .. value)
+    data[key] = uri_decode(value)
+  end
+  
+  return data
+end
+
+local function getRequestData(payload)
+  local requestData
+  return function ()
+    print("Getting Request Data")
+    if requestData then
+      return requestData
+    else
+      local mimeType = string.match(payload, "Content%-Type: ([%w/-]+)")
+      local body_start = payload:find("\r\n\r\n", 1, true)
+      local body = payload:sub(body_start, #payload)
+      payload = nil
+      collectgarbage()
+      
+      -- print("mimeType = [" .. mimeType .. "]")
+      
+      if mimeType == "application/json" then
+        print("JSON: " .. body)
+        requestData = cjson.decode(body)
+      elseif mimeType == "application/x-www-form-urlencoded" then
+        requestData = parseFormData(body)
+      else
+        requestData = {}
+      end
+      
+      return requestData
+    end
+  end
+end
+
 local function parseUri(uri)
    local r = {}
    local filename
@@ -44,7 +94,12 @@ local function parseUri(uri)
       filename,ext = filename:match("(.+)%.(.+)")
       table.insert(fullExt,1,ext)
    end
-   r.ext = table.concat(fullExt,".")
+   if #fullExt > 1 and fullExt[#fullExt] == 'gz' then
+      r.ext = fullExt[#fullExt-1]
+      r.isGzipped = true
+   elseif #fullExt >= 1 then
+      r.ext = fullExt[#fullExt]
+   end
    r.isScript = r.ext == "lua" or r.ext == "lc"
    r.file = uriToFilename(r.file)
    return r
@@ -61,5 +116,6 @@ return function (request)
    _, i, r.method, r.request = line:find("^([A-Z]+) (.-) HTTP/[1-9]+.[0-9]+$")
    r.methodIsValid = validateMethod(r.method)
    r.uri = parseUri(r.request)
+   r.getRequestData = getRequestData(request)
    return r
 end

+ 2 - 2
httpserver-static.lua

@@ -2,8 +2,8 @@
 -- Part of nodemcu-httpserver, handles sending static files to client.
 -- Author: Marcos Kirsch
 
-return function (connection, args)
-   dofile("httpserver-header.lc")(connection, 200, args.ext)
+return function (connection, req, args)
+   dofile("httpserver-header.lc")(connection, 200, args.ext, args.gzipped)
    --print("Begin sending:", args.file)
    -- Send file in little chunks
    local continue = true

+ 85 - 11
httpserver.lua

@@ -13,10 +13,58 @@ return function (port)
          -- We do it in a separate thread because we need to yield when sending lots
          -- of data in order to avoid overflowing the mcu's buffer.
          local connectionThread
+        
+         local allowStatic = {GET=true, HEAD=true, POST=false, PUT=false, DELETE=false, TRACE=false, OPTIONS=false, CONNECT=false, PATCH=false}
 
-         local function onGet(connection, uri)
+         local function startServing(fileServeFunction, connection, req, args) 
+            local bufferedConnection = {}
+            connectionThread = coroutine.create(function(fileServeFunction, bconnection, req, args)
+               fileServeFunction(bconnection, req, args)
+               if not bconnection:flush() then
+	         connection:close()
+                 connectionThread = nil
+ 	       end
+            end)
+            function bufferedConnection:flush() 
+              if self.size > 0 then 
+		connection:send(table.concat(self.data, ""))
+                self.data = {}
+                self.size = 0    
+		return true
+	      end
+	      return false
+            end
+            function bufferedConnection:send(payload) 
+              local l = payload:len()
+              if l + self.size > 1000 then
+                 if self:flush() then
+                   coroutine.yield()          
+		 end
+              end
+	      if l > 800 then
+		connection:send(payload)
+		coroutine.yield()
+	      else
+                table.insert(self.data, payload)
+                self.size = self.size + l
+              end
+            end
+            bufferedConnection.size = 0
+            bufferedConnection.data = {}
+            local status, err = coroutine.resume(connectionThread, fileServeFunction, bufferedConnection, req, args)
+            if not status then
+               print(err)
+            end
+         end
+
+         local function onRequest(connection, req)
             collectgarbage()
+            local method = req.method
+            local uri = req.uri
             local fileServeFunction = nil
+            
+            print("Method: " .. method);
+            
             if #(uri.file) > 32 then
                -- nodemcu-firmware cannot handle long filenames.
                uri.args = {code = 400, errorString = "Bad Request"}
@@ -24,18 +72,35 @@ return function (port)
             else
                local fileExists = file.open(uri.file, "r")
                file.close()
+            
+               if not fileExists then
+                 -- gzip check
+                 fileExists = file.open(uri.file .. ".gz", "r")
+                 file.close()
+
+                 if fileExists then
+                    print("gzip variant exists, serving that one")
+                    uri.file = uri.file .. ".gz"
+                    uri.isGzipped = true
+                 end
+               end
+
                if not fileExists then
                   uri.args = {code = 404, errorString = "Not Found"}
                   fileServeFunction = dofile("httpserver-error.lc")
                elseif uri.isScript then
                   fileServeFunction = dofile(uri.file)
                else
-                  uri.args = {file = uri.file, ext = uri.ext}
-                  fileServeFunction = dofile("httpserver-static.lc")
+                  if allowStatic[method] then
+                    uri.args = {file = uri.file, ext = uri.ext, gzipped = uri.isGzipped}
+                    fileServeFunction = dofile("httpserver-static.lc")
+                  else
+                    uri.args = {code = 405, errorString = "Method not supported"}
+                    fileServeFunction = dofile("httpserver-error.lc")
+                  end
                end
             end
-            connectionThread = coroutine.create(fileServeFunction)
-            coroutine.resume(connectionThread, connection, uri.args)
+            startServing(fileServeFunction, connection, req, uri.args)
          end
 
          local function onReceive(connection, payload)
@@ -51,8 +116,9 @@ return function (port)
                auth = dofile("httpserver-basicauth.lc")
                user = auth.authenticate(payload) -- authenticate returns nil on failed auth
             end
-            if user and req.methodIsValid and req.method == "GET" then
-               onGet(connection, req.uri)
+
+            if user and req.methodIsValid and (req.method == "GET" or req.method == "POST" or req.method == "PUT") then
+               onRequest(connection, req)
             else
                local args = {}
                local fileServeFunction = dofile("httpserver-error.lc")
@@ -63,18 +129,20 @@ return function (port)
                else
                   args = {code = 400, errorString = "Bad Request"}
                end
-               connectionThread = coroutine.create(fileServeFunction)
-               coroutine.resume(connectionThread, connection, args)
+               startServing(fileServeFunction, connection, req, args)
             end
          end
 
          local function onSent(connection, payload)
             collectgarbage()
             if connectionThread then
-               local connectionThreadStatus = coroutine.status(connectionThread)
+               local connectionThreadStatus = coroutine.status(connectionThread) 
                if connectionThreadStatus == "suspended" then
                   -- Not finished sending file, resume.
-                  coroutine.resume(connectionThread)
+                  local status, err = coroutine.resume(connectionThread)
+                  if not status then
+                     print(err)
+                  end
                elseif connectionThreadStatus == "dead" then
                   -- We're done sending file.
                   connection:close()
@@ -85,6 +153,12 @@ return function (port)
 
          connection:on("receive", onReceive)
          connection:on("sent", onSent)
+         connection:on("disconnection",function(c)
+            if connectionThread then
+               connectionThread = nil
+               collectgarbage()
+            end
+         end) 
 
       end
    )

+ 95 - 81
init.lua

@@ -1,81 +1,95 @@
--- Begin WiFi configuration
-
-local wifiConfig = {}
-
--- Uncomment the WiFi mode you want.
-wifiConfig.mode = wifi.STATION         -- station: join a WiFi network
--- wifiConfig.mode = wifi.AP              -- access point: create a WiFi network
--- wifiConfig.mode = wifi.STATIONAP       -- both station and access point
-
-wifiConfig.accessPointConfig = {}
-wifiConfig.accessPointConfig.ssid = "ESP-"..node.chipid()   -- Name of the SSID you want to create
-wifiConfig.accessPointConfig.pwd = "ESP-"..node.chipid()    -- WiFi password - at least 8 characters
-
--- Configure fixed IP address
-wifi.sta.setip({ip="10.0.7.111q", netmask="255.255.224.0", gateway="24.55.0.1"})
-
-wifiConfig.stationPointConfig = {}
-wifiConfig.stationPointConfig.ssid = "Internet"        -- Name of the WiFi network you want to join
-wifiConfig.stationPointConfig.pwd =  ""                -- Password for the WiFi network
-
--- Tell the chip to connect to the access point
-
-wifi.setmode(wifiConfig.mode)
-print('set (mode='..wifi.getmode()..')')
-print('MAC: ',wifi.sta.getmac())
-print('chip: ',node.chipid())
-print('heap: ',node.heap())
-
-wifi.ap.config(wifiConfig.accessPointConfig)
-wifi.sta.config(wifiConfig.stationPointConfig.ssid, wifiConfig.stationPointConfig.pwd)
-wifiConfig = nil
-collectgarbage()
-
--- End WiFi configuration
-
--- Compile server code and remove original .lua files.
--- This only happens the first time afer the .lua files are uploaded.
-
-local compileAndRemoveIfNeeded = function(f)
-   if file.open(f) then
-      file.close()
-      print('Compiling:', f)
-      node.compile(f)
-      file.remove(f)
-      collectgarbage()
-   end
-end
-
-local serverFiles = {'httpserver.lua', 'httpserver-basicauth.lua', 'httpserver-conf.lua', 'httpserver-b64decode.lua', 'httpserver-request.lua', 'httpserver-static.lua', 'httpserver-header.lua', 'httpserver-error.lua'}
-for i, f in ipairs(serverFiles) do compileAndRemoveIfNeeded(f) end
-
-compileAndRemoveIfNeeded = nil
-serverFiles = nil
-collectgarbage()
-
--- Connect to the WiFi access point.
--- Once the device is connected, you may start the HTTP server.
-
-local joinCounter = 0
-local joinMaxAttempts = 5
-tmr.alarm(0, 3000, 1, function()
-   local ip = wifi.sta.getip()
-   if ip == nil and joinCounter < joinMaxAttempts then
-      print('Connecting to WiFi Access Point ...')
-      joinCounter = joinCounter +1
-   else
-      if joinCounter == joinMaxAttempts then
-         print('Failed to connect to WiFi Access Point.')
-      else
-         print('IP: ',ip)
-         -- Uncomment to automatically start the server in port 80
-         --dofile("httpserver.lc")(80)
-      end
-      tmr.stop(0)
-      joinCounter = nil
-      joinMaxAttempts = nil
-      collectgarbage()
-   end
-
-end)
-
+-- Begin WiFi configuration
+
+local wifiConfig = {}
+
+-- wifi.STATION         -- station: join a WiFi network
+-- wifi.SOFTAP          -- access point: create a WiFi network
+-- wifi.wifi.STATIONAP  -- both station and access point
+wifiConfig.mode = wifi.STATIONAP  -- both station and access point
+
+wifiConfig.accessPointConfig = {}
+wifiConfig.accessPointConfig.ssid = "ESP-"..node.chipid()   -- Name of the SSID you want to create
+wifiConfig.accessPointConfig.pwd = "ESP-"..node.chipid()    -- WiFi password - at least 8 characters
+
+wifiConfig.accessPointIpConfig = {}
+wifiConfig.accessPointIpConfig.ip = "192.168.111.1"
+wifiConfig.accessPointIpConfig.netmask = "255.255.255.0"
+wifiConfig.accessPointIpConfig.gateway = "192.168.111.1"
+
+wifiConfig.stationPointConfig = {}
+wifiConfig.stationPointConfig.ssid = "Internet"        -- Name of the WiFi network you want to join
+wifiConfig.stationPointConfig.pwd =  ""                -- Password for the WiFi network
+
+-- Tell the chip to connect to the access point
+
+wifi.setmode(wifiConfig.mode)
+print('set (mode='..wifi.getmode()..')')
+
+if (wifiConfig.mode == wifi.SOFTAP) or (wifiConfig.mode == wifi.STATIONAP) then
+    print('AP MAC: ',wifi.ap.getmac())
+    wifi.ap.config(wifiConfig.accessPointConfig)
+    wifi.ap.setip(wifiConfig.accessPointIpConfig)
+end
+if (wifiConfig.mode == wifi.STATION) or (wifiConfig.mode == wifi.STATIONAP) then
+    print('Client MAC: ',wifi.sta.getmac())
+    wifi.sta.config(wifiConfig.stationPointConfig.ssid, wifiConfig.stationPointConfig.pwd, 1)
+end
+
+print('chip: ',node.chipid())
+print('heap: ',node.heap())
+
+wifiConfig = nil
+collectgarbage()
+
+-- End WiFi configuration
+
+-- Compile server code and remove original .lua files.
+-- This only happens the first time afer the .lua files are uploaded.
+
+local compileAndRemoveIfNeeded = function(f)
+   if file.open(f) then
+      file.close()
+      print('Compiling:', f)
+      node.compile(f)
+      file.remove(f)
+      collectgarbage()
+   end
+end
+
+local serverFiles = {'httpserver.lua', 'httpserver-basicauth.lua', 'httpserver-conf.lua', 'httpserver-b64decode.lua', 'httpserver-request.lua', 'httpserver-static.lua', 'httpserver-header.lua', 'httpserver-error.lua'}
+for i, f in ipairs(serverFiles) do compileAndRemoveIfNeeded(f) end
+
+compileAndRemoveIfNeeded = nil
+serverFiles = nil
+collectgarbage()
+
+-- Connect to the WiFi access point.
+-- Once the device is connected, you may start the HTTP server.
+
+if (wifi.getmode() == wifi.STATION) or (wifi.getmode() == wifi.STATIONAP) then
+    local joinCounter = 0
+    local joinMaxAttempts = 5
+    tmr.alarm(0, 3000, 1, function()
+       local ip = wifi.sta.getip()
+       if ip == nil and joinCounter < joinMaxAttempts then
+          print('Connecting to WiFi Access Point ...')
+          joinCounter = joinCounter +1
+       else
+          if joinCounter == joinMaxAttempts then
+             print('Failed to connect to WiFi Access Point.')
+          else
+             print('IP: ',ip)
+          end
+          tmr.stop(0)
+          joinCounter = nil
+          joinMaxAttempts = nil
+          collectgarbage()
+       end
+    end)
+end
+
+-- Uncomment to automatically start the server in port 80
+if (not not wifi.sta.getip()) or (not not wifi.ap.getip()) then
+    --dofile("httpserver.lc")(80)
+end
+